为什么 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 上的触发器,而不是视图上的触发器。
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;
有了这个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 上的触发器,而不是视图上的触发器。
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;