我有一个表格,其中有以下列 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
但我无法为百分比想出一个合适的条件,既要足够快,又要在行数较少时返回所有行。
如果......之间的记录数为....
......那么你的原始查询基本上就好办了。你的索引在 (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; -- ③
①使用列别名 rn
为 row_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
在结果中保留空的时间片,这表明数据分布不均匀。
任何一种关于表设计、数据分布、写入模式等元信息都可能被用来进一步优化。可能会优化索引:只扫描索引、部分索引、......。
对于这个查询。
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)
. 这应该有助于性能。