我有一个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
我想删除每个odjectid
和devicename
的最旧条目。在这种情况下,我想删除所有但是:
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
有办法吗?或者是否可以在“临时表”中选择“objectid
和devicename
”的最旧条目?
答案 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