【问题标题】:Select well spread points from a big table从大表中选择良好的分散点
【发布时间】:2017-05-10 12:11:06
【问题描述】:

我正在尝试编写一个存储过程,用于从一个大表中及时选择 X 个井分布点。

我有一张桌子points

  "Userid" integer
, "Time"   timestamp with time zone
, "Value"  integer 

它包含数亿条记录。每个用户大约有一百万条记录。

我想选择 X 个点(比如说 50 个),这些点从时间 A 到时间 B 都分布得很好。问题是这些点分布不均(如果一个点在 6:00:00,下一个例如,点可能在 15 秒、20 或 4 分钟之后)。

为一个 id 选择所有点最多可能需要 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 分钟标记。

【问题讨论】:

  • 样本数据、期望的结果和您当前的代码真的很有帮助。
  • 已经完成了,先生。
  • 因为他们的时间太接近第一个点,并且为了得到 11 个最好的(分散良好的)点,我需要一个尽可能接近时间 14:00, 14 的点: 01、14:02、14:03.... 14:10
  • "best" points 仍然没有很好的定义。 “良好传播”是一个可以解释的模糊术语。您实施的方法是找到与给定时间网格最近的点,这似乎是一个很好的近似值,但它确实不一定必须最小化所选行之间的时间差总和。并且您的 Postgres 版本应该始终被披露。

标签: sql postgresql time-series nearest-neighbor postgresql-performance


【解决方案1】:

answer使用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 列来证明为什么我认为这些行更适合请求

【讨论】:

    【解决方案2】:

    您的问题背后有一个优化问题,仅使用 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 分钟进行搜索,但随后返回的总行数变化更大。相关答案:

    这一切都归结为您需要什么以及允许妥协的确切定义。

    相关:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-02-15
      • 2012-08-12
      • 1970-01-01
      • 2017-02-09
      • 2011-12-12
      • 2012-12-07
      • 1970-01-01
      相关资源
      最近更新 更多