SQL - 仅保留每天的第一个和最后一个记录

时间:2015-01-12 15:46:51

标签: mysql sql greatest-n-per-group

我有一个存储简单日志数据的表:

CREATE TABLE chronicle (
    id INT auto_increment PRIMARY KEY, 
    data1 VARCHAR(256),
    data2 VARCHAR(256),
    time DATETIME
);

该表接近1m记录,因此我想开始整合数据。

我希望每天能够记录每个DISTINCT(data1, data2)的第一个和最后一个记录,并删除所有其余记录。

我知道如何只使用我想要的任何语言提取数据并处理它,然后使用巨大的IN (...) query删除记录,但似乎更好的选择是直接使用SQL(我错了吗? )

我已经尝试了几个查询,但是我对JOIN之外的SQL不太满意。

这是我到目前为止所做的:

SELECT id, Max(time), Min(time)
FROM   (SELECT id, data1 ,data2, time, Cast(time AS DATE) AS day
        FROM chronicle) AS initial
GROUP BY day;

这让我每天都是第一次也是最后一次,但它没有被数据分开(即我得到每天的最后一条记录,而不是每天每组不同数据的最后一条记录。)另外,id仅适用于Min(时间)。

我在这个特定问题上找到的信息仅用于查找当天的最后一条记录,而不是每组数据的最后记录。

重要提示:我想要每天DISTINCT(data1, data2)的第一个/最后一个记录,而不仅仅是表格中每天的第一个/最后一个记录。每天将有超过2条记录。

解决方案: 我的解决方案归功于Jonathan Dahan和Gordon Linoff:

SELECT o.data1, o.data2, o.time FROM chronicle AS o JOIN (
    SELECT Min(id) as id FROM chronicle GROUP BY DATE(time), data1, data2
    UNION SELECT Max(id) as id FROM test_chronicle GROUP BY DATE(time), data1. data2
) AS n ON o.id = n.id;

从这里开始,引用同一个表来删除行就很简单了。

2 个答案:

答案 0 :(得分:1)

你有正确的想法。您只需加入即可获取原始信息。

SELECT c.*
FROM chronicle c JOIN
     (SELECT date(time) as day, min(time) as mint, max(time) as maxt
      FROM chronicle
      GROUP BY date(time)
     ) cc
     ON c.time IN (cc.mint, cc.maxt);

请注意,join条件不需要明确包含day,因为它是time的一部分。当然,如果您愿意,可以添加date(c.time) = cc.day

我建议您创建一个新表,而不是删除原始表中的行。有点谎言:

create table ChronicleByDay like chronicle;

insert into ChronicleByDay
    SELECT c.*
    FROM chronicle c JOIN
         (SELECT date(time) as day, min(time) as mint, max(time) as maxt
          FROM chronicle
          GROUP BY date(time)
         ) cc
         ON c.time IN (cc.mint, cc.maxt);

这样,如果您需要,可以获得更详细的信息。

答案 1 :(得分:1)

这会在日期搜索时提高效果。

ALTER TABLE chronicle
ADD INDEX `ix_chronicle_time` (`time` ASC);

这将删除记录:

CREATE TEMPORARY TABLE #tmp_ids (
  `id` INT NOT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO #tmp_ids (id)
SELECT
    min(id)
FROM
    chronicle
GROUP BY
    CAST(day as DATE),
    data1,
    data2
UNION
SELECT
    Max(id)
FROM
    chronicle
GROUP BY
    CAST(day as DATE),
    data1,
    data2;

DELETE FROM
    chronicle
WHERE
    ID not in (select id FROM #tmp_ids)
    AND date <= '2015-01-01'; -- if you want to consider all dates, then remove this condition