我正在尝试编写一个存储过程,用于从一个大表中及时选择X个扩散点数量。
我有一张表points
:
"Userid" integer
, "Time" timestamp with time zone
, "Value" integer
它包含数亿条记录。每个用户大约有一百万条记录。
我想选择X点(比方说50),这些点从时间A到时间B都很好地扩散。问题是点数不均匀分布(如果一个点在6:00:00,下一个点例如,可以在15秒,20或4分钟后点。
选择身份证的所有积分最多可能需要60秒(因为有大约一百万点)。
有没有办法以快速的方式尽可能多地选择我想要的确切点数?
示例数据:
+--------+---------------------+-------+
| UserId | Time | Value |
+--------+---------------------+-------+
1 | 1 | 2017-04-10 14:00:00 | 1 |
2 | 1 | 2017-04-10 14:00:10 | 10 |
3 | 1 | 2017-04-10 14:00:20 | 32 |
4 | 1 | 2017-04-10 14:00:35 | 80 |
5 | 1 | 2017-04-10 14:00:58 | 101 |
6 | 1 | 2017-04-10 14:01:00 | 203 |
7 | 1 | 2017-04-10 14:01:30 | 204 |
8 | 1 | 2017-04-10 14:01:40 | 205 |
9 | 1 | 2017-04-10 14:02:02 | 32 |
10 | 1 | 2017-04-10 14:02:15 | 7 |
11 | 1 | 2017-04-10 14:02:30 | 900 |
12 | 1 | 2017-04-10 14:02:45 | 22 |
13 | 1 | 2017-04-10 14:03:00 | 34 |
14 | 1 | 2017-04-10 14:03:30 | 54 |
15 | 1 | 2017-04-10 14:04:00 | 54 |
16 | 1 | 2017-04-10 14:06:00 | 60 |
17 | 1 | 2017-04-10 14:07:20 | 654 |
18 | 1 | 2017-04-10 14:07:40 | 32 |
19 | 1 | 2017-04-10 14:08:00 | 33 |
20 | 1 | 2017-04-10 14:08:12 | 32 |
21 | 1 | 2017-04-10 14:10:00 | 8 |
+--------+---------------------+-------+
对于ID为1的用户,我想从上面的列表中选择11个“最佳”点, 从时间2017-04-10 14:00:00到2017-04-10 14:10:00。
目前,在为用户选择所有点后,在服务器上完成了它。 我通过除以时间差来计算“最佳时间”并获得如下列表:14:00:00,14:01:00,...... 14:10:00(11“最佳时间”,作为分数)。比我找到每个“最佳时间”的最近点,还没有被选中。 结果将是分数:1,6,9,13,15,16,17,18,19,20,21
我正在尝试这样的事情:
SELECT * FROM "points"
WHERE "Userid" = 1 AND
(("Time" =
(SELECT "Time" FROM
"points"
ORDER BY abs(extract(epoch from '2017-04-10 14:00:00' - "Time"))
LIMIT 1)) OR
("Time" =
(SELECT "Time" FROM
"points"
ORDER BY abs(extract(epoch from '2017-04-10 14:01:00' - "Time"))
LIMIT 1)) OR
("Time" =
(SELECT "Time" FROM
"points"
ORDER BY abs(extract(epoch from '2017-04-10 14:02:00' - "Time"))
LIMIT 1)))
这里的问题是:
A)不考虑已经选择的点数
B)由于ORDER BY
,每增加一次会使查询的运行时间增加约1秒,而对于50分,我会回到1分钟。
答案 0 :(得分:0)
回答使用generate_series('2017-04-10 14:00:00','2017-04-10 14:10:00','1 minute'::interval)
和join
进行比较。
让其他人节省数据集的时间:
t=# create table points(i int,"UserId" int,"Time" timestamp(0), "Value" int,b text);
CREATE TABLE
Time: 13.728 ms
t=# copy points from stdin delimiter '|';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 1 | 1 | 2017-04-10 14:00:00 | 1 |
>> 2 | 1 | 2017-04-10 14:00:10 | 10 |
3 | 1 | 2017-04-10 14:00:20 | 32 |
4 | 1 | 2017-04-10 14:00:35 | 80 |
5 | 1 | 2017-04-10 14:00:58 | 101 |
6 | 1 | 2017-04-10 14:01:00 | 203 |
7 | 1 | 2017-04-10 14:01:30 | >> 204 |
8 | 1 | 2017-04-10 14:01:40 | 205 |
9 | 1 | 2017-04-10 14:02:02 | 32 |
10 | 1 | 2017-04-10 14:02:15 | 7 |
11 | 1 | 2017-04-10 14:02:30 | 900 |
12 | 1 | 2017-04-10 14:02:45 | 22 |
>> >> >> >> >> >> >> >> >> >> 13 | 1 | 2017-04-10 14:03:00 | 34 |
14 | 1 | 2017-04-10 14:03:30 | 54 |
15 | 1 | 2017-04-10 14:04:00 | 54 |
16 | 1 | 2017-04-10 14:06:00 | 60 |
17 | 1 | 2017-04-10 14:07:20 | 654 |
18 | 1 | 2017-04-10 14:07:40 | 32 |
19 | 1 | 2017-04-10 14:08:00 | 33 |
20 | 1 | 2017-04-10 14:08:12 | 32 |
21 | 1 | 2017-04-10 14:10:00 | 8 |>> >> >> >> >> >> >> >> \.
>> \.
COPY 21
Time: 7684.259 ms
t=# alter table points rename column "UserId" to "Userid";
ALTER TABLE
Time: 1.013 ms
坦白说我不明白这个要求。这就是我从描述中得到的结果,结果与OP的预期结果不同:
t=# with r as (
with g as (
select generate_series('2017-04-10 14:00:00','2017-04-10 14:10:00','1 minute'::interval) s
)
select *,abs(extract(epoch from '2017-04-10 14:02:00' - "Time"))
from g
join points on g.s = date_trunc('minute',"Time")
order by abs
limit 11
)
select i, "Time","Value",abs
from r
order by i;
i | Time | Value | abs
----+---------------------+-------+-----
4 | 2017-04-10 14:00:35 | 80 | 85
5 | 2017-04-10 14:00:58 | 101 | 62
6 | 2017-04-10 14:01:00 | 203 | 60
7 | 2017-04-10 14:01:30 | 204 | 30
8 | 2017-04-10 14:01:40 | 205 | 20
9 | 2017-04-10 14:02:02 | 32 | 2
10 | 2017-04-10 14:02:15 | 7 | 15
11 | 2017-04-10 14:02:30 | 900 | 30
12 | 2017-04-10 14:02:45 | 22 | 45
13 | 2017-04-10 14:03:00 | 34 | 60
14 | 2017-04-10 14:03:30 | 54 | 90
(11 rows)
我添加了abs列来证明为什么我认为这些行符合要求更好
答案 1 :(得分:0)
您的问题背后存在一个仅使用SQL难以解决的优化问题。
也就是说,您的近似尝试可以实现使用索引,并且无论表大小如何都表现出良好的性能。如果你还没有这个索引,你需要这个索引:
CREATE INDEX ON points ("Userid", "Time");
查询:
SELECT *
FROM generate_series(timestamptz '2017-04-10 14:00:00+0'
, timestamptz '2017-04-10 14:09:00+0' -- 1 min *before* end!
, interval '1 minute') grid(t)
LEFT JOIN LATERAL (
SELECT *
FROM points
WHERE "Userid" = 1
AND "Time" >= grid.t
AND "Time" < grid.t + interval '1 minute' -- same interval
ORDER BY "Time"
LIMIT 1
) t ON true;
dbfiddle here
最重要的是,重写的查询可以使用上面的索引,并且 非常快 ,解决问题 B)。
它还在某种程度上解决了问题 A),因为没有多次返回任何点。如果网格中的两个相邻点之间没有行,则结果中不会显示任何行。在这种情况下,使用LEFT JOIN .. ON true
会保留所有网格行并附加NULL。通过切换到CROSS JOIN
来消除那些NULL行。您可以通过这种方式获得更少的结果行。
我只搜索每个网格点的提前。您可以附加第二个LATERAL
联接以在每个网格点后面搜索(只是另一个索引扫描),并从两个结果中取得较近的一个(忽略NULL)。但这引入了两个问题:
LATERAL
联接,并进行两次索引扫描。您可以使用递归CTE在实际找到的最后一次之前1分钟进行搜索,但随后返回的总行数变化更大。相关回答:
这一切都归结为你需要的确切定义,以及允许妥协的地方。
相关: