【发布时间】: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