【问题标题】:Rails Postgres Insert Duplicate Row Check IssuesRails Postgres 插入重复行检查问题
【发布时间】:2018-01-12 18:42:09
【问题描述】:

我正在尝试检测并将任何重复的 INSERT 丢弃到表中,如果检测到重复,则返回现有记录的 ID(主键)。否则,插入记录并返回新 ID。

我可以使用 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 控制台或使用 INSERT 的 psql 中对此进行测试可以正常工作。如果存在欺骗,它将返回第一个现有记录的 id,而不执行 INSERT。否则,它将继续插入。但是,在 Rails 中,它会失败并返回此错误:

错误:无法对关系“territory_products”执行插入返回 提示:您需要一个带有 RETURNING 子句的无条件 ON INSERT DO INSTEAD 规则。

转到触发器,我试试这个:

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();

这也可以正常工作,除了我无法让它返回现有 ID,因为 Return NULL(这是不允许 INSERT 所必需的)。

谁能解决这些问题中的任何一个,所以我得到了我正在寻找的结果? (例如,如果出现重复,则静默丢弃 INSERT,并返回现有记录的 ID。或者如果 INSERT 成功,则返回新记录的 ID)。

【问题讨论】:

  • 为什么不在 Rails 中创建唯一性验证并在 PG 中添加复合索引以防止竞争条件?
  • 按照麦克斯所说的去做。仅强制唯一记录,如果记录存在,您可以进行更新。
  • 唯一性验证在大容量情况下并不是完全证明的,因为 Rails 基本上只是在 INSERT 之前运行 SELECT 来进行检查。此外,我们有时会在数据库上运行更新脚本,因此希望有检查,但不要到处抛出异常。

标签: ruby-on-rails postgresql duplicates


【解决方案1】:

为您的数据库添加唯一索引:

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

在您的逻辑中查找现有记录,如果不存在则创建。如果出现竞争条件,它将引发异常。再次搜索记录即可。

#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

【讨论】:

  • 好的,我买那个。但我试图避免在 Rails 中以及在脚本插入数据库期间引发异常。只需忽略重复项并返回现有 ID(如果有)。有没有办法让规则或触发器做到这一点?
猜你喜欢
  • 2014-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-06
  • 2011-12-04
  • 1970-01-01
相关资源
最近更新 更多