Necesito realizar UPSERT / INSERT O UPDATE contra una base de datos SQLite.
Existe el comando INSERT OR REPLACE que en muchos casos puede ser útil. Pero si desea mantener sus ID con autoincrement en su lugar debido a claves externas, no funciona ya que elimina la fila, crea una nueva y, en consecuencia, esta nueva fila tiene una nueva ID.
Esta sería la mesa:
jugadores - (clave principal en la identificación, nombre de usuario único)
| id | user_name | age |
------------------------------
| 1982 | johnny | 23 |
| 1983 | steven | 29 |
| 1984 | pepee | 40 |
db.execSQL("insert into bla(id,name) values (?,?) on conflict(id) do update set name=?")
. Me da un error de sintaxis en la palabra "on"Estilo de preguntas y respuestas
Bueno, después de investigar y luchar con el problema durante horas, descubrí que hay dos formas de lograr esto, dependiendo de la estructura de su tabla y si tiene restricciones de claves externas activadas para mantener la integridad. Me gustaría compartir esto en un formato limpio para ahorrar algo de tiempo a las personas que puedan estar en mi situación.
Opción 1: puede permitirse eliminar la fila
En otras palabras, no tiene una clave externa, o si las tiene, su motor SQLite está configurado para que no haya excepciones de integridad. El camino a seguir es INSERTAR O REEMPLAZAR . Si está intentando insertar / actualizar un reproductor cuya ID ya existe, el motor SQLite eliminará esa fila e insertará los datos que está proporcionando. Ahora surge la pregunta: ¿qué hacer para mantener asociada la antigua ID?
Digamos que queremos UPSERT con los datos user_name = 'steven' y age = 32.
Mira este código:
El truco está en la fusión. Devuelve la identificación del usuario 'steven' si la hay, y de lo contrario, devuelve una nueva identificación nueva.
Opción 2: no puede permitirse eliminar la fila
Después de jugar con la solución anterior, me di cuenta de que en mi caso eso podría terminar destruyendo datos, ya que este ID funciona como una clave externa para otra tabla. Además, creé la tabla con la cláusula ON DELETE CASCADE , lo que significaría que eliminaría los datos en silencio. Peligroso.
Entonces, primero pensé en una cláusula IF, pero SQLite solo tiene CASE . Y este CASE no se puede usar (o al menos no lo administré) para realizar una consulta de ACTUALIZACIÓN si EXISTE (seleccione la identificación de los jugadores donde user_name = 'steven'), e INSERT si no lo hizo. No vayas.
Y luego, finalmente usé la fuerza bruta, con éxito. La lógica es que, para cada UPSERT que desee realizar, primero ejecute INSERT OR IGNORE para asegurarse de que haya una fila con nuestro usuario, y luego ejecute una consulta UPDATE con exactamente los mismos datos que intentó insertar.
Los mismos datos que antes: user_name = 'steven' y age = 32.
¡Y eso es todo!
EDITAR
Como ha comentado Andy, intentar insertar primero y luego actualizar puede provocar que se activen los activadores con más frecuencia de lo esperado. En mi opinión, esto no es un problema de seguridad de los datos, pero es cierto que disparar eventos innecesarios tiene poco sentido. Por tanto, una solución mejorada sería:
fuente
Aquí hay un enfoque que no requiere el 'ignorar' de fuerza bruta que solo funcionaría si hubiera una violación clave. De esta forma funciona según las condiciones que especifique en la actualización.
Prueba esto...
Cómo funciona
La 'salsa mágica' aquí se usa
Changes()
en laWhere
cláusula.Changes()
representa el número de filas afectadas por la última operación, que en este caso es la actualización.En el ejemplo anterior, si no hay cambios desde la actualización (es decir, el registro no existe), entonces
Changes()
= 0 para que laWhere
cláusula en laInsert
declaración se evalúe como verdadera y se inserte una nueva fila con los datos especificados.Si la
Update
hicieron actualización de una fila existente, entoncesChanges()
= 1 (o más exactamente, no cero si se actualiza más de una fila), por lo que el 'Dónde' cláusula en elInsert
ahora se evalúa como falsa y por lo tanto ninguna inserción se llevará a cabo.La belleza de esto es que no se necesita fuerza bruta, ni eliminar innecesariamente y luego volver a insertar datos, lo que puede resultar en alterar las claves descendentes en las relaciones de clave externa.
Además, dado que es solo una
Where
cláusula estándar , puede basarse en cualquier cosa que defina, no solo en violaciones clave. Del mismo modo, puede usarloChanges()
en combinación con cualquier otra cosa que desee / necesite en cualquier lugar donde se permitan expresiones.fuente
Changes() = 0
, devolverá falso y dos filas harán INSERT OR REEMPLAZARUPSERT
en primer lugar? Pero aun así, es bueno que la actualización ocurra, la configuraciónChanges=1
o, de lo contrario, laINSERT
declaración se dispararía incorrectamente, lo que no desea.El problema con todas las respuestas presentadas es la falta total de tener en cuenta los desencadenantes (y probablemente otros efectos secundarios). Solución como
lleva a que se ejecuten ambos disparadores (para insertar y luego para actualizar) cuando la fila no existe.
La solución adecuada es
en ese caso, solo se ejecuta una instrucción (cuando la fila existe o no).
fuente
UPDATE OR IGNORE
es necesario, ya que la actualización no se bloqueará si no se encuentran filas.Para tener un UPSERT puro sin agujeros (para programadores) que no se relacionan con claves únicas y de otro tipo:
SELECT changes () devolverá el número de actualizaciones realizadas en la última consulta. Luego verifique si el valor de retorno de changes () es 0, si es así, ejecute:
fuente
También puede simplemente agregar una cláusula ON CONFLICT REPLACE a su restricción única de nombre de usuario y luego simplemente INSERTAR, dejando que SQLite averigüe qué hacer en caso de un conflicto. Ver: https://sqlite.org/lang_conflict.html .
También tenga en cuenta la oración sobre los activadores de eliminación: cuando la estrategia de resolución de conflictos REPLACE elimina filas para satisfacer una restricción, los activadores de eliminación se activan si y solo si los activadores recursivos están habilitados.
fuente
Opción 1: Insertar -> Actualizar
Si desea evitar ambos
changes()=0
eINSERT OR IGNORE
incluso si no puede permitirse eliminar la fila, puede utilizar esta lógica;Primero, inserte (si no existe) y luego actualice filtrando con la clave única.
Ejemplo
Respecto a los disparadores
Aviso: no lo he probado para ver qué desencadenadores se están llamando, pero supongo lo siguiente:
si la fila no existe
si la fila existe
Opción 2: Insertar o reemplazar - conserve su propia identificación
de esta manera puede tener un solo comando SQL
Editar: opción agregada 2.
fuente