database - 使用 SQLite 触发器强制显式设置列值

标签 database sqlite triggers

是否可以在 SQLite3 中声明一个触发器,强制用户在 UPDATE 语句中明确提供一个值?

假设我们有一个 Article table :

CREATE TABLE Article (
  Id           INTEGER  PRIMARY KEY,
  Title        TEXT NOT NULL UNIQUE,
  Content      TEXT,
  UserInserted TEXT NOT NULL,
  UserUpdated  TEXT
);

我可以声明以下触发器,该触发器禁止 UserUpdated 列的空值:
CREATE TRIGGER IF NOT EXISTS Trig_Article_BEFORE_UPDATE
  BEFORE UPDATE OF Title, Content ON Article
BEGIN
  SELECT
  CASE
    WHEN new.UserUpdated IS NULL THEN RAISE(ABORT, 'UserUpdated must not be NULL.')
    WHEN length(new.UserUpdated) = 0 THEN RAISE(ABORT, 'UserUpdated must not be NULL.')
  END;
END;

插入按预期工作:
INSERT INTO Article(Title, Content, UserInserted) VALUES('Foo', '', '<user_A>');

在不提供 UserUpdated 的情况下进行更新首先也可以:
UPDATE Article SET Content = 'Bar' WHERE Id = 1;
-- Error: UserUpdated must not be NULL.

UPDATE Article SET Content = 'Bar', UserUpdated = '' WHERE Id = 1;
-- Error: UserUpdated must not be NULL.

但是一旦一个UserUpdated已设置它不再需要显式提供该列。
UPDATE Article SET Content = 'Bar', UserUpdated = '<user_B>' WHERE Id = 1;
UPDATE Article SET Content = 'Foo Bar' WHERE Id = 1;
-- No error

有没有办法声明一个触发器,以便最后一条语句也抛出一个错误?

更新 22.11.2019

感谢 C Perkins' answer我想出了一个使用额外列的解决方案。

一个额外的列CurrentUser已添加到 Article :
CREATE TABLE Article (
  -- ...
  CurrentUser  TEXT
);

一个 BEFORE UPDATE触发器确保设置此列:
CREATE TRIGGER IF NOT EXISTS Trig_Article_BEFORE_UPDATE
  BEFORE UPDATE ON Article
  WHEN old.Title <> new.Title OR
       old.Content <> new.Content OR
       old.CurrentUser <> new.CurrentUser
BEGIN
  SELECT
  CASE
    WHEN new.CurrentUser IS NULL THEN RAISE(ABORT, 'CurrentUser must not be NULL.')
    WHEN length(new.CurrentUser) = 0 THEN RAISE(ABORT, 'CurrentUser must not be NULL.')
  END;
END;

一个 AFTER UPDATE触发器(如果 CurrentUser 不为空)从 CurrentUser 复制值至UserUpdated并清除 CurrentUser再次。
CREATE TRIGGER IF NOT EXISTS Trig_Article_AFTER_UPDATE
  AFTER UPDATE ON Article
  WHEN new.CurrentUser IS NOT NULL
BEGIN
  UPDATE Article SET UserUpdated = new.CurrentUser, CurrentUser = NULL WHERE Id = new.Id;
END;

为了防止直接更新 UserUpdated使用另一个触发器:
CREATE TRIGGER IF NOT EXISTS Trig_Article_UserUpdated_BEFORE_UPDATE
  BEFORE UPDATE ON Article
  WHEN old.UserUpdated <> new.UserUpdated AND
       old.CurrentUser IS NULL
BEGIN
  SELECT RAISE(ABORT, 'You must not UPDATE UserUpdated.');
END;

毕竟我得到了想要的行为。每次ContentTitle已更新列 CurrentUser必须在更新声明和 UserUpdated 中明确提供反射(reflect)最后一个更新值的用户。

最佳答案

利用另一个仅更新列。以下是所涉及内容的简要概述:

  • 在表中添加“仅更新”列:UserUpdateONLY
  • 在 BEFORE UPDATE 触发器上,要求 New.UserUpdateONLY IS NOT NULL AND length(New.UpdateUpdateONLY) != 0New.UserUpdated == Old.UserUpdated OR (New.UserUpdated IS NULL AND Old.UserUpdated IS NULL)避免两列与更新数据发生矛盾(如果任一条件为假,则引发错误)。
  • 在 INSTEAD OF UPDATE 触发器上,将值从仅更新列复制到普通存储列:SET UserUpdateONLY = NULL, UserUpdated = NEW.UserUpdateONLY

  • 唯一可能的问题是,如果在普通列上允许 NULL 更新,那么明显的“触发”值在这种情况下将不起作用。如果这可能是个问题,请将不太可能的值存储为 UserUpdateONLY 列的默认值,例如 '<NOT VALID>'以便始终检测到新的有效。

    关于database - 使用 SQLite 触发器强制显式设置列值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58977783/

    相关文章:

    c# - 检查模型与两个不同物体之间是否同时发生碰撞

    sql - 数据库与纯文本文件 : What are some technical reasons for choosing one over another when performance isn't an issue?

    iphone - 将移动 (iPhone) 应用程序与网络应用程序同步

    sql - 如何组合两个 LEFT JOINS 而不发生交叉?

    c# - 无法使用 C# 在 SQLite 中插入数据

    MySQL触发器与游标和循环更新错误的值

    sql-server - 将 MS SQL 数据库逆向工程为 ERD

    Android-SQLite。一对多关系

    android - Android 正在读取 SQLite 中的 iOS 日期(NSDate)

    php - MySQL中的触发器语句出错