2017-08-04 7 views
0

重複したINSERTを検出して静かに破棄しようとしています。重複が検出された場合は、既存のレコードのID(主キー)を返します。それ以外の場合は、レコードを挿入して新しいIDを返します。Rails Postgres重複行の挿入問題

私はこれをRULEまたはTRIGGERのいずれかで行うことができますが、両方とも欠点があります。私のルールの例は次のとおりです。

CREATE OR REPLACE RULE territory_products_ignore_duplicate_inserts AS 
ON INSERT TO territory_products 
WHERE (EXISTS (SELECT 1 
     FROM territory_products tp 
     WHERE tp.territory_id = NEW.territory_id AND tp.product_id = NEW.product_id)) DO INSTEAD SELECT tp.id 
     FROM territory_products tp WHERE tp.territory_id = NEW.territory_id AND tp.product_id = NEW.product_id LIMIT 1; 

これをSQLコンソールまたはpsqlでINSERTでテストすると問題はありません。重複がある場合、最初の既存のレコードのIDを返し、INSERTは返しません。それ以外の場合は、INSERTが実行されます。しかし、Railsのでは、それが失敗し、このエラーが返されます。

エラー:「territory_products」の関係に ヒントを返すINSERTを実行することはできません:あなたの代わりにRETURNING句を持つルールでくださいINSERT ON無条件を必要としています。 TRIGGERへの移行

、私はこれを試してみてください。

CREATE OR REPLACE Function territory_products_ignore_dups() Returns Trigger 
As $$ 
Begin 
If Exists (
    Select id From territory_products tp 
    Where tp.territory_id = NEW.territory_id And tp.product_id = NEW.product_id    
) Then 
    Return NULL; 
End If; 
Return NEW; 
End; 
$$ Language plpgsql; 

Create Trigger territory_products_ignore_dups 
Before Insert On territory_products 
For Each Row 
Execute Procedure territory_products_ignore_dups(); 

これも理由は禁止する必要が戻りNULL(の、私はそれが既存のIDを返すために得ることができないことを除いて、正常に動作しますINSERT)。

誰でもこれらの問題のいずれかを解決できるので、私が探している結果が得られますか? (例えば、重複の場合には静的にINSERTを破棄し、既存のレコードのIDを返すか、INSERTが成功した場合は新しいレコードのIDを返します)。既存のレコードのためにあなたのロジックを見て

# migration file 
add_index : territory_products, [:territory_id, :product_id], unique: true 

または存在しない場合は作成します。

+4

Railsで一意性検証を作成し、競合状態を防ぐためにPGに複合インデックスを追加するだけではどうですか。 – max

+0

最大のことを言ってください。一意のレコードのみを強制し、レコードが存在する場合は更新を行うことができます。 – bkunzi01

+0

Railsは基本的にSELECTを実行してINSERTの前にチェックを実行するので、大量の状況では一意性の検証は完全なものではありません。また、DB上で更新スクリプトを実行することもありますので、そこにチェックを入れたいのですが、どこにでも例外をスローすることはありません。 – BeachBum

答えて

0

は、データベースに一意のインデックスを追加します。競合状態の場合には例外が発生します。レコードを再度検索するだけです。

#logic when finding/creating record 
begin 
    TerritoryProduct.where(territory_id: params[:territory_id], product_id: params[:product_id]).first_or_create! 
rescue ActiveRecord::RecordNotUnique 
    # In case it already exists 
    TerritoryProduct.where(territory_id: params[:territory_id], product_id: params[:product_id]).first 
end 
+0

これを買うよ。しかし、私はRailsとDBへのスクリプト挿入の両方で例外を発生させないようにしていました。重複を無視し、存在する場合は既存のIDを返します。ルールやトリガーを行う方法はありませんか? – BeachBum