sql >> Base de Datos >  >> RDS >> SQLite

Lidiar con conflictos de clave principal al insertar datos en SQLite

SQLite tiene una cláusula de extensión de SQL no estándar llamada ON CONFLICT que nos permite especificar cómo tratar los conflictos de restricciones.

En particular, la cláusula se aplica a UNIQUE , NOT NULL , CHECK y PRIMARY KEY restricciones.

Este artículo proporciona ejemplos de cómo se puede usar esta cláusula para determinar cómo manejar los conflictos de restricción de clave principal.

Por "conflictos de restricción de clave principal", me refiero a cuando intenta insertar un valor duplicado en una columna de clave principal. De manera predeterminada, cuando intente hacer esto, la operación se cancelará y SQLite devolverá un error.

Pero puedes usar el ON CONFLICT cláusula para cambiar la forma en que SQLite trata estas situaciones.

Una opción es usar esta cláusula en el CREATE TABLE declaración al crear la tabla. Hacer eso determinará cómo todos INSERT se tratan las operaciones.

Otra opción es usar la cláusula en INSERT cada vez que intenta insertar datos en la tabla. Esto le permite aprovechar la cláusula incluso cuando la tabla no se creó con ella. Cuando usa esta opción, la sintaxis es diferente; usas OR en lugar de ON CONFLICT .

Los ejemplos en esta página usan la segunda opción:creo la tabla sin el ON CONFLICT cláusula, y en su lugar especifico OR en el INSERT declaración.

Tabla de muestra

Creemos una tabla simple y agreguemos una fila.

CREATE TABLE Products( 
    ProductId INTEGER PRIMARY KEY, 
    ProductName, 
    Price
);

INSERT INTO Products VALUES (1, 'Hammer', 8.00);

SELECT * FROM Products;

Resultado:

ProductId   ProductName  Price     
----------  -----------  ----------
1           Hammer       8.0       

Actualmente tenemos una fila, con un ProductId de 1 .

Ahora podemos ejecutar los diversos escenarios de inserción de datos en esa tabla que viola la restricción de clave principal.

Ejemplo 1:cancelar (comportamiento predeterminado)

Como se mencionó, el comportamiento predeterminado para SQLite es abortar INSERT operación y devolver un error.

INSERT INTO Products VALUES (1, 'Wrench', 12.50);

Resultado:

Error: UNIQUE constraint failed: Products.ProductId

Se devolvió un error y no se insertó nada.

Este es el equivalente de usar OR ABORT opción.

INSERT OR ABORT INTO Products VALUES (1, 'Wrench', 12.50);

Resultado:

Error: UNIQUE constraint failed: Products.ProductId

Podemos verificar que no se insertó nada ejecutando un SELECT declaración contra la mesa.

SELECT * FROM Products;

Resultado:

ProductId   ProductName  Price     
----------  -----------  ----------
1           Hammer       8.0       

Podemos ver que la tabla solo contiene la fila original.

Ejemplo 2:ignorar

Una alternativa es hacer que SQLite ignore la fila infractora. En otras palabras, saltará la fila y continuará procesando las filas subsiguientes.

Para hacer esto dentro de su INSERT declaración, use OR IGNORE .

El efecto de esto es que INSERT la operación tiene éxito, pero sin ninguna fila que viole la restricción de clave principal.

INSERT OR IGNORE INTO Products VALUES 
  (1, 'Hammer', 12.00),
  (2, 'Nails', 2.50),
  (3, 'Saw', 10.50),
  (1, 'Wrench', 22.50),
  (5, 'Chisel', 23.00),
  (6, 'Bandage', 120.00);

SELECT * FROM Products;

Resultado:

ProductId   ProductName  Price     
----------  -----------  ----------
1           Hammer       8.0       
2           Nails        2.5       
3           Saw          10.5      
5           Chisel       23.0      
6           Bandage      120.0     

En este caso, traté de insertar dos filas nuevas con una identificación que ya existía en la tabla, por lo que se omitieron ambas filas.

Ejemplo 3:reemplazar

Otra opción que tiene es reemplazar la fila original con la nueva fila.

En otras palabras, sobrescribirá los datos existentes con sus nuevos datos.

Para hacer esto, use OR REPLACE .

INSERT OR REPLACE INTO Products VALUES 
  (1, 'Hammer', 12.00),
  (2, 'Nails', 2.50),
  (3, 'Saw', 10.50),
  (1, 'Wrench', 22.50),
  (5, 'Chisel', 23.00),
  (6, 'Bandage', 120.00);

SELECT * FROM Products;

Resultado:

ProductId   ProductName  Price     
----------  -----------  ----------
1           Wrench       22.5      
2           Nails        2.5       
3           Saw          10.5      
5           Chisel       23.0      
6           Bandage      120.0     

En este caso, la mayoría de las filas eran iguales, por lo que contienen los mismos datos después de INSERT operación. Sin embargo, podemos ver que la primera fila se actualizó para usar los valores en mi INSERT declaración.

También podemos ver que usó el segundo conjunto de valores (ya que dos compartían el mismo ProductId ).

Así que el efecto es algo así como una UPDATE instrucción y INSERT declaración combinada.

Ejemplo 4:reversión

Otra opción es usar el ROLLBACK opción.

Esto aborta la instrucción SQL actual con un error SQLITE_CONSTRAINT y revierte la transacción actual. Si no hay ninguna transacción activa (aparte de la transacción implícita que se crea en cada comando), entonces funciona igual que ABORT algoritmo.

Vale la pena tener en cuenta cómo funciona esta opción. Aquí hay un ejemplo que usa múltiples INSERT OR ROLLBACK declaraciones dentro de una transacción.

DELETE FROM Products;

BEGIN TRANSACTION;
INSERT OR ROLLBACK INTO Products VALUES (1, 'Hammer', 8.00);
INSERT OR ROLLBACK INTO Products VALUES (2, 'Nails', 2.50);
INSERT OR ROLLBACK INTO Products VALUES (3, 'Saw', 10.50);
INSERT OR ROLLBACK INTO Products VALUES (1, 'Wrench', 22.50);
INSERT OR ROLLBACK INTO Products VALUES (5, 'Chisel', 23.00);
INSERT OR ROLLBACK INTO Products VALUES (6, 'Bandage', 120.00);
COMMIT;
  
SELECT * FROM Products;

Aquí está el resultado completo de mi terminal cuando ejecuto esto:

sqlite> BEGIN TRANSACTION;
sqlite> INSERT OR ROLLBACK INTO Products VALUES (1, 'Hammer', 8.00);
sqlite> INSERT OR ROLLBACK INTO Products VALUES (2, 'Nails', 2.50);
sqlite> INSERT OR ROLLBACK INTO Products VALUES (3, 'Saw', 10.50);
sqlite> INSERT OR ROLLBACK INTO Products VALUES (1, 'Wrench', 22.50);
Error: UNIQUE constraint failed: Products.ProductId
sqlite> INSERT OR ROLLBACK INTO Products VALUES (5, 'Chisel', 23.00);
sqlite> INSERT OR ROLLBACK INTO Products VALUES (6, 'Bandage', 120.00);
sqlite> COMMIT;
Error: cannot commit - no transaction is active
sqlite>   
sqlite> SELECT * FROM Products;
ProductId   ProductName  Price     
----------  -----------  ----------
5           Chisel       23.0      
6           Bandage      120.0     
sqlite> 

Básicamente, lo que sucedió aquí es que llegó hasta la violación de la restricción y luego revirtió la transacción. Luego se procesaron las siguientes dos líneas y luego el COMMIT se encontró la palabra clave. Para entonces, la transacción ya se había revertido, por lo que recibimos otro error que nos decía que no había ninguna transacción activa.

Esto es lo que sucede si lo elimino de la transacción.

DELETE FROM Products;

INSERT OR ROLLBACK INTO Products VALUES (1, 'Hammer', 8.00);
INSERT OR ROLLBACK INTO Products VALUES (2, 'Nails', 2.50);
INSERT OR ROLLBACK INTO Products VALUES (3, 'Saw', 10.50);
INSERT OR ROLLBACK INTO Products VALUES (1, 'Wrench', 22.50);
INSERT OR ROLLBACK INTO Products VALUES (5, 'Chisel', 23.00);
INSERT OR ROLLBACK INTO Products VALUES (6, 'Bandage', 120.00);
  
SELECT * FROM Products;

Aquí está el resultado completo de mi terminal cuando ejecuto esto:

sqlite> DELETE FROM Products;
sqlite> 
sqlite> INSERT OR ROLLBACK INTO Products VALUES (1, 'Hammer', 8.00);
sqlite> INSERT OR ROLLBACK INTO Products VALUES (2, 'Nails', 2.50);
sqlite> INSERT OR ROLLBACK INTO Products VALUES (3, 'Saw', 10.50);
sqlite> INSERT OR ROLLBACK INTO Products VALUES (1, 'Wrench', 22.50);
Error: UNIQUE constraint failed: Products.ProductId
sqlite> INSERT OR ROLLBACK INTO Products VALUES (5, 'Chisel', 23.00);
sqlite> INSERT OR ROLLBACK INTO Products VALUES (6, 'Bandage', 120.00);
sqlite>   
sqlite> SELECT * FROM Products;
ProductId   ProductName  Price     
----------  -----------  ----------
1           Hammer       8.0       
2           Nails        2.5       
3           Saw          10.5      
5           Chisel       23.0      
6           Bandage      120.0     
sqlite>

En este caso, funcionó como ABORT .

Para demostrar esto, aquí está la misma declaración usando ABORT en lugar de ROLLBACK .

DELETE FROM Products;

INSERT OR ABORT INTO Products VALUES (1, 'Hammer', 8.00);
INSERT OR ABORT INTO Products VALUES (2, 'Nails', 2.50);
INSERT OR ABORT INTO Products VALUES (3, 'Saw', 10.50);
INSERT OR ABORT INTO Products VALUES (1, 'Wrench', 22.50);
INSERT OR ABORT INTO Products VALUES (5, 'Chisel', 23.00);
INSERT OR ABORT INTO Products VALUES (6, 'Bandage', 120.00);
  
SELECT * FROM Products;

Aquí está el resultado completo de mi terminal cuando ejecuto esto:

sqlite> DELETE FROM Products;
sqlite> 
sqlite> INSERT OR ABORT INTO Products VALUES (1, 'Hammer', 8.00);
sqlite> INSERT OR ABORT INTO Products VALUES (2, 'Nails', 2.50);
sqlite> INSERT OR ABORT INTO Products VALUES (3, 'Saw', 10.50);
sqlite> INSERT OR ABORT INTO Products VALUES (1, 'Wrench', 22.50);
Error: UNIQUE constraint failed: Products.ProductId
sqlite> INSERT OR ABORT INTO Products VALUES (5, 'Chisel', 23.00);
sqlite> INSERT OR ABORT INTO Products VALUES (6, 'Bandage', 120.00);
sqlite>   
sqlite> SELECT * FROM Products;
ProductId   ProductName  Price     
----------  -----------  ----------
1           Hammer       8.0       
2           Nails        2.5       
3           Saw          10.5      
5           Chisel       23.0      
6           Bandage      120.0     
sqlite> 

La opción del fracaso

El FAIL La opción aborta la instrucción SQL actual con un error SQLITE_CONSTRAINT. Pero esta opción no revierte los cambios anteriores de la instrucción SQL que falló ni finaliza la transacción.

DELETE FROM Products;

INSERT OR FAIL INTO Products VALUES 
  (1, 'Hammer', 8.00),
  (2, 'Nails', 2.50),
  (3, 'Saw', 10.50),
  (1, 'Wrench', 22.50),
  (5, 'Chisel', 23.00),
  (6, 'Bandage', 120.00);

SELECT * FROM Products;

Resultado:

ProductId   ProductName  Price     
----------  -----------  ----------
1           Hammer       8.0       
2           Nails        2.5       
3           Saw          10.5