2012-03-19 13 views
12

Acabo de empezar a usar Sequel en una aplicación Sinatra realmente pequeña. Como tengo una sola tabla DB, no necesito usar modelos.¿Cómo actualizar o insertar en el conjunto de datos de Sequel?

Quiero actualizar un registro si existe o insertar un nuevo registro si no existe. Se me ocurrió la siguiente solución:

rec = $nums.where(:number => n, :type => t) 
    if $nums.select(1).where(rec.exists) 
    rec.update(:counter => :counter + 1) 
    else 
    $nums.insert(:number => n, :counter => 1, :type => t) 
    end 

Dónde $nums es DB[:numbers] conjunto de datos.

Creo que de esta manera no es la implementación más elegante del comportamiento de "actualización o inserción".

¿Cómo se debe hacer?

+0

http://stackoverflow.com/questions/3647454/increment-counter-or-insert-row-in-one-statement-in-sqlite – Reactormonk

Respuesta

17

Probablemente no lo verifique antes de actualizar/insertar; porque:

  1. Esta es una llamada extra db.
  2. Esto podría introducir una condición de carrera.

Lo que debe hacer en su lugar es para probar el valor de retorno de actualización:

rec = $nums.where(:number => n, :type => t) 
if 1 != rec.update(:counter => :counter + 1) 
    $nums.insert(:number => n, :counter => 1, :type => t) 
end 
+0

Esta es una buena solución. Gracias – Akarsh

+1

Esta solución todavía presenta la posibilidad de condición de carrera. Si dos procesos/hilos paralelos realizan la actualización (línea 2) antes de que uno de ellos alcance la inserción (línea 3), se insertarán dos registros. Considere usar algo como mutex, db lock o una estrategia de transacción apropiada. – Flexoid

+0

Flexoid: tiene razón, y la solución a continuación, básicamente "poner todo en una transacción", es correcta. Aún así, no tiene sentido un orden de comandos "SELECCIONAR, ACTUALIZAR, INSERTAR" cuando "ACTUALIZAR + INSERTAR" es suficiente. (+ la transacción, por supuesto.) Cosa interesante con la transacción: si dos transacciones que se ejecutan en paralelo incrementan el mismo contador * todavía * se topa con problemas. – radiospiel

2

Creo que no se puede tener mucho más limpio que eso (aunque algunas bases de datos tienen una sintaxis upsert específica, que podría ser supported by Sequel). Puedes envolver lo que tienes en un método aparte y pretender que no existe. :)

Sólo par de sugerencias:

  • Encierra todo dentro de una transacción.
  • Crear índice único en (number, type) campos.
  • No utilice variables globales.
1

Usted podría utilizar upsert, excepto que no funciona actualmente para la actualización de los contadores. Con suerte, una versión futura: ¡ideas bienvenidas!

8

Sequel 4.25.0 (31 de de julio de libertad, 2015) añadió insert_conflict for Postgres v9.5+
Sequel 4.30.0 (4º de enero de libertad, 2016) añadió insert_conflict for SQLite

Esto se puede utilizar para insertar o actualizar una fila, al igual que :

DB[:table_name].insert_conflict(:update).insert(number:n, type:t, counter:c) 
Cuestiones relacionadas