简体   繁体   English

PostgreSQL中的PL / pgSQL查询返回新的空表的结果

[英]PL/pgSQL query in PostgreSQL returns result for new, empty table

I am learning to use triggers in PostgreSQL but run into an issue with this code: 我正在学习在PostgreSQL中使用触发器,但此代码遇到问题:

CREATE OR REPLACE FUNCTION checkAdressen() RETURNS  TRIGGER AS $$
DECLARE
  adrCnt int = 0;
BEGIN
  SELECT INTO adrCnt count(*) FROM Adresse
  WHERE gehoert_zu = NEW.kundenId;

  IF adrCnt < 1 OR adrCnt > 3 THEN
    RAISE EXCEPTION 'Customer must have 1 to 3 addresses.';
  ELSE
    RAISE EXCEPTION 'No exception';
  END IF;
END;
$$ LANGUAGE plpgsql;

I create a trigger with this procedure after freshly creating all my tables so they are all empty. 在重新创建所有表之后,使用此过程创建触发器,使它们全部为空。 However the count(*) function in the above code returns 1. When I run SELECT count(*) FROM adresse; 但是上面代码中的count(*)函数返回1。当我运行SELECT count(*) FROM adresse; outside of PL/pgSQL, I get 0. I tried using the FOUND variable but it is always true. 在PL / pgSQL之外,我得到0。我尝试使用FOUND变量,但这始终是正确的。

Even more strangely, when I insert some values into my tables and then delete them again so that they are empty again, the code works as intended and count(*) returns 0. Also if I leave out the WHERE gehoert_zu = NEW.kundenId , count(*) returns 0 which means I get more results with the WHERE clause than without. 更奇怪的是,当我在表中插入一些值然后再次删除它们以便它们再次为空时,代码将按预期工作,并且count(*)返回0。同样,如果我忽略了WHERE gehoert_zu = NEW.kundenIdcount(*)返回0,这意味着使用WHERE子句可以获得比不使用更多的结果。

--Edit: - 编辑:

Here is an example of how I use the procedure: 这是我如何使用该过程的示例:

CREATE TABLE kunde (
kundenId    int PRIMARY KEY
);

CREATE TABLE adresse (
id      int PRIMARY KEY,
gehoert_zu  int REFERENCES kunde
);

CREATE CONSTRAINT TRIGGER adressenKonsistenzTrigger AFTER INSERT ON Kunde
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE PROCEDURE checkAdressen();

INSERT INTO kunde VALUES (1);
INSERT INTO adresse VALUES (1,1);

It looks like I am getting the DEFERRABLE INITIALLY DEFERRED part wrong. 看来我将DEFERRABLE INITIALLY DEFERRED部分弄错了。 I assumed the trigger would be executed after the first INSERT statement but it happens after the second one, although the inserts are not inside a BEGIN; 我假设触发器将在第一个INSERT语句之后执行,但它会在第二个INSERT语句之后执行,尽管插入内容不在BEGIN; - COMMIT; - COMMIT; - Block. -块

According to the PostgreSQL Documentation inserts are commited automatically every time if not inside such a block and thus there shouldn't be an entry in adresse when the first INSERT statement is commited. 根据PostgreSQL文档刀片自动每次COMMITED如果没有这样一个块内,因此不应该有一个条目中的adresse当第一INSERT语句将提交。

Can anyone point out my mistake? 谁能指出我的错误?

--Edit: - 编辑:

The trigger and DEFERRABLE INITIALLY DEFERRED seem to be working all right. 触发器和DEFERRABLE INITIALLY DEFERRED似乎工作正常。 My mistake was to assume that since I am not using a BEGIN - COMMIT -Block each insert would be executed in an own transaction with the trigger being executed afterwards every time. 我的错误是假设由于我不使用BEGIN - COMMIT -Block,所以每个插入操作都将在自己的事务中执行,而触发器每次之后都将执行。

However even without the BEGIN - COMMIT all inserts get bundled into one transaction and the trigger is executed afterwards. 但是,即使没有BEGIN - COMMIT所有插入也都捆绑到一个事务中,然后执行触发器。 Given this behaviour, what is the point in using BEGIN - COMMIT ? 鉴于这种行为,使用BEGIN - COMMIT什么意义?

You need a transaction plus the "DEFERRABLE INITIALLY DEFERRED" because of the chicken and egg problem. 由于鸡肉和鸡蛋的问题,您需要进行一笔交易,并加上“可延期的初始延期”。


starting with two empty tables: 从两个空表开始:

  • you cannot insert a single row into the person table, because the it needs at least one address. 您不能在个人表中插入一行,因为它至少需要一个地址。
  • you cannot insert a single row into the address table, because the FK constraint needs a corresponding row on the person table to exist 您不能在地址表中插入一行,因为FK约束需要人员表上存在对应的行

This is why you need to bundle the two inserts into one operation: the transaction. 这就是为什么您需要将两个插入内容捆绑到一个操作中:事务。 You need the BEGIN+ COMMIT, and the DEFERRABLE allows transient forbidden database states to exists: it causes the check to be evaluated at commit time. 您需要BEGIN + COMMIT,并且DEFERRABLE允许存在暂时禁止的数据库状态 :这将导致在提交时评估检查。

This may seem a bit silly, but the answer is you need to stop deferring the trigger and run it BEFORE the insert. 这似乎有点愚蠢,但是答案是您需要停止延迟触发器,并在插入BEFORE运行它。 If you run it after the insert, of course there is data in the table. 如果您在插入后运行它,那么表中当然会有数据。

As far as I can tell this is working as expected. 据我所知,这按预期工作。

One further note, you probably dont mean: 进一步说明,您可能不是要说的:

RAISE EXCEPTION 'No Exception';

You probably want 你可能想要

RAISE INFO 'No Exception';

Then you can change your settings and run queries in transactions to test that the trigger does what you want it to do. 然后,您可以更改设置并在事务中运行查询以测试触发器是否按照您希望的方式进行。 As it is, every insert is going to fail and you have no way to move this into production without editing your procedure. 实际上,每个插入都会失败,如果不编辑过程就无法将其移入生产环境。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM