- Implement upsert logic in import_phone() function - Check for existing (id, phone) combinations before insert - Track duplicates within CSV to skip gracefully - Update existing records instead of failing on duplicates - Add detailed statistics: inserted, updated, skipped counts - Align with upsert pattern used in other import functions - Add documentation in docs/PHONE_IMPORT_FIX.md Fixes: UNIQUE constraint failed: phone.id, phone.phone error when re-importing or uploading CSV with duplicate entries
3.1 KiB
3.1 KiB
Phone Import Unique Constraint Fix
Issue
When uploading PHONE.csv, the import would fail with a SQLite integrity error:
UNIQUE constraint failed: phone.id, phone.phone
This error occurred at row 507 and cascaded to subsequent rows due to transaction rollback.
Root Cause
The LegacyPhone model has a composite primary key on (id, phone) to prevent duplicate phone number entries for the same person/entity. The original import_phone() function used bulk inserts without checking for existing records, causing the constraint violation when:
- Re-importing the same CSV file
- The CSV contains duplicate
(id, phone)combinations - Partial imports left some data in the database
Solution
Updated import_phone() in /app/import_legacy.py to implement an upsert strategy:
Changes Made
- Check for duplicates within CSV: Track seen
(id, phone)combinations to skip duplicates in the same import - Check database for existing records: Query for existing
(id, phone)before inserting - Update or Insert:
- If record exists → update the
locationfield - If record doesn't exist → insert new record
- If record exists → update the
- Enhanced error handling: Rollback only the failed row, not the entire batch
- Better logging: Track
inserted,updated, andskippedcounts separately
Code Changes
# Before: Bulk insert without checking
db.bulk_save_objects(batch)
db.commit()
# After: Upsert with duplicate handling
existing = db.query(LegacyPhone).filter(
LegacyPhone.id == rolodex_id,
LegacyPhone.phone == phone
).first()
if existing:
existing.location = clean_string(row.get('Location'))
result['updated'] += 1
else:
record = LegacyPhone(...)
db.add(record)
result['inserted'] += 1
Result Tracking
The function now returns detailed statistics:
success: Total successfully processed rowsinserted: New records addedupdated: Existing records updatedskipped: Duplicate combinations within the CSVerrors: List of error messages for failed rowstotal_rows: Total rows in CSV
Testing
After deploying this fix:
- Uploading
PHONE.csvfor the first time will insert all records - Re-uploading the same file will update existing records (no errors)
- Uploading a CSV with internal duplicates will skip duplicates gracefully
Consistency with Other Imports
This fix aligns import_phone() with the upsert pattern already used in:
import_rolodex()- handles duplicates by IDimport_trnstype()- upserts by T_Typeimport_trnslkup()- upserts by T_Codeimport_footers()- upserts by F_Code- And other reference table imports
Related Files
/app/import_legacy.py- Contains the fixedimport_phone()function/app/models.py- DefinesLegacyPhonemodel with composite PK/app/main.py- Routes CSV uploads to import functions
Prevention
To prevent similar issues in future imports:
- Always use upsert logic for tables with unique constraints
- Test re-imports of the same CSV file
- Handle duplicates within the CSV gracefully
- Provide detailed success/error statistics to users