http://en.wikipedia.org/wiki/Upsert
Insertar proceso almacenado de actualización en SQL Server
¿Hay alguna forma inteligente de hacer esto en SQLite que no haya pensado?
Básicamente quiero actualizar tres de cuatro columnas si el registro existe, si no existe quiero INSERTAR el registro con el valor predeterminado (NUL) para la cuarta columna.
La ID es una clave principal, por lo que solo habrá un registro para UPSERT.
(Estoy tratando de evitar la sobrecarga de SELECT para determinar si necesito ACTUALIZAR o INSERTAR obviamente)
Sugerencias?
No puedo confirmar esa sintaxis en el sitio SQLite para TABLE CREATE. No he creado una demostración para probarlo, pero no parece ser compatible.
Si lo fuera, tengo tres columnas, por lo que en realidad se vería así:
CREATE TABLE table1(
id INTEGER PRIMARY KEY ON CONFLICT REPLACE,
Blob1 BLOB ON CONFLICT REPLACE,
Blob2 BLOB ON CONFLICT REPLACE,
Blob3 BLOB
);
pero los dos primeros blobs no causarán un conflicto, solo la ID lo haría, así que supongo que Blob1 y Blob2 no serían reemplazados (como se desea)
ACTUALIZACIONES en SQLite cuando los datos vinculantes son una transacción completa, lo que significa que cada fila enviada a actualizar requiere: Preparar / Vincular / Pasar / Finalizar declaraciones a diferencia de INSERT que permite el uso de la función de reinicio
La vida de un objeto de declaración es algo así:
- Cree el objeto usando sqlite3_prepare_v2 ()
- Vincula los valores a los parámetros del host usando las interfaces sqlite3_bind_.
- Ejecute el SQL llamando a sqlite3_step ()
- Restablezca la instrucción usando sqlite3_reset (), luego regrese al paso 2 y repita.
- Destruya el objeto de declaración usando sqlite3_finalize ().
ACTUALIZACIÓN Supongo que es lento en comparación con INSERTAR, pero ¿cómo se compara con SELECCIONAR usando la tecla Primaria?
¿Quizás debería usar select para leer la cuarta columna (Blob3) y luego usar REPLACE para escribir un nuevo registro que combine la cuarta columna original con los nuevos datos de las primeras 3 columnas?
Respuestas:
Asumiendo tres columnas en la tabla: ID, NOMBRE, PAPEL
MALO: Esto insertará o reemplazará todas las columnas con nuevos valores para ID = 1:
MALO: Esto insertará o reemplazará 2 de las columnas ... la columna NOMBRE se establecerá en NULL o el valor predeterminado:
BIEN : ¡Utilice SQLite en caso de conflicto, soporte UPSERT en SQLite! ¡La sintaxis UPSERT se agregó a SQLite con la versión 3.24.0!
UPSERT es una adición de sintaxis especial a INSERT que hace que INSERT se comporte como una ACTUALIZACIÓN o un no operativo si INSERT viola una restricción de unicidad. UPSERT no es SQL estándar. UPSERT en SQLite sigue la sintaxis establecida por PostgreSQL.
BUENO pero TENDIDO: Esto actualizará 2 de las columnas. Cuando ID = 1 existe, el NOMBRE no se verá afectado. Cuando ID = 1 no existe, el nombre será el predeterminado (NULL).
Esto actualizará 2 de las columnas. Cuando existe ID = 1, el PAPEL no se verá afectado. Cuando ID = 1 no existe, el rol se establecerá en 'Benchwarmer' en lugar del valor predeterminado.
fuente
INSERT OR REPLACE
tiempo que especifica valores para todas las columnas.INSERTAR O REEMPLAZAR NO ES ES equivalente a "UPSERT".
Digamos que tengo la tabla Empleado con los campos id, nombre y rol:
Boom, has perdido el nombre del número de empleado 1. SQLite lo ha reemplazado con un valor predeterminado.
El resultado esperado de un UPSERT sería cambiar el rol y mantener el nombre.
fuente
coalesce((select ..), 'new value')
cláusula incrustada . La respuesta de Eric necesita más votos aquí, creo.La respuesta de Eric B está bien si desea preservar solo una o quizás dos columnas de la fila existente. Si desea conservar muchas columnas, se vuelve demasiado engorroso rápidamente.
Aquí hay un enfoque que escalará bien a cualquier cantidad de columnas a cada lado. Para ilustrarlo, asumiré el siguiente esquema:
Tenga en cuenta en particular que esa
name
es la clave natural de la fila:id
se usa solo para claves externas, por lo que el punto es que SQLite elija el valor de ID al insertar una nueva fila. Pero al actualizar una fila existente en función de suname
, quiero que continúe teniendo el antiguo valor de ID (¡obviamente!)Logro un verdadero
UPSERT
con la siguiente construcción:La forma exacta de esta consulta puede variar un poco. La clave es el uso de
INSERT SELECT
una combinación externa izquierda, para unir una fila existente a los nuevos valores.Aquí, si una fila no existía anteriormente,
old.id
seráNULL
y SQLite asignará una ID automáticamente, pero si ya existía dicha fila,old.id
tendrá un valor real y se reutilizará. Que es exactamente lo que quería.De hecho, esto es muy flexible. Observe cómo la
ts
columna falta por completo en todos los lados: como tiene unDEFAULT
valor, SQLite simplemente hará lo correcto en cualquier caso, por lo que no tengo que ocuparme de mí mismo.También puede incluir una columna en ambos
new
yold
los lados y luego usar por ejemploCOALESCE(new.content, old.content)
en el exteriorSELECT
que decir “Insertar el nuevo contenido si había alguna, de lo contrario mantener el contenido antiguo” - por ejemplo, si está utilizando una consulta fija y son vinculantes para el nuevo valores con marcadores de posición.fuente
WHERE name = "about"
restricciónSELECT ... AS old
para acelerar las cosas. Si tiene 1m + filas, esto es muy lento.WHERE
cláusula de este tipo requiere solo el tipo de redundancia en la consulta que estaba tratando de obviar en primer lugar cuando se me ocurrió este enfoque. Como siempre: cuando necesite rendimiento, desnormalice: la estructura de la consulta, en este caso.INSERT OR REPLACE INTO page (id, name, title, content, author) SELECT id, 'about', 'About this site', content, 42 FROM ( SELECT NULL ) LEFT JOIN ( SELECT * FROM page WHERE name = 'about' )
ON DELETE
cuando realiza un reemplazo (es decir, una actualización)?ON DELETE
desencadenantes. No sé sobre innecesariamente. Para la mayoría de los usuarios, probablemente sería innecesario, incluso no deseado, pero tal vez no para todos los usuarios. Del mismo modo, por el hecho de que también eliminará en cascada cualquier fila con claves foráneas en la fila en cuestión, probablemente un problema para muchos usuarios. SQLite no tiene nada más cercano a un verdadero UPSERT, desafortunadamente. (Salvo por fingirlo con unINSTEAD OF UPDATE
gatillo, supongo).Si generalmente está haciendo actualizaciones, lo haría ...
Si generalmente estás haciendo inserciones, lo haría
De esta manera, evita la selección y es transaccionalmente sólido en Sqlite.
fuente
Esta respuesta se ha actualizado y, por lo tanto, los comentarios a continuación ya no se aplican.
2018-05-18 PRENSA DE PARADA.
Soporte UPSERT en SQLite!¡La sintaxis UPSERT se agregó a SQLite con la versión 3.24.0 (pendiente)!
UPSERT es una adición de sintaxis especial a INSERT que hace que INSERT se comporte como una ACTUALIZACIÓN o un no operativo si INSERT viola una restricción de unicidad. UPSERT no es SQL estándar. UPSERT en SQLite sigue la sintaxis establecida por PostgreSQL.
alternativamente:
Otra forma completamente diferente de hacerlo es: en mi aplicación configuré mi ID de fila en memoria para que sea largo. Valor máximo cuando creo la fila en memoria. (MaxValue nunca se usará como ID, no vivirá lo suficiente ... Entonces, si rowID no es ese valor, entonces ya debe estar en la base de datos, por lo que necesita una ACTUALIZACIÓN si es MaxValue, entonces necesita una inserción. Esto solo es útil si puede rastrear los ID de fila en su aplicación.
fuente
WHERE
No se puedeINSERT
Me doy cuenta de que este es un hilo antiguo, pero últimamente he estado trabajando en sqlite3 y se me ocurrió este método que mejor se adaptaba a mis necesidades de generar dinámicamente consultas parametrizadas:
Todavía son 2 consultas con una cláusula where en la actualización, pero parece hacer el truco. También tengo esta visión en mi cabeza de que sqlite puede optimizar la declaración de actualización por completo si la llamada a los cambios () es mayor que cero. Si lo hace o no, eso está más allá de mi conocimiento, pero un hombre puede soñar, ¿no? ;)
Para los puntos de bonificación, puede agregar esta línea que le devuelve el ID de la fila, ya sea una fila recién insertada o una fila existente.
fuente
Update <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
Aquí hay una solución que realmente es un UPSERT (ACTUALIZAR o INSERTAR) en lugar de un INSERTAR O REEMPLAZAR (que funciona de manera diferente en muchas situaciones).
Funciona así:
1. Intente actualizar si existe un registro con el mismo Id.
2. Si la actualización no cambió ninguna fila (
NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)
), inserte el registro.Por lo tanto, se actualizó un registro existente o se realizará una inserción.
El detalle importante es usar la función de cambios () SQL para verificar si la declaración de actualización tocó algún registro existente y solo realizar la declaración de inserción si no tocó ningún registro.
Una cosa para mencionar es que la función de cambios () no devuelve los cambios realizados por los desencadenantes de nivel inferior (consulte http://sqlite.org/lang_corefunc.html#changes ), así que asegúrese de tener eso en cuenta.
Aquí está el SQL ...
Actualización de prueba:
Inserto de prueba:
fuente
INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;
también debería funcionar.A partir de la versión 3.24.0 UPSERT es compatible con SQLite.
De la documentación :
Fuente de la imagen: https://www.sqlite.org/images/syntax/upsert-clause.gif
fuente
De hecho, puede hacer una actualización en SQLite, solo se ve un poco diferente de lo que está acostumbrado. Se vería algo así como:
fuente
Ampliando la respuesta de Aristóteles, puede SELECCIONAR desde una tabla ficticia 'singleton' (una tabla de su propia creación con una sola fila). Esto evita algunas duplicaciones.
También mantuve el ejemplo portátil en MySQL y SQLite y usé una columna 'date_added' como ejemplo de cómo podría establecer una columna solo la primera vez.
fuente
El mejor enfoque que conozco es hacer una actualización, seguida de una inserción. La "sobrecarga de una selección" es necesaria, pero no es una carga terrible ya que está buscando la clave principal, que es rápida.
Debería poder modificar las siguientes declaraciones con los nombres de su tabla y campo para hacer lo que desee.
fuente
Si alguien quiere leer mi solución para SQLite en Cordova, obtuve este método genérico js gracias a la respuesta @david anterior.
Entonces, primero tome los nombres de las columnas con esta función:
Luego construya las transacciones mediante programación.
"Valores" es una matriz que debe crear antes y representa las filas que desea insertar o actualizar en la tabla.
"remoteid" es la identificación que utilicé como referencia, ya que estoy sincronizando con mi servidor remoto.
Para el uso del complemento SQLite Cordova, consulte el enlace oficial
fuente
Este método mezcla algunos de los otros métodos de la respuesta para esta pregunta e incorpora el uso de CTE (expresiones de tabla comunes). Introduciré la consulta y luego explicaré por qué hice lo que hice.
Me gustaría cambiar el apellido del empleado 300 a DAVIS si hay un empleado 300. De lo contrario, agregaré un nuevo empleado.
Nombre de la tabla: empleados Columnas: id, nombre, apellido
La consulta es:
Básicamente, utilicé el CTE para reducir la cantidad de veces que la instrucción select debe usarse para determinar los valores predeterminados. Como se trata de un CTE, solo seleccionamos las columnas que queremos de la tabla y la instrucción INSERT usa esto.
Ahora puede decidir qué valores predeterminados desea usar reemplazando los valores nulos, en la función COALESCE, con lo que deberían ser los valores.
fuente
Siguiendo a Aristóteles Pagaltzis y la idea de la respuesta
COALESCE
de Eric B , aquí hay una opción de actualización para actualizar solo unas pocas columnas o insertar una fila completa si no existe.En este caso, imagine que el título y el contenido deben actualizarse, manteniendo los otros valores antiguos cuando existan e insertando los valores suministrados cuando no se encuentra el nombre:
NOTA
id
se obliga a ser NULL cuandoINSERT
se supone que es autoincrement. Si es solo una clave primaria generada, entoncesCOALESCE
también se puede usar (ver el comentario de Aristóteles Pagaltzis ).Entonces, la regla general sería, si desea mantener los valores antiguos, use
COALESCE
, cuando desee actualizar los valores, usenew.fieldname
fuente
COALESCE(old.id, new.id)
definitivamente está mal con una clave de autoincremento. Y aunque "mantener la mayor parte de la fila sin cambios, excepto cuando faltan valores" suena como un caso de uso que alguien podría tener, no creo que eso sea lo que la gente está buscando cuando está buscando cómo hacer un UPSERT.old
tabla donde se asignaronNULL
, no a los valores suministradosnew
. Esta es la razón para usarCOALESCE
. No soy un experto en sqlite, he estado probando esta consulta y parece funcionar para el caso, te agradecería mucho si me pudieras señalar la solución con autoincrementosNULL
como clave, porque eso le dice a SQLite que en su lugar inserte el siguiente valor disponible.Creo que esto puede ser lo que estás buscando: cláusula ON CONFLICT .
Si define su tabla de esta manera:
Ahora, si hace un INSERT con una identificación que ya existe, SQLite automáticamente ACTUALIZA en lugar de INSERTAR.
Hth ...
fuente
REPLACE
declaración.Si no te importa hacer esto en dos operaciones.
Pasos:
1) Agregue nuevos elementos con "INSERTAR O IGNORAR"
2) Actualice los elementos existentes con "ACTUALIZAR"
La entrada a ambos pasos es la misma colección de elementos nuevos o actualizables. Funciona bien con elementos existentes que no necesitan cambios. Se actualizarán, pero con los mismos datos y, por lo tanto, el resultado neto no es ningún cambio.
Claro, más lento, etc. Ineficiente. Sí.
¿Fácil de escribir el sql y mantenerlo y comprenderlo? Seguro.
Es una compensación a considerar. Funciona muy bien para advenedizos pequeños. Funciona muy bien para aquellos que no les importa sacrificar la eficiencia para mantener el código.
fuente
Después de leer este hilo y decepcionarme de que no fuera fácil simplemente escuchar esta "UPSERT", investigué más a fondo ...
En realidad, puede hacer esto directamente y fácilmente en SQLITE.
En lugar de usar:
INSERT INTO
Utilizar:
INSERT OR REPLACE INTO
¡Esto hace exactamente lo que quieres que haga!
fuente
INSERT OR REPLACE
no es unUPSERT
. Vea la "respuesta" de gregschlom por la razón. La solución de Eric B realmente funciona y necesita algunos votos positivos.Si
COUNT(*) = 0
si no si
COUNT(*) > 0
fuente