2012-03-19 11 views
12

Ho appena iniziato a usare Sequel in una piccola app Sinatra. Dato che ho una sola tabella DB, non ho bisogno di usare i modelli.Come aggiornare o inserire nel set di dati Sequel?

Desidero aggiornare un record se esiste o inserire un nuovo record se non lo è. Sono venuto con la seguente soluzione:

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 

Dove $nums è DB[:numbers] set di dati.

Credo che in questo modo non sia l'implementazione più elegante del comportamento di "aggiornamento o inserimento".

Come dovrebbe essere fatto?

+0

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

risposta

17

Probabilmente non dovresti controllare prima di aggiornare/inserire; perché:

  1. Questa è una chiamata DB aggiuntiva.
  2. Questo potrebbe introdurre una condizione di gara.

Che cosa si dovrebbe fare, invece è quello di testare il valore di ritorno di aggiornamento:

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

Questa è una bella soluzione. Grazie – Akarsh

+1

Questa soluzione introduce ancora possibilità di condizioni di gara. Se due processi/thread paralleli eseguono l'aggiornamento (riga 2) prima che uno di essi raggiunga l'inserimento (riga 3), verranno inseriti due record. Prendi in considerazione l'utilizzo di qualcosa come mutex, db lock o strategia di transazione appropriata. – Flexoid

+0

Flexoid: hai ragione, e la soluzione di seguito - in pratica "mettere tutto in una transazione" è corretta. Tuttavia, non vi è alcun punto nell'ordine "SELECT, UPDATE, INSERT" dei comandi quando "UPDATE + INSERT" è sufficiente. (+ la transazione, ovviamente.) Interessante cosa w/transazione: se due transazioni in esecuzione in parallelo incrementano lo stesso contatore si * ancora * incontrano problemi. – radiospiel

2

Credo che non si possa avere molto più pulito di quello (anche se alcuni database hanno una specifica sintassi di upsert, che potrebbe essere supported by Sequel). Puoi semplicemente avvolgere ciò che hai in un metodo separato e fingere che non esista. :)

Appena coppia suggerimenti:

  • racchiudere tutto all'interno di una transazione.
  • Crea un indice univoco nei campi (number, type).
  • Non utilizzare variabili globali.
1

Si potrebbe utilizzare upsert, tranne che attualmente non funziona per l'aggiornamento contatori. Speriamo che una versione futura - idee benvenuto!

8

Sequel 4.25.0 (31 luglio rilasciato, 2015) ha aggiunto insert_conflict for Postgres v9.5+
Sequel 4.30.0 (rilasciato 4 gennaio 2016) ha aggiunto insert_conflict for SQLite

Questo può essere utilizzato sia per inserire o aggiornare una riga, in questo modo :

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