【问题标题】:More efficient SQLite trigger to "rollup" multiple rows更高效的 SQLite 触发器“汇总”多行
【发布时间】:2013-12-04 03:41:56
【问题描述】:

我正在从我的路由器收集传输数据;它提供每日、每月和每两分钟(间隔 120 秒)的摘要。如果我在一天中(因此在月中)重新启动路由器,这些报告将不完整。但是,我仍然有区间数据,并且可以汇总启动前后的记录。

以前,我在更新三个单独的表后使用脚本执行了此汇总操作。这很慢,因为我必须查询给定日期间隔的总和,然后是月份间隔。更新间隔时执行汇总会更快。所以我把一个触发器放在一起。问题是我编写触发器的方式,它更新每个间隔插入的每日和每月行。理想情况下,每个事务只发生一次,并处理刚刚添加的行。

所以,下面是一个包含的示例,显示了我所拥有的。计数列只是为了说明汇总发生的次数超出了它的需要。有没有办法简化这一点?

BEGIN TRANSACTION;

CREATE TABLE monthly  (count INTEGER DEFAULT 0, date DATE NOT NULL, interface TEXT NOT NULL, upload INTEGER DEFAULT 0, download INTEGER DEFAULT 0, rollup_upload INTEGER DEFAULT 0, rollup_download INTEGER DEFAULT 0, PRIMARY KEY (date, interface));
CREATE TABLE daily    (count INTEGER DEFAULT 0, date DATE NOT NULL, interface TEXT NOT NULL, upload INTEGER DEFAULT 0, download INTEGER DEFAULT 0, rollup_upload INTEGER DEFAULT 0, rollup_download INTEGER DEFAULT 0, PRIMARY KEY (date, interface));
CREATE TABLE interval (count INTEGER DEFAULT 0, date DATE NOT NULL, interface TEXT NOT NULL, upload INTEGER DEFAULT 0, download INTEGER DEFAULT 0, interval INTEGER, PRIMARY KEY (date, interface));

CREATE TRIGGER rollup_interval_trigger AFTER INSERT ON interval
BEGIN

INSERT OR REPLACE INTO daily (count, date, interface, upload, download, rollup_upload, rollup_download)
SELECT
    COALESCE((SELECT count FROM daily WHERE date IS strftime('%Y-%m-%d', NEW.date, 'localtime') AND interface IS 'wan'),0)+1,
    strftime('%Y-%m-%d', NEW.date, 'localtime'),
    'wan',
    COALESCE((SELECT upload FROM daily WHERE date IS strftime('%Y-%m-%d', NEW.date, 'localtime') AND interface IS 'wan'),0),
    COALESCE((SELECT download FROM daily WHERE date IS strftime('%Y-%m-%d', NEW.date, 'localtime') AND interface IS 'wan'),0),
    sum(upload) as rollup_upload,
    sum(download) as rollup_download
FROM interval
WHERE strftime('%Y-%m-%d', date, 'localtime') = strftime('%Y-%m-%d', NEW.date, 'localtime') AND interface IS 'vlan2';

INSERT OR REPLACE INTO monthly (count, date, interface, upload, download, rollup_upload, rollup_download)
SELECT
    COALESCE((SELECT count FROM monthly WHERE date IS strftime('%Y-%m-01', NEW.date, 'localtime') AND interface IS 'wan'),0)+1,
    strftime('%Y-%m-01', NEW.date, 'localtime'),
    'wan',
    COALESCE((SELECT upload FROM monthly WHERE date IS strftime('%Y-%m-01', NEW.date, 'localtime') AND interface IS 'wan'),0),
    COALESCE((SELECT download FROM monthly WHERE date IS strftime('%Y-%m-01', NEW.date, 'localtime') AND interface IS 'wan'),0),
    sum(upload) as rollup_upload,
    sum(download) as rollup_download
FROM interval
WHERE strftime('%Y-%m', date, 'localtime') = strftime('%Y-%m', NEW.date, 'localtime') AND interface IS 'vlan2';

END;

COMMIT;

insert into daily (date, interface, download, upload) values ('2012-10-02', 'wan', 10, 20);
insert into monthly (date, interface, download, upload) values ('2012-10-01', 'wan', 30, 40);

.headers ON

select * from daily;
select * from monthly;

begin transaction;
insert into interval (date, interval, download, upload, interface) values ('2012-10-02 11:00:00', 120, 10, 20, 'vlan2');
insert into interval (date, interval, download, upload, interface) values ('2012-10-02 12:00:00', 120, 10, 20, 'vlan2');
insert into interval (date, interval, download, upload, interface) values ('2012-10-02 13:00:00', 120, 10, 20, 'vlan2');
insert into interval (date, interval, download, upload, interface) values ('2012-10-02 14:00:00', 120, 10, 20, 'vlan2');
insert into interval (date, interval, download, upload, interface) values ('2012-10-01 12:00:00', 120, 10, 20, 'vlan2');
insert into interval (date, interval, download, upload, interface) values ('2012-10-03 12:00:00', 120, 10, 20, 'vlan2');
commit;

select * from interval;

select * from daily;
select * from monthly;

【问题讨论】:

    标签: sql sqlite triggers


    【解决方案1】:

    仅通过插入间隔的新数字来增加每日和每月的计数怎么样?然后你不会在每个插入上聚合。您所拥有的只是按主键两次查找一行并更新该行。

    CREATE TRIGGER rollup_interval_trigger AFTER INSERT ON interval
    BEGIN
        INSERT INTO daily 
        SELECT 0, strftime('%Y-%m-%d', NEW.date, 'localtime'), 'wan',0,0,0,0 
        WHERE NOT EXISTS (
          SELECT 1 
          FROM daily 
          WHERE date = strftime('%Y-%m-%d', NEW.date, 'localtime') and interface = 'wan');
    
        UPDATE daily 
        SET count = count + 1,
            rollup_upload = rollup_upload + new.upload ,
            rollup_download = rollup_download + new.download 
        WHERE date = strftime('%Y-%m-%d', NEW.date, 'localtime') and interface = 'wan';
    
        -- and similarly for table monthly
    END;
    

    由于 sqlite 没有“upsert”语句,因此您需要有一个单独的语句来创建每日/每月记录,如果它们在更新之前还没有。

    这给出了与您的测试数据触发器相同的结果。

    附言

    如果需要,您还可以通过 INSERT 或 IGNORE 替换 INSERT ... WHERE NOT EXISTS 以获得最佳性能(我不太喜欢它,因为它捕获 所有 冲突,而不仅仅是唯一的约束,并且通常可能隐藏其他错误)。

    【讨论】:

    • 好的,所以不要求和,只需增加汇总值。我必须对此进行测试才能对性能有所了解,但我喜欢它。如果我插入,然后删除并插入相同的间隔,它确实具有潜在的过度计数的缺点。这是一个边缘情况,不太可能,并且可以由删除触发器处理。我还可以有一个更新触发器来执行我开始时使用的汇总。
    • 关于 UPSERT,这就是我的 INSERT OR REPLACE 应该处理的。这与您建议的 INSERT WHERE NOT EXISTS / UPDATE 或 INSERT OR IGNORE 之间有区别吗?
    • 我认为 UPSERT (INSERT WHEN NOT EXISTS / UPDATE) 与 INSERT OR REPLACE 是正确的,因为 INSERT OR REPLACE 需要指定所有字段,因此需要指定额外的查询,而您的建议保持不变仅数据?
    • 哈,我想最后一个问题。 IGNORE 会捕获哪些其他冲突?类型不对?失效日期?无效的列?我也可能会考虑这个选项。具体来说,像 ABORT 之类的东西,我认为它会撤销当前的语句,但让其余的事务发生。我认为这也会报告错误,因此我知道要重新考虑间隔中的哪些行。谢谢。
    • 呸,我不想插入或中止,因为那不会进入更新。我将不得不调查可能的冲突案例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-08
    • 1970-01-01
    • 1970-01-01
    • 2021-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多