如果满足条件,防止插入

时间:2014-03-29 15:50:36

标签: sql postgresql database-design triggers constraints

我有一张像这样的表Content

id | text | date | idUser → User | contentType 

另一张表Answer

idAnswer → Content | idQuestion → Content | isAccepted

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

我尝试使用以下触发器解决此问题,但当我尝试插入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;$$;

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

2 个答案:

答案 0 :(得分:6)

您的基本方法是合理的。触发器是有效的解决方案。除 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列也是如此。

答案 1 :(得分:1)

我会以不同的方式处理这个问题。

建议:

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

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

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