INSERTAR SI NO EXISTE OTRA ACTUALIZACIÓN?

275

He encontrado algunas soluciones "serían" para el clásico "¿Cómo inserto un nuevo registro o actualizo uno si ya existe?", Pero no puedo hacer que ninguno de ellos funcione en SQLite.

Tengo una tabla definida de la siguiente manera:

CREATE TABLE Book 
ID     INTEGER PRIMARY KEY AUTOINCREMENT,
Name   VARCHAR(60) UNIQUE,
TypeID INTEGER,
Level  INTEGER,
Seen   INTEGER

Lo que quiero hacer es agregar un registro con un Nombre único. Si el nombre ya existe, quiero modificar los campos.

¿Alguien puede decirme cómo hacer esto, por favor?

SparkyNZ
fuente
3
"insertar o reemplazar" es completamente diferente de "insertar o actualizar"
Fattie

Respuestas:

320

Echa un vistazo a http://sqlite.org/lang_conflict.html .

Quieres algo como:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values
((select ID from Book where Name = "SearchName"), "SearchName", ...);

Tenga en cuenta que cualquier campo que no esté en la lista de inserción se establecerá en NULL si la fila ya existe en la tabla. Esta es la razón por la cual hay una subselección para la IDcolumna: en el caso de reemplazo, la instrucción lo establecería en NULL y luego se asignaría una nueva ID.

Este enfoque también se puede usar si desea dejar solo valores de campo particulares si la fila en el caso de reemplazo pero establece el campo en NULL en el caso de inserción.

Por ejemplo, suponiendo que quiere dejarlo Seensolo:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values (
   (select ID from Book where Name = "SearchName"),
   "SearchName",
    5,
    6,
    (select Seen from Book where Name = "SearchName"));
janm
fuente
109
"Insertar o reemplazar" incorrecto es diferente de "insertar o actualizar". Para obtener una respuesta válida, consulte stackoverflow.com/questions/418898/…
rds el
12
@rds No, no está mal porque esta pregunta dice "modificar los campos" y la clave principal no es parte de la lista de columnas, pero todos los demás campos sí. Si va a tener casos de esquina donde no está reemplazando todos los valores de campo, o si está jugando con la clave primaria, debería estar haciendo algo diferente. Si tiene un conjunto completo de nuevos campos, este enfoque está bien. ¿Tienes un problema específico que no puedo ver?
enero
9
Es válido si conoce todos los nuevos valores para todos los campos. Si el usuario solo actualiza, digamos Level, este enfoque no puede seguirse.
rds
44
Correcta . La otra respuesta es relevante, pero este enfoque es válido para actualizar todos los campos (excluyendo la clave).
Ярослав Рахматуллин
2
Sí, esto es completamente incorrecto. ¿Qué sucederá si solo quiero actualizar el valor de una sola columna en Confliction desde el nuevo? En el caso anterior, todos los demás datos serán reemplazados por uno nuevo que no sea correcto.
Mrug
85

Debe usar el INSERT OR IGNOREcomando seguido de un UPDATEcomando: en el siguiente ejemplo namehay una clave principal:

INSERT OR IGNORE INTO my_table (name, age) VALUES ('Karen', 34)
UPDATE my_table SET age = 34 WHERE name='Karen'

El primer comando insertará el registro. Si el registro existe, ignorará el error causado por el conflicto con una clave primaria existente.

El segundo comando actualizará el registro (que ahora definitivamente existe)

moshik
fuente
66
en qué momento lo ignorará? cuando el nombre y la edad son los mismos?
mou
Esta debería ser la solución ... si está utilizando algún disparador en la inserción, la respuesta aceptada se dispara cada vez. Esto no lo hace y solo realiza una actualización
PodTech.io
1
Se ignora basándose únicamente en el nombre. Recuerde que solo la columna "nombre" es una clave principal.
Gabriel Ferrer
3
Cuando el registro es nuevo, entonces la actualización no es necesaria, pero se ejecutará de todos modos, lo que lleva a un mal rendimiento.
MarcG
¿Cómo ejecutar una declaración preparada para esto?
prashant
65

Debe establecer una restricción en la tabla para desencadenar un " conflicto " que luego resuelve haciendo un reemplazo:

CREATE TABLE data   (id INTEGER PRIMARY KEY, event_id INTEGER, track_id INTEGER, value REAL);
CREATE UNIQUE INDEX data_idx ON data(event_id, track_id);

Entonces puedes emitir:

INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 2, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 5);

El "SELECCIONAR * DE los datos" le dará:

2|2|2|3.0
3|1|2|5.0

Tenga en cuenta que data.id es "3" y no "1" porque REPLACE hace un BORRAR e INSERTAR, no una ACTUALIZACIÓN. Esto también significa que debe asegurarse de definir todas las columnas necesarias o obtendrá valores NULL inesperados.

gaspard
fuente
33

En primer lugar actualizarlo. Si el recuento de filas afectado = 0, insértelo. Es el más fácil y adecuado para todos los RDBMS .

Burçin
fuente
11
Dos operaciones no deberían ser un problema con una transacción en el nivel de aislamiento correcto, independientemente de la base de datos.
enero
55
Insert or ReplaceEs realmente más preferible.
MPelletier
1
Realmente deseo que la documentación de TI contenga más ejemplos. He intentado esto a continuación y no funciona (mi sintaxis es obviamente incorrecta). ¿Alguna idea sobre lo que debería ser? INSERTAR EN EL Libro (Nombre, Id. De Tipo, Nivel, Visto) VALORES ('Superman', '2', '14', '0') EN EL REEMPLAZO DE CONFLICTOS Libro (Nombre, Id. De Tipo, Nivel, Visto) VALORES ('Superman', ' 2 ',' 14 ',' 0 ')
SparkyNZ
66
Además, si los valores de las filas son exactamente iguales, el recuento de filas afectado será cero y se creará una nueva fila duplicada.
Barkside
2
+1, porque INSERTAR O REEMPLAZAR eliminará la fila original en conflicto y si no está configurando todas las columnas, perderá los valores originales
Pavel Machyniak
20

INSERT OR REPLACE reemplazará los otros campos al valor predeterminado.

sqlite> CREATE TABLE Book (
  ID     INTEGER PRIMARY KEY AUTOINCREMENT,
  Name   TEXT,
  TypeID INTEGER,
  Level  INTEGER,
  Seen   INTEGER
);

sqlite> INSERT INTO Book VALUES (1001, 'C++', 10, 10, 0);
sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR REPLACE INTO Book(ID, Name) VALUES(1001, 'SQLite');

sqlite> SELECT * FROM Book;
1001|SQLite|||

Si quieres preservar el otro campo

sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR IGNORE INTO Book(ID) VALUES(1001);
sqlite> UPDATE Book SET Name='SQLite' WHERE ID=1001;

sqlite> SELECT * FROM Book;
1001|SQLite|10|10|0

o usando UPSERT (la sintaxis se agregó a SQLite con la versión 3.24.0 (2018-06-04))

INSERT INTO Book (ID, Name)
  VALUES (1001, 'SQLite')
  ON CONFLICT (ID) DO
  UPDATE SET Name=excluded.Name;

El excluded.prefijo igual al valor en VALUES.

Ala de acero
fuente
16

Upsert es lo que quieres. UPSERTLa sintaxis se agregó a SQLite con la versión 3.24.0 (2018-06-04).

CREATE TABLE phonebook2(
  name TEXT PRIMARY KEY,
  phonenumber TEXT,
  validDate DATE
);

INSERT INTO phonebook2(name,phonenumber,validDate)
  VALUES('Alice','704-555-1212','2018-05-08')
  ON CONFLICT(name) DO UPDATE SET
    phonenumber=excluded.phonenumber,
    validDate=excluded.validDate
  WHERE excluded.validDate>phonebook2.validDate;

Tenga en cuenta que en este punto la palabra real "UPSERT" no es parte de la sintaxis upsert.

La sintaxis correcta es

INSERT INTO ... ON CONFLICT(...) DO UPDATE SET...

y si está haciendo INSERT INTO SELECT ...su selección, necesita al menos WHERE trueresolver la ambigüedad del analizador sobre el token ONcon la sintaxis de unión.

Tenga en cuenta que INSERT OR REPLACE...eliminará el registro antes de insertar uno nuevo si tiene que reemplazarlo, lo que podría ser malo si tiene cascadas de clave externa u otros desencadenantes de eliminación.

Camarada Joecool
fuente
Existe en la documentación y tengo la última versión de sqlite que es 3.25.1 pero no funciona para mí
Bentaiba Miled Basma
¿Probaste esas dos consultas por encima literalmente?
Camarada Joecool
Tenga en cuenta que Ubuntu 18.04 viene con SQLite3 v3.22 , no 3.25, por lo que no admite la UPSERTsintaxis.
starbeamrainbowlabs
¡Gracias por la versión de referencia de la función!
Matteo
6

Si no tiene una clave principal, puede insertarla si no existe y luego realizar una actualización. La tabla debe contener al menos una entrada antes de usar esto.

INSERT INTO Test 
   (id, name)
   SELECT 
      101 as id, 
      'Bob' as name
   FROM Test
       WHERE NOT EXISTS(SELECT * FROM Test WHERE id = 101 and name = 'Bob') LIMIT 1;

Update Test SET id='101' WHERE name='Bob';
mate
fuente
Esta es la única solución que funcionó para mí sin crear entradas duplicadas. Probablemente porque la tabla que estoy usando no tiene claves primarias y tiene 2 columnas sin valores predeterminados. Aunque es una solución un poco larga, hace el trabajo correctamente y funciona como se espera.
Inbar Rose
4

Creo que quieres UPSERT .

"INSERTAR O REEMPLAZAR" sin el truco adicional en esa respuesta restablecerá los campos que no especifique a NULL u otro valor predeterminado. (Este comportamiento de INSERTAR O REEMPLAZAR es diferente a ACTUALIZAR; es exactamente como INSERTAR, porque en realidad es INSERTAR; sin embargo, si lo que desea es ACTUALIZAR si existe, probablemente desee la semántica ACTUALIZAR y se sorprenderá desagradablemente por el resultado real).

El truco de la implementación sugerida de UPSERT es básicamente usar INSERTAR O REEMPLAZAR, pero especificar todos los campos, usando cláusulas SELECT incrustadas para recuperar el valor actual de los campos que no desea cambiar.

metamatt
fuente
1

Creo que vale la pena señalar que puede haber algún comportamiento inesperado aquí si no comprende a fondo cómo interactúan PRIMARY KEY y UNIQUE .

Como ejemplo, si desea insertar un registro solo si el campo NOMBRE no se toma actualmente, y si es así, desea que se active una excepción de restricción, entonces INSERTAR O REEMPLAZAR no arrojará una excepción y en su lugar lo hará. resuelva la restricción ÚNICA reemplazando el registro en conflicto (el registro existente con el mismo NOMBRE ). Gaspard lo demuestra muy bien en su respuesta anterior.

Si desea que se active una excepción de restricción, debe usar una instrucción INSERT y confiar en un comando UPDATE separado para actualizar el registro una vez que sepa que no se toma el nombre.

Matt Matthias
fuente