为什么这个触发器不会阻止行被插入到 sqlite 中?

Why does this trigger not prevent rows from being inserted in sqlite?

这是我的 tables:

CREATE TABLE users(
    user_id INT,
    channel_id INT NOT NULL UNIQUE,
    PRIMARY KEY (user_id)
);

CREATE TABLE credits(
    user_id INT,
    number_of_items INT CHECK(number_of_items > 0),
    expiration DATETIME,
    PRIMARY KEY (user_id, number_of_items, expiration),
    FOREIGN KEY (user_id) REFERENCES users (user_id)
);

CREATE TABLE users_items(
    user_id INT,
    item_id INT,
    PRIMARY KEY (user_id, item_id),
    FOREIGN KEY (user_id) REFERENCES users (user_id)
);

这是我实现的触发器:

CREATE TRIGGER check_has_enough_credits_to_monitor_item
BEFORE INSERT ON users_items
BEGIN
   SELECT
     CASE
       WHEN (SELECT COUNT(*) FROM users_items WHERE user_id = NEW.user_id) >
            (SELECT sum(number_of_items) FROM credits WHERE user_id = NEW.user_id)
         THEN RAISE (ABORT, 'No more items allowed')
     END;
END;

如果具有该 ID 的用户已经超过他的信用限额或他的信用已过期,我希望此触发器阻止在 users_items table 中插入一行,但是,当我已使用 this fiddle 对其进行测试,触发器不会引发错误。目前触发器只检查允许的积分数是否与添加到 users_items 的项目数相比,而不检查日期,但我计划通过向触发器添加类似 AND expiration >= datetime('now') 的内容来检查日期让它看起来像这样:

CREATE TRIGGER check_has_enough_credits_to_monitor_item
BEFORE INSERT ON users_items
BEGIN
   SELECT
     CASE
       WHEN (SELECT COUNT(*) FROM users_items WHERE user_id = NEW.user_id) >=
            (SELECT sum(number_of_items) FROM credits WHERE user_id = NEW.user_id AND expiration >= datetime('now'))
         THEN RAISE (ABORT, 'No more items allowed')
     END;
END;

根据长期的经验,我想警告你,不要这样做。如果您可以改用声明的引用完整性 (DRI),则不要使用触发器,并且不要将触发器用于引用完整性以外的任何用途。特别是,不要将触发器用于业务规则。

如果你不遵守这一政策,你希望有一天你能遵守。

而是使用交易。例如,编写插入语句来强制执行规则:

insert into user_items
select ( ... values ... )
where [user has not surpassed his credit limit and his credits have not expired]

测试受影响的行以查看它是零还是一,如果未插入任何内容(意味着不满足条件)则显示错误。

在我的 SQLite 应用程序中,我保留了一个名为“存储过程”的关联数组,因此 SQL 永远不会出现在应用程序逻辑中。我在数组中按名称查找 SQL 文本,准备并执行它。这样一来,如果 SQL 出现问题,只需查找一个地方。

作为业务规则使用触发器可能出错的示例,请考虑如果同时插入两个用户(一个满足条件而另一个不满足条件)会发生什么情况。您真的要拒绝两行吗? (因为那是将要发生的事情。)那不是一个用例?好吧,它是:您正在编写关于 table 的规则,而不是如果违反规则应用程序将执行的操作。表格可以在应用程序外部更新。

这样想:如果您的规则被违反,您的数据仍然正确。数据库中没有任何不一致的地方。所发生的只是允许用户超出某些限制。您可以随时标记:table 的周期扫描,比赛开始前的检查,等等。 IOW,如果 table 以某种方式通过您精心编写的 INSERT 语句以外的方式进行了更新,您以后总能发现错误,并纠正错误。

聚合函数SUM()必须使用COALESCE()这样当returnsNULL因为没有满足条件的行你得到0 相反,这与第一个子查询的 COUNT(*) 的结果相当:

CREATE TRIGGER check_has_enough_credits_to_monitor_item
BEFORE INSERT ON users_items
BEGIN
   SELECT
     CASE
       WHEN (SELECT COUNT(*) FROM users_items WHERE user_id = NEW.user_id) >=
            (SELECT COALESCE(SUM(number_of_items), 0) FROM credits WHERE user_id = NEW.user_id AND expiration >= datetime('now'))
         THEN RAISE (ABORT, 'No more items allowed')
     END;
END;

参见demo