在 SQLite 中仅更新和记录 SQL 更改的行
Update and log only changed rows with SQL in SQLite
我正在用 R 编写 application/script 来更新 SQLite 数据库。
抱歉 - 我没有这方面的经验。
我的table包含4个字段Id
,Name
,LVL
,Notes
:
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')
;
我想对照另一个 table 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 and/or 注释字段发生变化(如 Jean 和 Louis 的 LVL 以及 Amelie 和 Louis 的注释)我想用新的 members
table 更新在我用 member_changes
table.
中的时间戳记录以前的值(作为整行)之后的值
实现此目标的最少查询集是多少?
member_changes
table 更好的设计是什么?它是否与 members
相同,但添加了 rowID
作为主键和 timestamp
字段? memberID 自然会允许重复。
非常感谢,
罗布
扩展答案概要
感谢@forpas 的友好回答,我将这个小系统与 2 个额外的触发器放在一起。新信息来自 tmp
table。假定成员名称是唯一的;可能不需要 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 收集的数据框)
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
的默认值为当前时间戳。
您需要 table tmp
的 AFTER INSERT
触发器,以便对于 tmp
中的每个插入行,来自成员的相应行将插入 members_changes
(如果 LVL
或 Notes
的任何值不同),然后来自 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;
参见demo。
我正在用 R 编写 application/script 来更新 SQLite 数据库。
抱歉 - 我没有这方面的经验。
我的table包含4个字段Id
,Name
,LVL
,Notes
:
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')
;
我想对照另一个 table 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 and/or 注释字段发生变化(如 Jean 和 Louis 的 LVL 以及 Amelie 和 Louis 的注释)我想用新的 members
table 更新在我用 member_changes
table.
实现此目标的最少查询集是多少?
member_changes
table 更好的设计是什么?它是否与 members
相同,但添加了 rowID
作为主键和 timestamp
字段? memberID 自然会允许重复。
非常感谢,
罗布
扩展答案概要
感谢@forpas 的友好回答,我将这个小系统与 2 个额外的触发器放在一起。新信息来自 tmp
table。假定成员名称是唯一的;可能不需要 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 收集的数据框)
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
的默认值为当前时间戳。
您需要 table tmp
的 AFTER INSERT
触发器,以便对于 tmp
中的每个插入行,来自成员的相应行将插入 members_changes
(如果 LVL
或 Notes
的任何值不同),然后来自 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;
参见demo。