PostgreSQL删除除最旧记录之外的所有记录

时间:2012-10-10 15:00:46

标签: sql postgresql duplicate-removal

我有一个PostgreSQL数据库,在objectid多个devicenames上有多个条目,但每个条目都有一个唯一的timestamp。该表看起来像这样:

address | devicename | objectid      |  timestamp       
--------+------------+---------------+------------------------------
1.1.1.1 | device1    | vs_hub.ch1_25 | 2012-10-02 17:36:41.011629+00
1.1.1.2 | device2    | vs_hub.ch1_25 | 2012-10-02 17:48:01.755559+00
1.1.1.1 | device1    | vs_hub.ch1_25 | 2012-10-03 15:37:09.06065+00
1.1.1.2 | device2    | vs_hub.ch1_25 | 2012-10-03 15:48:33.93128+00
1.1.1.1 | device1    | vs_hub.ch1_25 | 2012-10-05 16:01:59.266779+00
1.1.1.2 | device2    | vs_hub.ch1_25 | 2012-10-05 16:13:46.843113+00
1.1.1.1 | device1    | vs_hub.ch1_25 | 2012-10-06 01:11:45.853361+00
1.1.1.2 | device2    | vs_hub.ch1_25 | 2012-10-06 01:23:21.204324+00

我想删除每个odjectiddevicename的最旧条目。在这种情况下,我想删除所有但是:

1.1.1.1 | device1 | vs_hub.ch1_25 | 2012-10-02 17:36:41.011629+00
1.1.1.2 | device2 | vs_hub.ch1_25 | 2012-10-02 17:48:01.755559+00

有办法吗?或者是否可以在“临时表”中选择“objectiddevicename”的最旧条目?

5 个答案:

答案 0 :(得分:4)

这应该这样做:

delete from devices
using (
   select ctid as cid, 
          row_number() over (partition by devicename, objectid order by timestamp asc) as rn
   from devices
) newest
where newest.cid = devices.ctid
and newest.rn <> 1;

它创建一个派生表,为每个(address,devicename,objectid)组合分配唯一的数字,给出最早的一个(timestamp值最小的那个)数字1.然后使用此结果删除所有没有数字1的那些。虚拟列ctid用于唯一标识这些行(它是Postgres提供的内部标识符)。

请注意,要删除非常多的行,Erwin的方法肯定会更快。

SQLFiddle演示:http://www.sqlfiddle.com/#!1/5d9fe/2

答案 1 :(得分:3)

为了提炼所描述的结果,这可能是最简单和最快的:

SELECT DISTINCT ON (devicename, objectid) *
FROM   tbl
ORDER  BY devicename, objectid, ts DESC;

详情及解释in this related answer

根据您的示例数据,我得出结论,您将要删除原始表的大部分。只需TRUNCATE表格(或DROP并重新创建)可能会更快,因为您应该添加代理pk列,并将剩余的行写入其中。这也将为您提供一个prestine表,以最适合您的查询的方式隐式聚类(排序),并保存VACUUM必须要做的工作。整体而言,它可能仍然更快:

我还强烈建议在您的表格中添加代理主键,最好是serial列。

BEGIN;

CREATE TEMP TABLE tmp_tbl ON COMMIT DROP AS
SELECT DISTINCT ON (devicename, objectid) *
FROM   tbl
ORDER  BY devicename, objectid, ts DESC;

TRUNCATE tbl;
ALTER TABLE tbl ADD column tbl_id serial PRIMARY KEY;

-- or, if you can afford to drop & recreate:
-- DROP TABLE tbl;
-- CREATE TABLE tbl (
--   tbl_id serial PRIMARY KEY
-- , address text
-- , devicename text
-- , objectid text
-- , ts timestamp);

INSERT INTO tbl (address, devicename, objectid, ts)
SELECT address, devicename, objectid, ts
FROM   tmp_tbl;

COMMIT;

在交易中完成所有操作,以确保您不会在中途失败。

只要temp_buffers的设置足以容纳临时表,这就很快。否则系统将开始将数据交换到磁盘,性能会下降。您可以为此当前会话设置temp_buffers,如下所示:

SET temp_buffers = 1000MB;

因此,您不会浪费temp_buffers通常不需要的RAM。必须在会话中第一次使用临时对象之前。有关详情,请参见this related answer

此外,由于INSERT跟随交易中的TRUNCATE,因此Write Ahead Log会很容易提高效果。

考虑CREATE TABLE AS替代路线:

唯一的缺点:您需要在桌面上使用独占锁。在具有大量并发负载的数据库中,这可能是一个问题。

最后,永远不要使用timestamp作为列名。它是每个SQL标准中的reserved word和PostgreSQL中的类型名称。我可能已经注意到,我将列重命名为ts

答案 2 :(得分:0)

DELETE FROM DEVICES D WHERE.timestamp =(SELECT min(timestamp)FROM DEVICES WHERE objectid = d.objectid and device = d.device)

答案 3 :(得分:0)

假设address, devicename and objectid构成唯一标识符

,这应该可行
DELETE FROM tablename 
WHERE 
  address || devicename || objectid || timestamp NOT IN 
  (SELECT 
     address || devicename || objectid || min(timestamp) 
   FROM tablename 
   GROUP BY address, devicename, objectid)

这使用一个由唯一列组成的连接字符串,将选择绑定在一起。可以找到该唯一组合的最小日期,下一个将从表中删除这些记录。可能不是最有效的,但它应该有效。

答案 4 :(得分:0)

我的建议是使用子查询,检查是否存在具有较旧时间戳的记录:

DELETE FROM tablename
WHERE EXISTS(
  SELECT * FROM tablename a
  WHERE tablenmae.address = a.address
    AND tablename.devicename = a.devicename
    AND tablename.objectid = a.objectid
    AND a.timestamp < tablename.timestamp
)

选择最旧记录的查询将如下所示:

SELECT address, devicename, objectid, MIN(timestamp)
FROM tablename
GROUP BY address, devicename, objectid