2012-03-19 12 views
12

本当に小さなSinatraアプリでSequelを使い始めました。 DBテーブルは1つしかないので、モデルを使う必要はありません。Sequelデータセットの更新または挿入方法は?

私はそれが存在する場合は、レコードを更新するか、そうでない場合は、新しいレコードを挿入します。 $numsDB[:numbers]データセットです

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 

:私は、次の解決策を考え出しました。

私はこの方法では、「更新または挿入」行動の最もエレガントな実装ではありませんと信じています。

どうすればよいですか?

+0

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

答えて

17

おそらく更新/挿入する前に確認しないでください。ので:

  1. これは、余分なデシベル呼び出しです。
  2. これは競合状態をもたらす可能性があります。あなたの代わりに何をすべき

更新の戻り値をテストすることです:

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

これは素晴らしいソリューションです。おかげで – Akarsh

+1

この解決策はまだ競合状態の可能性を紹介します。 2つの並列プロセス/スレッドが挿入(行3)に達する前に更新(行2)を実行すると、2つのレコードが挿入されます。 mutex、db lock、適切なトランザクション戦略などの使用を検討してください。 – Flexoid

+0

フレキソイド:あなたは正しいですし、以下の解決策 - 基本的には「すべてを取引に入れる」が正しいです。それでも、「UPDATE + INSERT」で十分な場合は、コマンドの「SELECT、UPDATE、INSERT」の順番はありません。 (+トランザクション、もちろん。)面白いことは、トランザクション:並列で実行されている2つのトランザクションが同じカウンターを増やしても、あなたはまだ問題に陥っています。 – radiospiel

2

私は(いくつかのデータベースはsupported by Sequelかもしれない特定のアップサート構文を、持っているが)あなたはそれよりも、それは非常にクリーンすることはできませんと信じています。あなたはあなたが持っているものを別の方法で包んで、それが存在しないふりをすることができます。 :)

だけのカップル提案:

  • トランザクション内のすべてを囲みます。
  • (number, type)フィールドに一意のインデックスを作成します。
  • グローバル変数は使用しないでください。
1

それが現在のカウンタを更新するために動作しません除いあなたは、upsertを使用することができます。うまくいけば、将来のバージョンは - アイデアを歓迎します!

8

続編4.25.0(リリース7月31日、2015)insert_conflict for Postgres v9.5+
続編4.30.0(リリース1月4日、2016)これはそうのように、行を挿入または更新するためにも使用することができるinsert_conflict for SQLite

を追加追加しました:

DB[:table_name].insert_conflict(:update).insert(number:n, type:t, counter:c) 
関連する問題