SQLite - UPSERT * no * INSERT o REPLACE


535

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í:

  1. Cree el objeto usando sqlite3_prepare_v2 ()
  2. Vincula los valores a los parámetros del host usando las interfaces sqlite3_bind_.
  3. Ejecute el SQL llamando a sqlite3_step ()
  4. Restablezca la instrucción usando sqlite3_reset (), luego regrese al paso 2 y repita.
  5. 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?


55
SQLite - UPSERT disponible en pre-lanzamiento referirse: sqlite.1065341.n5.nabble.com/...
Gaurav

3
UPSERT disponible en la versión 3.24.0 de SQLite
pablo_worker

Respuestas:


854

Asumiendo tres columnas en la tabla: ID, NOMBRE, PAPEL


MALO: Esto insertará o reemplazará todas las columnas con nuevos valores para ID = 1:

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (1, 'John Foo', 'CEO');

MALO: Esto insertará o reemplazará 2 de las columnas ... la columna NOMBRE se establecerá en NULL o el valor predeterminado:

INSERT OR REPLACE INTO Employee (id, role) 
  VALUES (1, 'code monkey');

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.

ingrese la descripción de la imagen aquí

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).

INSERT OR REPLACE INTO Employee (id, role, name) 
  VALUES (  1, 
            'code monkey',
            (SELECT name FROM Employee WHERE id = 1)
          );

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.

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (  1, 
            'Susan Bar',
            COALESCE((SELECT role FROM Employee WHERE id = 1), 'Benchwarmer')
          );

33
+1 brillante! La cláusula de selección incrustada le brinda la flexibilidad de anular la funcionalidad predeterminada EN EL REEMPLAZO DE CONFLICTOS si necesita combinar / comparar el valor anterior y el nuevo valor para cualquier campo.
G__

24
Si el empleado es referenciado por otras filas con eliminación en cascada, entonces las otras filas aún se eliminarán por reemplazo.
Don Reba

246
Esto duele. SQlite necesita UPSERT.
andig

21
¿Podría explicar por qué esto insertará o reemplazará todas las columnas con nuevos valores para ID = 1: se considera MALO en su primer ejemplo? El comando que presenta allí está destinado a crear un nuevo registro con ID 1, nombre John Foo y CEO del rol , o sobrescribir el registro cuya ID es 1, si ya está allí, con esos datos (suponiendo que la identificación es la clave principal) . Entonces, ¿por qué es malo si eso sucede exactamente?
O Mapper

10
@ Cornelius: Eso está claro, pero eso no es lo que sucede en el primer ejemplo. El primer ejemplo está destinado a establecer a la fuerza todas las columnas, que es exactamente lo que sucede, sin importar si el registro se inserta o se reemplaza. Entonces, ¿por qué eso se considera malo? La respuesta vinculada también solo señala por qué algo malo puede suceder al especificar un subconjunto de las columnas, como en su segundo ejemplo; no parece dar detalles sobre los efectos negativos de lo que sucede en su primer ejemplo, al INSERT OR REPLACEtiempo que especifica valores para todas las columnas.
O Mapper

134

INSERTAR O REEMPLAZAR NO ES ES equivalente a "UPSERT".

Digamos que tengo la tabla Empleado con los campos id, nombre y rol:

INSERT OR REPLACE INTO Employee ("id", "name", "role") VALUES (1, "John Foo", "CEO")
INSERT OR REPLACE INTO Employee ("id", "role") VALUES (1, "code monkey")

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.


21
-1 de mí, me temo. Sí, la respuesta aceptada es incorrecta, pero aunque su respuesta señala el problema, tampoco es una respuesta. Para una respuesta real, vea la solución inteligente de Eric B usando una coalesce((select ..), 'new value')cláusula incrustada . La respuesta de Eric necesita más votos aquí, creo.
Día

22
En efecto. Definitivamente, Eric es la mejor respuesta y merece más votos. Dicho esto, creo que al señalar el problema, he contribuido un poco a encontrar la buena respuesta (la respuesta de Eric llegó más tarde, y se basa en las tablas sql de muestra en mi respuesta). Así que no sé si merezco un -1, pero no importa :)
gregschlom

66
+1 para compensar el -1 anterior. jajaja Sin embargo, una línea de tiempo interesante en este hilo. Claramente, su respuesta fue más de una semana antes de la de Eric, pero ambos respondieron la pregunta casi dos años después de haberla formulado. +1 para Eric también por elaborar.
Rico


2
@QED no, porque eliminar + insertar (que es un reemplazo) es 2 declaraciones dml, con sus propios disparadores, por ejemplo. No es lo mismo que 1 declaración de actualización solamente.
Sebas

109

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:

 CREATE TABLE page (
     id      INTEGER PRIMARY KEY,
     name    TEXT UNIQUE,
     title   TEXT,
     content TEXT,
     author  INTEGER NOT NULL REFERENCES user (id),
     ts      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 );

Tenga en cuenta en particular que esa namees la clave natural de la fila: idse 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 UPSERTcon la siguiente construcción:

 WITH new (name, title, author) AS ( VALUES('about', 'About this site', 42) )
 INSERT OR REPLACE INTO page (id, name, title, content, author)
 SELECT old.id, new.name, new.title, old.content, new.author
 FROM new LEFT JOIN page AS old ON new.name = old.name;

La forma exacta de esta consulta puede variar un poco. La clave es el uso deINSERT SELECT una combinación externa izquierda, para unir una fila existente a los nuevos valores.

Aquí, si una fila no existía anteriormente, old.idserá NULLy SQLite asignará una ID automáticamente, pero si ya existía dicha fila, old.idtendrá un valor real y se reutilizará. Que es exactamente lo que quería.

De hecho, esto es muy flexible. Observe cómo la tscolumna falta por completo en todos los lados: como tiene un DEFAULTvalor, 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 newy oldlos lados y luego usar por ejemplo COALESCE(new.content, old.content)en el exterior SELECTque 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.


13
+1 funciona muy bien, pero agrega una WHERE name = "about"restricción SELECT ... AS oldpara acelerar las cosas. Si tiene 1m + filas, esto es muy lento.

Buen punto, +1 en tu comentario. Sin embargo, dejaré eso fuera de la respuesta, porque agregar una WHEREclá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.
Aristóteles Pagaltzis

44
Puede simplificar el ejemplo de Aristóteles a esto, si lo desea: 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' )
jcox

44
¿No desencadenaría esto innecesariamente disparadores ON DELETEcuando realiza un reemplazo (es decir, una actualización)?
Karakuri

3
Ciertamente activará ON DELETEdesencadenantes. 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 un INSTEAD OF UPDATEgatillo, supongo).
Aristóteles Pagaltzis

86

Si generalmente está haciendo actualizaciones, lo haría ...

  1. Comience una transacción
  2. Hacer la actualización
  3. Verificar el recuento de filas
  4. Si es 0, inserte
  5. Cometer

Si generalmente estás haciendo inserciones, lo haría

  1. Comience una transacción
  2. Prueba un inserto
  3. Verificar error de violación de clave principal
  4. si tenemos un error, haga la actualización
  5. Cometer

De esta manera, evita la selección y es transaccionalmente sólido en Sqlite.


Gracias, acabo de preguntar esto @ stackoverflow.com/questions/2717590/… . +1 de mi parte! =)
Alix Axel

44
Si va a verificar el recuento de filas usando sqlite3_changes () en el tercer paso, asegúrese de no usar el controlador de base de datos de varios subprocesos para realizar modificaciones.
Linulin

3
¿No sería aún menos extenso lo siguiente con el mismo efecto: 1) seleccione la tabla de forma de id donde id = 'x' 2) if (ResultSet.rows.length == 0) actualice la tabla donde id = 'x';
Florin

20
Realmente deseo que INSERT O ACTUALIZACIÓN sea parte del lenguaje
Florin

Esto terminó funcionando mejor para mí, tratando de hacer REPLACE INTO o siempre e insertar / actualizar estaba matando mi rendimiento cuando principalmente hacía actualizaciones en lugar de inserciones.
PherricOxide

83

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.

ingrese la descripción de la imagen aquí

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.


55
Amén. Simple es mejor que complejo. Esto se siente un poco más simple que la respuesta aceptada.
kkurian

44
Pensé que no podías hacer INSERT INTO IN ... WHERE en sqlite? Este es un error de sintaxis para mí en sqlite3
Charlie Martin

55
@CharlieMartin es correcto. Esta sintaxis no es válida para SQLite, que es lo que solicitó el OP. WHERENo se puede INSERT
agregar

44
Esta respuesta me ha hecho perder mucho tiempo, la pregunta es sobre SQLITE y no sabía que INSERT WHERE no era compatible con sqite, agregue esta nota que no es válido para sqlite en su respuesta
Miquel

3
@colminator y otros: arreglé su declaración SQL usando su sugerencia.
F Lekschas

62

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:

insert or ignore into <table>(<primaryKey>, <column1>, <column2>, ...) values(<primaryKeyValue>, <value1>, <value2>, ...); 
update <table> set <column1>=<value1>, <column2>=<value2>, ... where changes()=0 and <primaryKey>=<primaryKeyValue>; 

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.

select case changes() WHEN 0 THEN last_insert_rowid() else <primaryKeyValue> end;

+ 1. Exactamente lo que estaba tratando de reemplazar haciendo una conversión de algún código TSQL de SQL Server. Gracias. El SQL que estaba tratando de reemplazar fue comoUpdate <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
DarrenMB el

14

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:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 2;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 2, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

Inserto de prueba:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 3;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 3, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

2
Esta parece ser la mejor solución para mí que Eric. Sin embargo INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;también debería funcionar.
bkausbk

Gracias amigo, también funciona en WebSQL (usando el complemento Cordova y SQLite)
Zappescu

11

A partir de la versión 3.24.0 UPSERT es compatible con SQLite.

De la documentación :

UPSERT es una adición de sintaxis especial a INSERT que hace que INSERT se comporte como una ACTUALIZACIÓN o un no-op si el INSERT viola una restricción de unicidad. UPSERT no es SQL estándar. UPSERT en SQLite sigue la sintaxis establecida por PostgreSQL. La sintaxis UPSERT se agregó a SQLite con la versión 3.24.0 (pendiente).

Un UPSERT es una declaración INSERT ordinaria seguida de la cláusula especial ON CONFLICT

ingrese la descripción de la imagen aquí

Fuente de la imagen: https://www.sqlite.org/images/syntax/upsert-clause.gif


8

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:

INSERT INTO table name (column1, column2) 
VALUES ("value12", "value2") WHERE id = 123 
ON CONFLICT DO UPDATE 
SET column1 = "value1", column2 = "value2" WHERE id = 123

5

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.

 REPLACE INTO page (
   id,
   name,
   title,
   content,
   author,
   date_added)
 SELECT
   old.id,
   "about",
   "About this site",
   old.content,
   42,
   IFNULL(old.date_added,"21/05/2013")
 FROM singleton
 LEFT JOIN page AS old ON old.name = "about";

4

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.

--first, update any matches
UPDATE DESTINATION_TABLE DT
SET
  MY_FIELD1 = (
              SELECT MY_FIELD1
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
 ,MY_FIELD2 = (
              SELECT MY_FIELD2
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
WHERE EXISTS(
            SELECT ST2.PRIMARY_KEY
            FROM
              SOURCE_TABLE ST2
             ,DESTINATION_TABLE DT2
            WHERE ST2.PRIMARY_KEY = DT2.PRIMARY_KEY
            );

--second, insert any non-matches
INSERT INTO DESTINATION_TABLE(
  MY_FIELD1
 ,MY_FIELD2
)
SELECT
  ST.MY_FIELD1
 ,NULL AS MY_FIELD2  --insert NULL into this field
FROM
  SOURCE_TABLE ST
WHERE NOT EXISTS(
                SELECT DT2.PRIMARY_KEY
                FROM DESTINATION_TABLE DT2
                WHERE DT2.PRIMARY_KEY = ST.PRIMARY_KEY
                );

77
Camino a complicado.
partir

Creo que no es una buena idea porque debe hacer dos solicitudes al motor de la base de datos.
Ricardo da Rocha Vitor

3

Si alguien quiere leer mi solución para SQLite en Cordova, obtuve este método genérico js gracias a la respuesta @david anterior.

function    addOrUpdateRecords(tableName, values, callback) {
get_columnNames(tableName, function (data) {
    var columnNames = data;
    myDb.transaction(function (transaction) {
        var query_update = "";
        var query_insert = "";
        var update_string = "UPDATE " + tableName + " SET ";
        var insert_string = "INSERT INTO " + tableName + " SELECT ";
        myDb.transaction(function (transaction) {
            // Data from the array [[data1, ... datan],[()],[()]...]:
            $.each(values, function (index1, value1) {
                var sel_str = "";
                var upd_str = "";
                var remoteid = "";
                $.each(value1, function (index2, value2) {
                    if (index2 == 0) remoteid = value2;
                    upd_str = upd_str + columnNames[index2] + "='" + value2 + "', ";
                    sel_str = sel_str + "'" + value2 + "', ";
                });
                sel_str = sel_str.substr(0, sel_str.length - 2);
                sel_str = sel_str + " WHERE NOT EXISTS(SELECT changes() AS change FROM "+tableName+" WHERE change <> 0);";
                upd_str = upd_str.substr(0, upd_str.length - 2);
                upd_str = upd_str + " WHERE remoteid = '" + remoteid + "';";                    
                query_update = update_string + upd_str;
                query_insert = insert_string + sel_str;  
                // Start transaction:
                transaction.executeSql(query_update);
                transaction.executeSql(query_insert);                    
            });
        }, function (error) {
            callback("Error: " + error);
        }, function () {
            callback("Success");
        });
    });
});
}

Entonces, primero tome los nombres de las columnas con esta función:

function get_columnNames(tableName, callback) {
myDb.transaction(function (transaction) {
    var query_exec = "SELECT name, sql FROM sqlite_master WHERE type='table' AND name ='" + tableName + "'";
    transaction.executeSql(query_exec, [], function (tx, results) {
        var columnParts = results.rows.item(0).sql.replace(/^[^\(]+\(([^\)]+)\)/g, '$1').split(','); ///// RegEx
        var columnNames = [];
        for (i in columnParts) {
            if (typeof columnParts[i] === 'string')
                columnNames.push(columnParts[i].split(" ")[0]);
        };
        callback(columnNames);
    });
});
}

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


2

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:

INSERT OR REPLACE INTO employees (employee_id, first_name, last_name)
WITH registered_employees AS ( --CTE for checking if the row exists or not
    SELECT --this is needed to ensure that the null row comes second
        *
    FROM (
        SELECT --an existing row
            *
        FROM
            employees
        WHERE
            employee_id = '300'

        UNION

        SELECT --a dummy row if the original cannot be found
            NULL AS employee_id,
            NULL AS first_name,
            NULL AS last_name
    )
    ORDER BY
        employee_id IS NULL --we want nulls to be last
    LIMIT 1 --we only want one row from this statement
)
SELECT --this is where you provide defaults for what you would like to insert
    registered_employees.employee_id, --if this is null the SQLite default will be used
    COALESCE(registered_employees.first_name, 'SALLY'),
    'DAVIS'
FROM
    registered_employees
;

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.


2

Siguiendo a Aristóteles Pagaltzis y la idea de la respuestaCOALESCE 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 cuando INSERTse supone que es autoincrement. Si es solo una clave primaria generada, entonces COALESCEtambién se puede usar (ver el comentario de Aristóteles Pagaltzis ).

WITH new (id, name, title, content, author)
     AS ( VALUES(100, 'about', 'About this site', 'Whatever new content here', 42) )
INSERT OR REPLACE INTO page (id, name, title, content, author)
SELECT
     old.id, COALESCE(old.name, new.name),
     new.title, new.content,
     COALESCE(old.author, new.author)
FROM new LEFT JOIN page AS old ON new.name = old.name;

Entonces, la regla general sería, si desea mantener los valores antiguos, use COALESCE, cuando desee actualizar los valores, usenew.fieldname


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.
Aristóteles Pagaltzis

@AristotlePagaltzis se disculpa si me equivoco, no estoy usando autoincrements. Lo que estoy buscando con esta consulta es actualizar solo algunas columnas o insertar una fila completa con los valores proporcionados en caso de que no exista . He estado jugando con su consulta y no pude lograrlo al insertar: las columnas seleccionadas de la oldtabla donde se asignaron NULL, no a los valores suministrados new. Esta es la razón para usar COALESCE. 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 autoincrementos
Miquel

2
En el caso de una clave de autoincremento, desea insertarla NULLcomo clave, porque eso le dice a SQLite que en su lugar inserte el siguiente valor disponible.
Aristóteles Pagaltzis

No digo que no debas hacer lo que estás haciendo (si lo necesitas, entonces lo necesitas), solo que no es realmente una respuesta a la pregunta aquí. UPSERT generalmente significa que tiene una fila que desea almacenar en la tabla y simplemente no sabe si ya tiene una fila coincidente para colocar los valores o si necesita insertarlos como una nueva fila. Su caso de uso es que si ya tiene una fila coincidente, desea ignorar la mayoría de los valores de la nueva fila . Eso está bien, simplemente no es la pregunta que se hizo.
Aristóteles Pagaltzis

1

Creo que esto puede ser lo que estás buscando: cláusula ON CONFLICT .

Si define su tabla de esta manera:

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    field1 TEXT 
); 

Ahora, si hace un INSERT con una identificación que ya existe, SQLite automáticamente ACTUALIZA en lugar de INSERTAR.

Hth ...


66
No creo que esto funcione, eliminará las columnas que faltan en la declaración de inserción
Sam Saffron

3
@Mosor: -1 de mi parte, lo siento. Esto es lo mismo que emitir una REPLACEdeclaración.
Alix Axel

77
-1 porque esto borra y luego inserta si la clave primaria ya existe.
2010

1

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.


Nota para todos: INSERTAR O REEMPLAZAR NO es de lo que trata esta publicación. INSERTAR O REEMPLAZAR creará una NUEVA fila en su tabla, con una nueva ID. Eso no es una ACTUALIZACIÓN.
sapbucket

-2

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!


21
-1 INSERT OR REPLACEno es un UPSERT. Vea la "respuesta" de gregschlom por la razón. La solución de Eric B realmente funciona y necesita algunos votos positivos.
Día

-4
SELECT COUNT(*) FROM table1 WHERE id = 1;

Si COUNT(*) = 0

INSERT INTO table1(col1, col2, cole) VALUES(var1,var2,var3);

si no si COUNT(*) > 0

UPDATE table1 SET col1 = var4, col2 = var5, col3 = var6 WHERE id = 1;

Esto es demasiado complicado, SQL puede manejar esto bien en una consulta
Robin Kanters
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.