在 PostgreSQL 中有效地合并最近日期的两个数据集

Posted

技术标签:

【中文标题】在 PostgreSQL 中有效地合并最近日期的两个数据集【英文标题】:Merging two data sets on closest date efficiently in PostgreSQL 【发布时间】:2014-11-08 17:47:59 【问题描述】:

我尝试在最近的日期合并两个具有不同时间分辨率的表。

表格是这样的:

表1:

id    | date    | device  | value1
----------------------------------
1     | 10:22   | 13      | 0.53
2     | 10:24   | 13      | 0.67
3     | 10:25   | 14      | 0.83
4     | 10:25   | 13      | 0.32

表2:

id    | date    | device  | value2
----------------------------------
22    | 10:18   | 13      | 0.77
23    | 10:21   | 14      | 0.53
24    | 10:23   | 13      | 0.67
25    | 10:28   | 14      | 0.83
26    | 10:31   | 13      | 0.23

我想将这些表与第一个表合并。所以我想将 value2 附加到 Table1 中,对于每个设备,都会出现最新的 value2。

结果:

id    | date    | device  | value1 | value2
-------------------------------------------
1     | 10:22   | 13      | 0.53   | 0.77
2     | 10:24   | 13      | 0.67   | 0.67
3     | 10:25   | 14      | 0.83   | 0.53
4     | 10:25   | 13      | 0.32   | 0.67

我有一些 (20-30) 设备,在 Table2 (=m) 中有数千行,在 Table1 (=n) 中有数百万行。

我可以按照日期 (O(n*logn)) 对所有表进行排序,将它们写入文本文件并像合并一样遍历 Table1,同时从 Table2 中提取数据直到它更新(我必须管理 ~20-30指向每个设备的最新数据的指针,但仅此而已),合并后我可以将其上传回数据库。那么复杂性是O(n*log(n)) 用于排序和O(n+m) 用于迭代表。

但最好在数据库中完成。但我能达到的最佳查询是 O(n^2) 复杂度:

SELECT DISTINCT ON (Table1.id)
       Table1.id, Table1.date, Table1.device, Table1.value1, Table2.value2
FROM Table1, Table2
WHERE Table1.date > Table2.date and Table1.device = Table2.device
ORDER BY Table1.id, Table1.date-Table2.date;

我需要处理的数据量真的很慢,有没有更好的方法来做到这一点?或者只是用下载的数据做这些事情?

【问题讨论】:

SQL Query to Join Two Tables Based Off Closest Timestamp 的可能重复项 【参考方案1】:

您的查询可以重写为:

SELECT DISTINCT ON (t1.id)
       t1.id, t1.date, t1.device, t1.value1, t2.value2
FROM   table1 t1
JOIN   table2 t2 USING (device)
WHERE  t1.date > t2.date
ORDER  BY t1.id, t2.date DESC;

无需为每个行组合计算日期差异(这很昂贵,而不是sargable),只需从每组中选择具有最大t2.date 的行。索引支持是可取的。 DISTINCT ON的详细信息:

Select first row in each GROUP BY group?

这可能还不够快。鉴于您的数据分布,您将需要一个松散索引扫描,它可以用相关子查询(如 Gordon 的查询)或更现代和通用的JOIN LATERAL 来模拟:

SELECT t1.id, t1.date, t1.device, t1.value1, t2.value2
FROM   table1 t1
LEFT   JOIN LATERAL (
   SELECT value2
   FROM   table2
   WHERE  device = t1.device
   AND    date   < t1.date
   ORDER  BY date DESC
   LIMIT  1
   ) t2 ON TRUE;

LEFT JOIN 避免在t2 中找不到匹配项时丢失行。详情:

Optimize GROUP BY query to retrieve latest row per user

但这仍然不是很快,因为您有“Table2 中有数千行,Table1 中有数百万行”。 p>

两个想法,可能更快,但也更复杂:

1。 UNION ALL加窗口函数

UNION ALL 查询中组合Table1Table2,并在派生表上运行窗口函数。 "moving aggregate support" in Postgres 9.4 或更高版本对此进行了增强。

SELECT id, date, device, value1, value2
FROM  (
   SELECT id, date, device, value1
        , min(value2) OVER (PARTITION BY device, grp) AS value2
   FROM  (
      SELECT *
           , count(value2) OVER (PARTITION BY device ORDER BY date) AS grp
      FROM  (
         SELECT id, date, device, value1, NULL::numeric AS value2 
         FROM   table1

         UNION  ALL
         SELECT id, date, device, NULL::numeric AS value1, value2
         FROM   table2
         ) s1
      ) s2
   ) s3
WHERE  value1 IS NOT NULL
ORDER  BY date, id;

您必须测试它是否可以竞争。足够的work_mem 允许在内存中排序。

dbfiddle here 用于所有三个查询旧 sqlfiddle

2。 PL/pgSQL 函数

Table2 中每个设备的光标,循环 Table1,在前进到 cursor.date &gt; t1.date 后从相应的设备光标中选择值,并保持value2 的前一行。类似于这里的获胜实现:

Window Functions or Common Table Expressions: count previous rows within range

可能最快,但要编写更多代码。

【讨论】:

把它赶出公园的方法!你应该得到最好的答案。【参考方案2】:

由于表 1 小得多,使用相关子查询可能更有效:

select t1.*,
       (select t2.value2
        from table2 t2
        where t2.device = t.device and t2.date <= t1.date
        order by t2.date desc
        limit 1
       ) as value2
from table1 t1;

还要在table2(device, date, value2) 上创建索引以提高性能。

【讨论】:

对不起,我写错了:Table2 较小,Table1 较大,因此 Table2 中的值将多次出现在 Result 表中(如示例中所示)。我相应地编辑了问题。 @hunyadym 。 . .同样的想法应该成立。 table1 的索引应该很容易放入内存中,并且 table2 中的每一行都需要对该索引进行简短扫描。 太好了,我试过了,速度要快得多。谢谢!

以上是关于在 PostgreSQL 中有效地合并最近日期的两个数据集的主要内容,如果未能解决你的问题,请参考以下文章

如何在 PostgreSQL 中合并两个查询?

Postgresql合并年月日月份和日期左侧补零

如何将一列中的两列合并为日期与熊猫?

合并 postgres 数据

如何在 PostgreSQL 中有效地设置减去连接表?

R-基于最近日期合并数据框