This error occurs when you attempt to insert or update a row with a primary key value that already exists in the table. SQLite enforces primary key uniqueness strictly, rejecting duplicate key values to maintain data integrity and ensure each row is uniquely identifiable.
The SQLITE_CONSTRAINT_PRIMARYKEY error (error code 1555) indicates that your SQL query violates the PRIMARY KEY constraint on a table. A PRIMARY KEY constraint ensures that each row in a table has a unique, non-null identifier that distinguishes it from all other rows. When you attempt to INSERT or UPDATE a record with a primary key value that already exists in the table, SQLite aborts the operation and raises this constraint violation error. The operation fails completely, and no changes are committed to the database unless you use a conflict resolution clause like ON CONFLICT or INSERT OR REPLACE.
Before performing an INSERT, query the table to verify that the primary key value does not already exist:
-- Check if a record with this primary key already exists
SELECT COUNT(*) FROM users WHERE id = 1;
-- If count is 0, safe to insert
IF count = 0 THEN
INSERT INTO users (id, name, email) VALUES (1, 'John', '[email protected]');
END IF;In Python with sqlite3:
import sqlite3
connection = sqlite3.connect('database.db')
cursor = connection.cursor()
# Check if id exists
cursor.execute('SELECT COUNT(*) FROM users WHERE id = ?', (1,))
if cursor.fetchone()[0] == 0:
cursor.execute('INSERT INTO users (id, name, email) VALUES (?, ?, ?)',
(1, 'John', '[email protected]'))
connection.commit()
else:
print('Record with id 1 already exists')If you want to replace an existing record when a duplicate primary key is encountered, use the REPLACE conflict resolution algorithm. This deletes the old row and inserts the new one:
-- Will replace any existing user with id 1
REPLACE INTO users (id, name, email) VALUES (1, 'John Smith', '[email protected]');Or use the ON CONFLICT clause (modern SQLite 3.24.0+):
INSERT INTO users (id, name, email) VALUES (1, 'John Smith', '[email protected]')
ON CONFLICT(id) DO UPDATE SET name = 'John Smith', email = '[email protected]';In Python:
cursor.execute('''
INSERT INTO users (id, name, email) VALUES (?, ?, ?)
ON CONFLICT(id) DO UPDATE SET name = ?, email = ?
''', (1, 'John Smith', '[email protected]', 'John Smith', '[email protected]'))
connection.commit()If you want to silently skip inserting a duplicate primary key (without raising an error), use the IGNORE conflict resolution algorithm:
-- Will skip this insert if id 1 already exists, no error raised
INSERT OR IGNORE INTO users (id, name, email) VALUES (1, 'John', '[email protected]');This is useful when batch-inserting data and you want to skip duplicates without stopping the entire operation.
cursor.execute('''
INSERT OR IGNORE INTO users (id, name, email) VALUES (?, ?, ?)
''', (1, 'John', '[email protected]'))
connection.commit()Instead of manually assigning id values, let SQLite auto-generate unique primary keys using AUTOINCREMENT. This prevents primary key conflicts when inserting new rows:
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL
);
-- Don't specify id, let SQLite generate it automatically
INSERT INTO users (name, email) VALUES ('John', '[email protected]');
INSERT INTO users (name, email) VALUES ('Jane', '[email protected]');
-- id values automatically become 1, 2, 3, etc.Key points:
- Use exactly "INTEGER PRIMARY KEY AUTOINCREMENT" (not INT or BIGINT)
- Don't pass an id value in your INSERT statement
- Each insert automatically gets a unique, incrementing id
In Python:
cursor.execute('''
INSERT INTO users (name, email) VALUES (?, ?)
''', ('John', '[email protected]'))
connection.commit()
print(f'New user id: {cursor.lastrowid}')If your table has duplicate primary keys due to a data import error, truncate the table and re-import the data with duplicate handling:
-- Delete all rows from the table
DELETE FROM users;
-- Optionally reset the autoincrement counter
DELETE FROM sqlite_sequence WHERE name='users';
-- Now reimport data using INSERT OR IGNORE or INSERT OR REPLACE
INSERT OR IGNORE INTO users (id, name, email)
SELECT id, name, email FROM users_temp;In Python:
# Delete existing data
cursor.execute('DELETE FROM users')
cursor.execute("DELETE FROM sqlite_sequence WHERE name='users'")
connection.commit()
# Re-import with conflict handling
cursor.execute('''
INSERT OR IGNORE INTO users (id, name, email)
SELECT id, name, email FROM users_temp
''')
connection.commit()Wrap your INSERT/UPDATE operations in a try-except block to gracefully handle primary key constraint violations:
import sqlite3
connection = sqlite3.connect('database.db')
cursor = connection.cursor()
try:
cursor.execute('''
INSERT INTO users (id, name, email) VALUES (?, ?, ?)
''', (1, 'John', '[email protected]'))
connection.commit()
print('User inserted successfully')
except sqlite3.IntegrityError as e:
if 'PRIMARY KEY constraint failed' in str(e):
print(f'User with id 1 already exists. Updating instead...')
cursor.execute('''
UPDATE users SET name = ?, email = ? WHERE id = ?
''', ('John', '[email protected]', 1))
connection.commit()
else:
raiseIn JavaScript (Node.js with better-sqlite3):
const Database = require('better-sqlite3');
const db = new Database('database.db');
try {
const stmt = db.prepare('INSERT INTO users (id, name, email) VALUES (?, ?, ?)');
stmt.run(1, 'John', '[email protected]');
console.log('User inserted successfully');
} catch (error) {
if (error.message.includes('PRIMARY KEY constraint failed')) {
console.log('User with id 1 already exists. Updating instead...');
const updateStmt = db.prepare('UPDATE users SET name = ?, email = ? WHERE id = ?');
updateStmt.run('John', '[email protected]', 1);
} else {
throw error;
}
}ROWID and INTEGER PRIMARY KEY: In SQLite, a column defined as INTEGER PRIMARY KEY is an alias for the internal ROWID. When you don't use AUTOINCREMENT, SQLite automatically reuses ROWIDs from deleted rows. This can cause primary key conflicts if you're not careful. AUTOINCREMENT prevents this reuse but comes with minor performance overhead.
sqlite_sequence table: When you use AUTOINCREMENT, SQLite maintains an internal table called sqlite_sequence that tracks the highest ROWID ever used. You can inspect or reset it:
-- View the current autoincrement counter
SELECT * FROM sqlite_sequence WHERE name='users';
-- Reset the counter to 100
UPDATE sqlite_sequence SET seq = 100 WHERE name='users';
-- Delete to reset to 0
DELETE FROM sqlite_sequence WHERE name='users';Composite PRIMARY KEYs: You can define a primary key across multiple columns to enforce uniqueness on combinations of values:
CREATE TABLE user_profiles (
user_id INTEGER,
platform TEXT,
username TEXT,
PRIMARY KEY (user_id, platform)
);
-- OK: Same user on different platforms
INSERT INTO user_profiles VALUES (1, 'twitter', '@john');
INSERT INTO user_profiles VALUES (1, 'github', 'john123');
-- PRIMARY KEY constraint failed: Duplicate (user_id, platform) combination
INSERT INTO user_profiles VALUES (1, 'twitter', '@john_dev');ON CONFLICT clause: Modern SQLite (3.24.0+) supports the ON CONFLICT clause for atomic insert-or-update operations:
-- Update if exists, insert if not
INSERT INTO users (id, name, email) VALUES (1, 'John', '[email protected]')
ON CONFLICT(id) DO UPDATE SET name = 'John', email = '[email protected]';
-- Or do nothing if a conflict occurs
INSERT INTO users (id, name, email) VALUES (1, 'John', '[email protected]')
ON CONFLICT(id) DO NOTHING;ORM-specific issues: Some ORMs (like Room in Android or certain versions of SQLAlchemy) may incorrectly set the primary key to 0 or a default value during insert instead of letting SQLite auto-generate it. This causes every inserted row to overwrite the first row. Check your ORM documentation for how to properly omit primary key values during insert.
SQLITE_CORRUPT_VTAB: Content in virtual table is corrupt
Content in virtual table is corrupt
SQLITE_IOERR_WRITE: Disk I/O error during write
Disk I/O error during write operation
SQLITE_READONLY: Attempt to write a readonly database
How to fix "SQLITE_READONLY: Attempt to write a readonly database" in SQLite
SQLITE_READONLY_DBMOVED: Database file has been moved since opened
How to fix 'SQLITE_READONLY_DBMOVED: Database file has been moved since opened'
SQLITE_IOERR_SHORT_READ: Read returned less data than requested
How to fix 'SQLITE_IOERR_SHORT_READ: Read returned less data than requested' in SQLite