Tengo la siguiente tabla de contadores:
CREATE TABLE cache (
key text PRIMARY KEY,
generation int
);
Me gustaría incrementar uno de los contadores o ponerlo en cero si la fila correspondiente aún no existe. ¿Hay alguna manera de hacer esto sin problemas de concurrencia en SQL estándar? La operación a veces es parte de una transacción, a veces por separado.
El SQL debe ejecutarse sin modificaciones en SQLite, PostgreSQL y MySQL, si es posible.
Una búsqueda arrojó varias ideas que sufren problemas de concurrencia o son específicas de una base de datos:
Intente con
INSERT
una nueva fila yUPDATE
si hubo un error. Desafortunadamente, el error deINSERT
aborta la transacción actual.UPDATE
la fila, y si no se modificaron filas,INSERT
una nueva fila.MySQL tiene una
ON DUPLICATE KEY UPDATE
cláusula.
EDITAR: Gracias por todas las excelentes respuestas. Parece que Paul tiene razón y no hay una forma única y portátil de hacer esto. Eso me sorprende bastante, ya que parece una operación muy básica.
Respuestas:
MySQL (y posteriormente SQLite) también admiten la sintaxis REPLACE INTO:
REPLACE INTO my_table (pk_id, col1) VALUES (5, '123');
Esto identifica automáticamente la clave principal y encuentra una fila coincidente para actualizar, insertando una nueva si no se encuentra ninguna.
fuente
SQLite admite reemplazar una fila si ya existe:
INSERT OR REPLACE INTO [...blah...]
Puedes acortar esto a
REPLACE INTO [...blah...]
Este atajo se agregó para que sea compatible con la
REPLACE INTO
expresión MySQL .fuente
PRAMARY KEY
en tus valores.Haría algo como lo siguiente:
INSERT INTO cache VALUES (key, generation) ON DUPLICATE KEY UPDATE (key = key, generation = generation + 1);
Estableciendo el valor de generación en 0 en el código o en el sql pero usando el ON DUP ... para incrementar el valor. Creo que esa es la sintaxis de todos modos.
fuente
la cláusula ON DUPLICATE KEY UPDATE es la mejor solución porque: REPLACE hace un DELETE seguido de un INSERT, por lo que durante un período muy breve, el registro se elimina, lo que crea la mínima posibilidad de que una consulta pueda volver después de haber saltado eso si la página se visto durante la consulta REPLACE.
Prefiero INSERT ... ON DUPLICATE UPDATE ... por esa razón.
La solución de jmoz es la mejor: aunque prefiero la sintaxis SET a los paréntesis
INSERT INTO cache SET key = 'key', generation = 'generation' ON DUPLICATE KEY UPDATE key = 'key', generation = (generation + 1) ;
fuente
No sé si va a encontrar una solución de plataforma neutral.
Esto se denomina comúnmente "UPSERT".
Vea algunas discusiones relacionadas:
fuente
En PostgreSQL no hay un comando de fusión, y escribirlo no es trivial; en realidad, hay casos extremos extraños que hacen que la tarea sea "interesante".
El mejor enfoque (como en: trabajar en las condiciones más posibles) es usar la función, como la que se muestra en manual (merge_db).
Si no desea utilizar la función, generalmente puede salirse con la suya:
updated = db.execute(UPDATE ... RETURNING 1) if (!updated) db.execute(INSERT...)
Sólo recuerde que no es culpa la prueba y que se fallará eventualmente.
fuente
SQL estándar proporciona la instrucción MERGE para esta tarea. No todos los DBMS admiten la declaración MERGE.
fuente
Si no tiene una forma común de actualizar o insertar atómicamente (por ejemplo, a través de una transacción), puede recurrir a otro esquema de bloqueo. Un archivo de 0 bytes, exclusión mutua del sistema, canalización con nombre, etc.
fuente
¿Podrías usar un disparador de inserción? Si falla, realice una actualización.
fuente
Si está de acuerdo con el uso de una biblioteca que escribe el SQL por usted, entonces puede usar Upsert (actualmente Ruby y Python solamente):
Eso funciona en MySQL, Postgres y SQLite3.
Escribe un procedimiento almacenado o una función definida por el usuario (UDF) en MySQL y Postgres. Se utiliza
INSERT OR REPLACE
en SQLite3.fuente