我有一个已经增长到数百兆字节的数据库(用于跟踪电子邮件统计信息),我一直在寻找减少它的方法。
似乎文件大小的主要原因是相同的字符串往往会在数千行中重复出现。为了避免这个问题,我计划为字符串池创建另一个表,如下所示:
CREATE TABLE AddressLookup (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Address TEXT UNIQUE
);
CREATE TABLE EmailInfo (
MessageID INTEGER PRIMARY KEY AUTOINCREMENT,
ToAddrRef INTEGER REFERENCES AddressLookup(ID),
FromAddrRef INTEGER REFERENCES AddressLookup(ID)
/* Additional columns omitted for brevity. */
);
为方便起见,加入这些表的视图:
CREATE VIEW EmailView AS
SELECT
MessageID,
A1.Address AS ToAddr,
A2.Address AS FromAddr
FROM EmailInfo
LEFT JOIN AddressLookup A1 ON (ToAddrRef = A1.ID)
LEFT JOIN AddressLookup A2 ON (FromAddrRef = A2.ID);
为了能够像使用普通表一样使用这个视图,我做了一些触发器:
CREATE TRIGGER trg_id_EmailView
INSTEAD OF DELETE ON EmailView
BEGIN
DELETE FROM EmailInfo WHERE MessageID = OLD.MessageID;
END;
CREATE TRIGGER trg_ii_EmailView
INSTEAD OF INSERT ON EmailView
BEGIN
INSERT OR IGNORE INTO AddressLookup(Address) VALUES (NEW.ToAddr);
INSERT OR IGNORE INTO AddressLookup(Address) VALUES (NEW.FromAddr);
INSERT INTO EmailInfo
SELECT NEW.MessageID, A1.ID, A2.ID
FROM AddressLookup A1, AddressLookup A2
WHERE A1.Address = NEW.ToAddr AND A2.Address = NEW.FromAddr;
END;
CREATE TRIGGER trg_iu_EmailView
INSTEAD OF UPDATE ON EmailView
BEGIN
UPDATE EmailInfo SET MessageID = NEW.MessageID
WHERE MessageID = OLD.MessageID;
REPLACE INTO EmailView
SELECT NEW.MessageID, NEW.ToAddr, NEW.FromAddr;
END;
问题
后:
INSERT OR REPLACE INTO EmailView VALUES (1, 'alice@example.com', 'bob@example.com');
INSERT OR REPLACE INTO EmailView VALUES (2, 'alice@example.com', 'chad@example.com');
更新的行包含:
MessageID ToAddr FromAddr
--------- ------ --------
1 NULL bob@example.com
2 alice@example.com chad@example.com
有一个不应该存在的NULL。 EmailInfo
表中的相应单元格包含孤立的ToAddrRef
值。
如果您一次执行一个INSERT,您会看到AddressLookup
表中的Alice的ID更改!
此行为似乎是documented:
可以将ON CONFLICT子句指定为触发器主体内的UPDATE或INSERT操作的一部分。但是,如果将ON CONFLICT子句指定为导致触发器触发的语句的一部分,则使用外部语句的冲突处理策略。
因此,顶级“INSERT OR REPLACE”语句中的“REPLACE”将覆盖触发器程序中的关键“INSERT OR IGNORE”。
有没有办法让它按照我想要的方式运作?
答案 0 :(得分:3)
好吧,既然SQLite 是开源的,我只需要改变codeTriggerProgram
函数来处理ON CONFLICT
它应该的方式。
答案 1 :(得分:1)
我遇到了类似的问题,我想我找到了一种方法来通过将OR REPLACE
从外部语句移动到表本身来欺骗sqlite做正确的事情:
CREATE TABLE a (id INTEGER PRIMARY KEY ON CONFLICT REPLACE, dataA TEXT);
CREATE TABLE b (id INTEGER PRIMARY KEY, dataB TEXT);
CREATE TRIGGER auto_add AFTER INSERT ON a
BEGIN INSERT OR IGNORE INTO b (id) VALUES (NEW.id); END;
当您INSERT OR REPLACE
进入表格“a”时,您总是替换添加到表格b的行,因为触发器现在使用OR REPLACE
。
但是如果你只是做一个INSERT INTO
它似乎有效,因为on冲突处理不再是外部插入的一部分,而是表本身的一部分。