7

我有一个 PostgreSQL 数据库,它有多个 , 多个条目objectiddevicenamestimestamp每个条目都有一个唯一的。该表如下所示:

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

我想删除除每个odjectidand的最旧条目之外的所有条目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

有没有办法做到这一点?或者是否可以将“objectiddevicename”的最旧条目选择到临时表中?

4

5 回答 5

7

这应该这样做:

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;

它创建一个派生表,该表将为(地址、设备名称、对象ID)的每个组合分配唯一编号,将最早的(具有最小值的timestamp那个)编号为1。然后使用此结果删除所有没有的数字 1。虚拟列ctid用于唯一标识这些行(它是 Postgres 提供的内部标识符)。

请注意,对于删除大量行,Erwin 的方法肯定会更快。

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

于 2012-10-10T18:19:24.687 回答
4

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

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

此相关答案中的详细信息和解释。

从您的示例数据中,我得出结论,您将删除原始表的大部分TRUNCATE仅表(或DROP& 重新创建,因为无论如何您都应该添加一个代理 pk 列)并将剩余的行写入它可能会更快。这也为您提供了一个原始表,以最适合您的查询的方式隐式聚集(排序),并节省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;

因此,您不会浪费通常不需要的 RAM temp_buffers。必须在会话中首次使用任何临时对象之前设置。此相关答案中的更多信息。

此外,由于在INSERT事务内部, Write Ahead LogTRUNCATE将很容易- 提高性能。

考虑CREATE TABLE AS替代路线:

唯一的缺点:您需要在表上使用排他锁。这在并发负载很重的数据库中可能是个问题。

最后,永远不要timestamp用作列名。它是每个 SQL 标准中的保留字,也是 PostgreSQL 中的类型名称。我ts改用了。

于 2012-10-10T16:36:21.320 回答
0

从设备 D 中删除 d.timestamp = (SELECT min(timestamp) 从设备中 objectid = d.objectid 和 device = d.device)

于 2012-10-10T15:09:08.183 回答
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)

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

于 2012-10-10T15:30:27.533 回答
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
于 2012-10-10T15:42:56.020 回答