为什么 SQLite 在 table 上给出 Cannot create 而不是 trigger

Why does SQLite give Cannot create instead of trigger on table

有了这个table

create table FOLDER
(
    _ID integer primary key autoincrement,
    NAME text not null,
    PARENT integer not null,
    DELETED integer,
    constraint VALUEOF_folder_deleted check (DELETED == 1 or DELETED isnull) on conflict abort,
    unique (NAME, PARENT) on conflict abort
);

我想用一个将 DELETED 字段设置为空的替换来替换存在 NAME PARENT 组合且 DELETED 设置为 1 的插入。

我试过这个触发器:

CREATE TRIGGER REPLACE_INS_folder instead of insert on FOLDER
when exists (select 1 from FOLDER where NAME == new.NAME and PARENT == new.PARENT and DELETED == 1)
begin
    update FOLDER set DELETED = null where NAME == new.NAME and PARENT == new.PARENT;
end;

但收到: Error: cannot create INSTEAD OF trigger on table: FOLDER

最初我在触发器中遇到语法错误,但收到了同样的错误,这表明发生了一些更严格的限制。本文档中的图表 https://www.sqlite.org/lang_createtrigger.html 表明我的触发器是有效的。但是,文本的第二部分 A trigger may be specified to fire whenever a DELETE, INSERT, or UPDATE of a particular database table occurs, or whenever an UPDATE occurs on on one or more specified columns of a table. 让我想知道是否只允许更新触发器使用 WHEN。

请注意,这是 table 上的触发器,而不是视图上的触发器。

documentation 说:

Triggers may be created on views, as well as ordinary tables, by specifying INSTEAD OF in the CREATE TRIGGER statement.

这是相当具有误导性的。这句话其实想表达的是

  • 在正常的 table 中,您只能使用 BEFORE 和 AFTER 触发器,但是
  • 在视图中,您只能使用 INSTEAD OF 触发器。

您不能在 table 上使用 INSTEAD OF 触发器。改成视图:

CREATE TABLE FolderTable(...);
CREATE VIEW Folder AS SELECT * FROM FolderTable;
CREATE TRIGGER ... INSTEAD OF INSERT ON Folder ...;

(您还需要 INSTEAD OF UPDATE/DELETE 触发器。)

试试这个:

CREATE TRIGGER replace_ins_folder
BEFORE INSERT ON folder
WHEN EXISTS (SELECT 1 FROM folder
             WHERE name == new.name
               AND parent == new.parent
               AND deleted == 1)
BEGIN
    UPDATE folder
    SET deleted = NULL
    WHERE name == new.name AND parent == new.parent;

    SELECT RAISE(IGNORE);
END;

从 SQLite 3.24.0 (2018-06-04) 开始,请求的功能被优雅地封装在 UPSERT 方案中 INSERT..ON CONFLICT: https://www.sqlite.org/lang_UPSERT.html

但是,在我的例子中,"ON CONFLICT" 子句不被我用来编写数据库客户端的编程语言识别。

所以我已经实施了 Artem Odnovolov 的建议的修改版本。它有效,但它需要对我的方案进行一些调整,该方案比 Steve Waring 发布的方案更通用。通用版本为:

  • 使用 "INSERT OR IGNORE" 而不是 "INSERT" 在 table 中插入新行。这取代了 SELECT RAISE(IGNORE) 语句。 IE。不会忽略后续触发器。
  • 用 "IS" 替换“==”允许比较可能为 NULL 的参数。
  • 根据 NEW.deleted 检查 OLD.deleted 的值允许更一般的 UPSERT 行为。

仍然以 Steve Waring 的 table 为例,table 创建代码将如下所示:

CREATE TABLE folder(id      integer PRIMARY KEY,
                    name    text,
                    parent  integer,
                    deleted integer,
                    UNIQUE(name, parent)
                   );

以及 BEFORE INSERT 触发器:

    CREATE TRIGGER replace_ins_folder
    BEFORE INSERT ON folder

    WHEN EXISTS (SELECT 1 FROM folder       --check for violation of UNIQUE constraint
                 WHERE name IS NEW.name
                   AND parent IS NEW.parent
                )
    BEGIN
        UPDATE folder
        SET deleted = NEW.deleted
        WHERE name IS NEW.name 
          AND parent IS NEW.parent 
          AND deleted IS NOT NEW.deleted; -- only do update when there is something to change
                                          -- otherwise subsequent ON UPDATE triggers may fire when not intended
    END;