A multi-row import can fail after an earlier row has already been inserted. Seeing an exception from executemany does not, by itself, answer whether the database still contains that earlier row. Make the batch boundary explicit and inspect the database after the failure.
This Python 3.12 exercise uses a fresh in-memory SQLite database. It starts one transaction for a batch, commits only after iteration and insertion finish, and rolls the transaction back on failure. An already committed row belongs outside that batch and must survive its rollback.
Own the transaction boundary
The example pins autocommit to LEGACY_TRANSACTION_CONTROL and isolation_level to None , then uses explicit SQL BEGIN , COMMIT , and ROLLBACK . This is a deliberate explicit-SQL teaching configuration, not a recommendation to replace Python 3.12's recommended autocommit=False policy everywhere.…