Pregunta

http://en.wikipedia.org/wiki/Upsert

Insertar actualización del proceso almacenado en SQL Server

¿Existe alguna forma inteligente de hacer esto en SQLite en la 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 identificación es una clave principal, por lo que solo habrá un registro para UPSERT.

(Estoy tratando de evitar la sobrecarga de SELECCIONAR 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 así 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 las dos primeras gotas no causarán un conflicto, solo la identificación lo haría, así que me asustaría a blob1 y blob2 no serían reemplazados (como se desee)


Actualizaciones en SQLite cuando los datos de enlace son una transacción completa, lo que significa que cada fila enviada para actualizarse requiere:Preparar/bind/step/finalizar declaraciones a diferencia de la inserción que permite el uso de la función de reinicio

La vida de un objeto de declaración es más o menos así:

  1. Crea el objeto usando sqlite3_prepare_v2()
  2. Vincule valores a parámetros del host utilizando las interfaces sqlite3_bind_.
  3. Ejecute SQL llamando a sqlite3_step()
  4. Restablezca la declaración usando sqlite3_reset(), luego regrese al paso 2 y repita.
  5. Destruya el objeto de declaración usando sqlite3_finalize().

ACTUALIZAR Supongo que es lento en comparación con INSERTAR, pero ¿cómo se compara con SELECCIONAR usando la clave principal?

¿Quizás debería usar la selección para leer la cuarta columna (Blob3) y luego usar REEMPLAZAR para escribir un nuevo registro que combine la cuarta columna original con los nuevos datos para las primeras 3 columnas?

¿Fue útil?

Solución

Suponiendo 3 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');

BUENO: Esto actualizará 2 de las columnas. Cuando ID = 1 existe, el NOMBRE no se verá afectado. Cuando ID = 1 no existe, el nombre será 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')
          );

Otros consejos

INSERTAR O REEMPLAZAR es NO 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.

La respuesta de Eric B. está bien si desea conservar solo una o quizás dos columnas de la fila existente.Si desea conservar muchas columnas, rápidamente se vuelve demasiado engorroso.

Este es un enfoque que se adaptará bien a cualquier cantidad de columnas en ambos lados.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 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 su name, quiero que siga teniendo el valor de ID anterior (¡obviamente!).

logro un verdadero UPSERT con 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 de INSERT SELECT con una unión exterior izquierda, para unir una fila existente a los nuevos valores.

Aquí, si una fila no existía previamente, 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 este será reutilizado.Que es exactamente lo que quería.

De hecho, esto es muy flexible.Observe cómo el ts La columna falta por todos lados, porque tiene un DEFAULT valor, SQLite hará lo correcto en cualquier caso, por lo que no tengo que ocuparme de ello yo mismo.

También puede incluir una columna tanto en el new y old lados y luego usar, p.e. COALESCE(new.content, old.content) en el exterior SELECT para decir “inserte el contenido nuevo si lo hubiera; de lo contrario, conserve el contenido anterior” – p.e.si está utilizando una consulta fija y vincula los nuevos valores con marcadores de posición.

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

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

Si generalmente está haciendo inserciones, lo haría

  1. Comenzar una transacción
  2. Pruebe una inserción
  3. Verificar error de violación de clave principal
  4. si recibimos un error, haga la actualización
  5. Comprometer

De esta forma, evitas la selección y eres transaccionalmente sólido en Sqlite.

Esta respuesta se ha actualizado y, por lo tanto, los comentarios a continuación ya no se aplican.

2018-05-18 STOP PRESS.

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 hacer esto 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.

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 ser 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;

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 tenerlo 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;

Insertar 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;

De hecho, puedes hacer un upsert en SQLite, solo se ve un poco diferente de lo que estás 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

Ampliando en Aristóteles & # 8217; respuesta de puede SELECCIONAR desde una tabla 'singleton' ficticia (una tabla de tu 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";

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 el 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

El mejor enfoque que conozco es hacer una actualización, seguida de una inserción. La & "; Gastos generales de una selección &"; es necesario, 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 su tabla & amp; nombres de campo para hacer lo que quieras.

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

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

Creo que esto puede ser lo que está 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 ...

Este método remezcla 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, first_name, last_name

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 se debe usar la instrucción select 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 los valores que deberían ser.

Siguiendo Aristóteles Pagaltzis y la idea de COALESCE de Respuesta de Eric B & # 8217 , aquí es 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 INSERT ya que se supone que es autoincrement. Si es solo una clave primaria generada, entonces también se puede utilizar new.fieldname (consulte 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 <=>, cuando quiera actualizar los valores, use <=>

Después de leer este hilo y decepcionarme de que no fuera fácil simplemente responder a este " UPSERT " ing, investigué más a fondo ...

Puede hacer esto directamente y fácilmente en SQLITE.

En lugar de usar: INSERT INTO

Uso: INSERT OR REPLACE INTO

¡Esto hace exactamente lo que quieres que haga!

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

if COUNT(*) = 0

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

más si COUNT(*) > 0

UPDATE table1 SET col1 = var4, col2 = var5, col3 = var6 WHERE id = 1;
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top