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

时间:2014-11-08 17:48:00

标签: sql algorithm postgresql merge greatest-n-per-group

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

表格是这样的:

表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,其中,对于每个设备,最新的值2出现。

结果:

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;

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

2 个答案:

答案 0 :(得分:5)

您的查询可以重写为:

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的详细信息:

但这可能不够快。鉴于您的数据分布,您需要一个松散索引扫描,可以使用相关子查询(如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可以避免丢失行。详细说明:

但那 仍然不是很快 ,因为你有“Table2中的数千行,而Table1中的数百万行”

两个想法

可能更快,但也更复杂。

1。 UNION ALL加窗函数

Table1查询中合并Table2UNION ALL,并在派生表上运行窗口函数。 "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对内存中排序的好处。

所有三个查询都

SQL Fiddle

2。 PL / pgSQL函数

Table2中每个设备的光标,循环遍历Table1,在前进到cursor.date > t1.date之前从相应的设备光标中选择值,并在最后一行之前保留value2 。与此处的获胜实施类似:

可能最快,但写的代码更多。不确定你还有兴趣。

答案 1 :(得分: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)上创建一个索引,以提高效果。