POSTGRES在数百万条记录中选择n条按时间平均分布的行。

问题描述 投票:0回答:1

我有一个表格,其中有以下列 id,filter1,filter2,time,value 其中包含数百万条记录。我想获取 n 在两个时间戳之间平均分布的行。如果时间戳之间的记录数少于 n 我想获取所有的记录。

我目前的查询如下,假设 n=200

SELECT s.* FROM (
    SELECT t.time, t.value,
           ROW_NUMBER() OVER(ORDER BY t.time) as rnk,
           COUNT(*) OVER() as total_cnt
    FROM table_name t
    WHERE t.filter1='filter_value' 
    and t.filter2='another_value' 
    and t.time between '2020-04-18' AND '2020-04-19') s

WHERE MOD(s.rnk,(total_cnt/200)) = 0 ;

我在'filter1,filter2,time'上有一个索引。当有大约1000万条记录时,这个查询还是非常慢。

我也试过 TABLESAMPLE 但我无法为百分比想出一个合适的条件,既要足够快,又要在行数较少时返回所有行。

sql database postgresql scaling query-performance
1个回答
2
投票

如果......之间的记录数为....

  • ... 你没有额外的逻辑或物理数据分布的元信息。
  • ......并且需要在一段时间内平均分配选择。

......那么你的原始查询基本上就好办了。你的索引在 (filter1,filter2,time) 像戈登建议的那样。如果只有不到百分之几的人通过了过滤器,那就很有帮助(很多)。然后,我们必须对所有符合条件的行进行计数和编号(对于许多符合条件的行来说,这是昂贵的部分),以便在样本中得到严格的均匀分布。

几个小建议。

SELECT s.*
FROM  (
   SELECT t.time, t.value
        , row_number() OVER (ORDER BY t.time) AS rn  -- ①
        , count(*) OVER() AS total_cnt
   FROM   table_name t
   WHERE  t.filter1 = 'filter_value' 
   AND    t.filter2 = 'another_value' 
   AND    t.time >= '2020-04-18'  -- assuming data type timestamp!
   AND    t.time <  '2020-04-20'  -- ②
   ) s
WHERE  mod(s.rn, total_cnt/n) = total_cnt/n/2 + 1;  -- ③

①使用列别名 rnrow_number(); rnk 将暗示 rank().

②假设列 "time" 是数据类型 timestamp 由于既不 date 也不 time 会有意义。("时间 "似乎有误导性。)所以这个谓词是 大错特错:

t.time between '2020-04-18' AND '2020-04-19'

给定的日期字段被强制为时间戳。2020-04-18 0:0 2020-04-19 0:0. 由于 BETWEEN 包括下界和上界,该过滤器有效地选择了2020-04-18的全部时间加上2020-04-19的第一个瞬间。这几乎没有任何意义。我建议的修正包括2020-04-18和2020-04-19的所有内容。

如果列 "time" 是数据类型 timestamptz,那么上面的内容也基本适用。另外,你在 timezone 数据库会话的设置进入混合。不要! 请看。

③您的原始状态 MOD(s.rnk,(total_cnt/n)) = 0 挑选每 total_cnt/n-行,总是跳过第一行。total_cnt/n - 1 行,这就形成了一个 后行. 为了说明这一点。

ooooXooooXooooXooooX

我的选择是把选择移到中心,这似乎更合理。

ooXooooXooooXooooXoo

整数除法的结果可能是0,加1(total_cnt/n/2 + 1)防止这种情况发生。再加上无论如何都是在 "中心 "比较多。

最后,应该提到的是,在以下情况下,等值的结果是在 time 是任意的。如果这很重要的话,你可能需要定义一个平局......。

也就是说,我们也许可以使用 任何元信息 关于数据分布对我们有利。或者说,如果我们能放宽对样本严格均匀分布的要求(到什么程度?

在只进行索引扫描的情况下,速度会大大加快

如果我们可以假设 均匀分布 随着时间的推移,对所有(或部分)组合的 (filter1, filter2) 我们只需将时间间隔分割开来,就可以不受制于 n 非常便宜的索引扫描. (或者如果我们不太在意数据的均匀分布,我们可能还是会这么做)。举例说明一下。

WITH input (f1    , f2    , lo                    , hi                    , n) AS (
   VALUES  ('val2', 'val2', timestamp '2020-04-18', timestamp '2020-04-20', 200)
   )
SELECT g.lo, s.*
FROM   (SELECT *, (hi - lo) / n AS span FROM input) i
CROSS  JOIN generate_series(lo, hi - span, span) g(lo)
LEFT   JOIN LATERAL (   
   SELECT t.time, t.value
   FROM   table_name t
   WHERE  t.filter1 = i.f1
   AND    t.filter2 = i.f2
   AND    t.time >= g.lo
   AND    t.time <  g.lo + span
   ORDER  BY time
   LIMIT  1
   ) s ON true;

这只是一个概念证明,可以有一百零一种方法来调整。在这个查询中,有很多事情要做,而且案例的信息不够精简。

主要目的是避免处理所有的行,只取要返回的行。

查询从下界开始,产生类似的选择模式。

XooooXooooXooooXoooo

这个 LEFT JOIN 在结果中保留空的时间片,这表明数据分布不均匀。

任何一种关于表设计、数据分布、写入模式等元信息都可能被用来进一步优化。可能会优化索引:只扫描索引、部分索引、......。


0
投票

对于这个查询。

SELECT s.*
FROM (SELECT t.time, t.value,
             ROW_NUMBER() OVER (ORDER BY t.time) as rnk,
             COUNT(*) OVER () as total_cnt
      FROM table_name t
      WHERE t.filter1 = 'filter_value' AND
            t.filter2 = 'another_value' AND
            t.time between '2020-04-18' AND '2020-04-19'
     ) s
WHERE MOD(s.rnk, (total_cnt / 200)) = 0 ;

你想要一个索引 (filter1, filter2, time). 这应该有助于性能。

© www.soinside.com 2019 - 2024. All rights reserved.