【问题标题】:Prevent insert if condition is met如果满足条件,则阻止插入
【发布时间】:2023-04-05 14:44:01
【问题描述】:

我有一张这样的桌子Content

id | text | date | idUser → User | contentType 

还有一张桌子Answer

idAnswer → Content | idQuestion → Content | isAccepted

我想确保Answer 的日期大于Question 的日期。问题是 ContentcontentType = 'QUESTION'。

我尝试使用以下触发器解决此问题,但是当我尝试插入 Answer 时出现错误:

ERROR:  record "new" has no field "idanswer"
CONTEXT:  SQL statement "SELECT (SELECT "Content".date FROM "Content" WHERE "Content".id = NEW.idAnswer) < (SELECT "Content".date FROM "Content" WHERE "Content".id = NEW.idQuestion)"
PL/pgSQL function "check_valid_date_answer" line 2 at IF

触发器:

CREATE TRIGGER check_valid_answer 
AFTER INSERT ON "Answer"
FOR EACH ROW EXECUTE PROCEDURE check_valid_date_answer();

触发功能:

CREATE FUNCTION check_valid_date_answer() RETURNS trigger
    LANGUAGE plpgsql
    AS $$BEGIN
  IF (SELECT "Content".date FROM "Content"
      WHERE "Content".id = NEW.idAnswer)
   < (SELECT "Content".date FROM "Content"
      WHERE "Content".id = NEW.idQuestion) 
  THEN
    RAISE NOTICE 'This Answer is an invalid date';
  END IF;
  RETURN NEW;
END;$$;

所以,我的问题是:我真的需要为此创建一个触发器吗?我看到我不能在Answer 中使用CHECK,因为我需要与另一个表的属性进行比较。有没有其他(更容易/更好)的方法来做到这一点?如果不是,为什么会出现错误以及如何解决?

【问题讨论】:

  • 您的create function 拥有if (select。错误消息显示SELECT (SELECT 。您确定问题中的代码与产生错误的代码相对应吗?
  • @Gordon Linoff 该函数是在phpPgAdmin 图形界面中创建的,但我是从那里导出的文件中复制粘贴的。

标签: sql postgresql database-design triggers constraints


【解决方案1】:

我会以不同的方式解决这个问题。

推荐:

  • 如果要在之前更改数据,请使用 BEFORE INSERT 触发器 插入它
  • 如果您必须执行其他操作,请使用 AFTER INSERT 触发器 工作
  • 如果您有其他数据一致性要求,请使用 CHECK 子句。

所以编写一个sql函数来检查一个日期比另一个日期早的条件,并添加检查约束。是的,您可以从函数中的其他表中进行选择。

我写了类似的东西(复杂检查)in answer to this question on SO

【讨论】:

  • 那么,您的意思是在CHECK 约束上使用子查询?我没有考虑过,但似乎是个好主意。像这样的东西? stackoverflow.com/questions/13000698/…
  • 嗯,就像我写的,见链接。这是简单的 SQL。
  • 你宁愿在你的 CREATE TABLE 中写 CHECK...。
  • 我试试看再说。
【解决方案2】:

您的基本方法是合理的。触发器是一个有效的解决方案。除了 3 个问题,它应该可以工作:

1) 您的命名约定

我们需要查看您的确切表定义才能确定,但​​证据就在那里。错误消息显示:has no field "idanswer" - 小写。不说 "idAnswer" - CaMeL 案例。如果您在 Postgres 中创建 CaMeL 案例标识符,您必须在它们的余生中到处使用双引号。

2) 中止违反插入

  • 要么提出 EXCEPTION 而不是友好的 NOTICE 来实际中止整个交易。

  • RETURN NULL 而不是RETURN NEW 只是静默中止插入的行而不引发异常并且不回滚任何内容。

我会做第一个。这可能会修复手头的错误并起作用:

CREATE FUNCTION trg_answer_insbef_check()
  RETURNS trigger AS
$func$
BEGIN
   IF (SELECT c.date FROM "Content" c WHERE c.id = NEW."idAnswer")
    < (SELECT c.date FROM "Content" c WHERE c.id = NEW."idQuestion") THEN
      RAISE EXCEPTION 'This Answer is an invalid date';
   END IF;
   RETURN NEW;
END
$func$  LANGUAGE plpgsql;

正确的解决方案是专门使用legal, lower case names 并完全避免此类问题。这包括您不幸的表名以及列名date,这是标准 SQL 中的 reserved word,不应用作标识符 - 即使 Postgres 允许。

3) 应该是BEFORE trigger

CREATE TRIGGER insbef_check
BEFORE INSERT ON "Answer"
FOR EACH ROW EXECUTE PROCEDURE trg_answer_insbef_check();

您想在执行其他任何操作之前中止无效插入。

当然,您必须确保时间戳表 Content 无法更改,或者您需要更多触发器来确保满足您的条件。
Answer 中的 fk 列也是如此。

【讨论】:

  • 对不起,idAnswer 是正确的列名,我的错。但是,有些东西不能正常工作。我正在插入一个日期低于QuestionAnswer,并且插入成功。 PS:触发器名称后面没有()
  • @HugoSousa:是的,还有第三个问题:您实际上并没有中止插入。将 3) 添加到答案中...
  • 刚刚自己发现了问题。需要提出EXCEPTION,而不是NOTICE,它起作用了。非常感谢您的完整答案。好像我现在要花一些时间来更改我的表和列名:) 另外,刚刚发现这篇文章由你 stackoverflow.com/questions/18409952/… 回答。作为一个好的做法,我不应该使用CHECKconstraint 来执行此操作,如@Str 所述。 ?
  • @HugoSousa:没错。或RETURN NULL.
猜你喜欢
  • 2019-03-01
  • 2020-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-19
  • 1970-01-01
相关资源
最近更新 更多