繁体   English   中英

在 SQLite 中仅使用 SQL 更新和记录更改的行

[英]Update and log only changed rows with SQL in SQLite

我正在 R 中编写一个更新 SQLite 数据库的应用程序/脚本。

我的道歉 - 我没有这方面的经验。

我的表由 4 个字段IdNameLVLNotes组成:

CREATE TABLE members (
Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
LVL INTEGER NOT NULL,
Notes TEXT
);

INSERT INTO members (Name,LVL,Notes)
VALUES  ('Jean',12,'First stage'),
        ('Jacques',1,'Second stage'),
        ('Amelie',1,'Second stage'),
        ('Louis',13,'Some other note altogether')
;

我想对照另一个表tmp检查它

CREATE TABLE tmp (
Name TEXT NOT NULL,
LVL INTEGER NOT NULL,
Notes TEXT
);

INSERT INTO tmp (Name,LVL,Notes)
VALUES  ('Jean',13,'First stage'),
        ('Jacques',1,'Second stage'),
        ('Amelie',1,'Third stage'),
        ('Louis',14,'Fourth stage')
;

如果 LVL 和/或 Notes 字段发生变化(如 Jean 和 Louis 的 LVL 以及 Amelie 和 Louis 的 Notes),我想在记录以前的值(作为整行)后用新值更新members表和时间戳在member_changes表中。

实现这一目标的最小查询集是什么?

member_changes表的更好设计是什么? 它是否与members相同,但添加了rowID作为主键和timestamp字段? 并且自然 memberID 将允许重复。

非常感谢,

扩展答案的概要

感谢@forpas 的友好回答,我把这个小系统和两个额外的触发器放在一起。 新信息通过tmp表进入。 假定成员名称是唯一的; 可能不需要members.Id的主键。 尽管如此:

-- CREATE members table for current guild members
-- Id is prim key and Name has unique index
CREATE TABLE members (
  Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
  Name TEXT NOT NULL UNIQUE,
  LVL INTEGER NOT NULL,
  Notes TEXT
);
-- SAMPLE DATA
INSERT INTO members (Name,LVL,Notes) VALUES  
        ('Jean',12,'First stage'),
        ('Jacques',1,'Second stage'),
        ('Amelie',1,'Second stage'),
        ('Louis',13,'Some other note altogether');
-- LOG table to see membership changes over time
CREATE TABLE members_changes (
  timestamp TEXT DEFAULT CURRENT_TIMESTAMP,
  Id INTEGER REFERENCES members(Id),
  Name TEXT NOT NULL,
  LVL INTEGER NOT NULL,
  Notes TEXT
);
-- TABLE through which the updates will come in via rvest
-- presumed cannot contain duplicate names
CREATE TABLE tmp (
  Name TEXT NOT NULL UNIQUE,
  LVL INTEGER NOT NULL,
  Notes TEXT
);
-- TRIGGERS (3)
-- (1) UPDATES MEMBERS if insertion in tmp shows changes
-- also LOGS this change in members_changes
CREATE TRIGGER IF NOT EXISTS tr_insert_tmp AFTER INSERT ON tmp
BEGIN
  INSERT INTO members_changes(Id,Name,LVL,Notes)
  SELECT Id,Name,LVL,Notes
  FROM members
  WHERE Name = NEW.NAME AND (LVL IS NOT NEW.LVL OR Notes IS NOT NEW.Notes);
  
  UPDATE members 
  SET LVL = NEW.LVL, Notes = NEW.Notes
  WHERE Name = NEW.Name AND (LVL IS NOT NEW.LVL OR Notes IS NOT NEW.Notes);
END;
-- (2) LOGS DELETIONS from members
CREATE TRIGGER IF NOT EXISTS tr_delete_members BEFORE DELETE ON members
BEGIN
    INSERT INTO members_changes(Id,Name,LVL,Notes)
    SELECT Id,Name,LVL,Notes || " :Deleted"
    FROM members
    WHERE Name = OLD.Name;
END;
-- (3) LOGS INSERTS into members (new members)
CREATE TRIGGER IF NOT EXISTS tr_insert_members AFTER INSERT ON members
BEGIN
    INSERT INTO members_changes(Id,Name,LVL,Notes)
    SELECT Id,Name,LVL,Notes || " :Inserted"
    FROM members
    WHERE Name = NEW.Name;
END;
-- this shows all defined triggers
select * from sqlite_master where type = 'trigger';

-- QUERIES to be run from the script after tmp is updated (b,c,d)
-- ADD NEW MEMBERS
-- it should mostly fail (changes are slow and few)
-- this is logged via tr_insert_members
INSERT OR IGNORE INTO members(Name,LVL,Notes) SELECT Name, LVL, Notes FROM tmp;
-- DELETE OLD MEMBERS 
-- logged via tr_delete_members
DELETE FROM members WHERE Name NOT IN (SELECT Name FROM tmp);
-- EMPTY tmp at the end of the script run
DELETE FROM tmp;

当应用程序运行时,唯一需要调用的查询是:

a)填充tmp的那个(来自rvest收集的dataframe)
b) 查询从tmp添加新成员
c) 查询删除不在tmp中的成员
d) 查询清空tmp

这要归功于@forpas 建议的数据库设置。 我从来没有使用过触发器,最终对它们有所了解。 对记录更改非常有帮助。

members_changes的正确设计是这样的:

CREATE TABLE members_changes (
  timestamp TEXT DEFAULT CURRENT_TIMESTAMP,
  Id INTEGER REFERENCES members(Id),
  Name TEXT NOT NULL,
  LVL INTEGER NOT NULL,
  Notes TEXT
);

timestamp的默认值是当前时间戳。

您需要表tmpAFTER INSERT触发器,以便对于tmp中的每个插入行,成员中的相应行将插入members_changes (如果LVLNotes的任何值不同),之后来自tmp的新行将更新members行:

CREATE TRIGGER IF NOT EXISTS tr_insert_tmp AFTER INSERT ON tmp
BEGIN
  INSERT INTO members_changes(Id,Name,LVL,Notes)
  SELECT Id,Name,LVL,Notes
  FROM members
  WHERE Name = NEW.NAME AND (LVL IS NOT NEW.LVL OR Notes IS NOT NEW.Notes);
  
  UPDATE members 
  SET LVL = NEW.LVL, Notes = NEW.Notes
  WHERE Name = NEW.Name AND (LVL IS NOT NEW.LVL OR Notes IS NOT NEW.Notes);
END; 

请参阅演示

暂无
暂无

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

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