如何在另一个表中定义范围时,如何有效地从postgresql表中选择数据?

时间:2017-01-11 02:14:46

标签: sql postgresql

使用示例表:

create table user_login (
user_id integer not null,
login_time numeric not null, -- seconds since epoch or similar
constraint unique(user_id, login_time)
);

create table user_page_visited (
page_id integer not null,
page_visited_at numeric not null -- seconds since epoch or similar
);

示例数据:

> user_login
  user_id login_time
1       1        100
2       1        140

> user_page_visited
  page_id page_visited_at
1       1             100
2       1             200
3       2             120
4       2             130
5       3             160
6       3             150

我希望返回基于user_page_visited的所有user_login.login_time行,例如,返回在现有login_time的20秒内访问的所有页面:

> user_page_visited
  page_id page_visited_at
1       1             100
3       2             120
5       3             160
6       3             150

当两个表都有很多行时,如何有效地执行此操作?例如,以下查询执行类似的操作(当范围重叠时返回重复的行),但似乎非常慢:

select * from
user_login l cross join
user_page_visited v
where v.page_visited_at >= l.login_time
and v.page_visited_at <= l.login_time + 20;

2 个答案:

答案 0 :(得分:1)

首先,使用常规join语法:

select *
from user_login l join
     user_page_visited v
     on v.page_visited_at >= l.login_time and
        v.page_visited_at <= l.login_time + 20;

接下来,请确保您在用于join的列上有索引。 。 。 user_login(login_time)user_page_visited(page_visited_at)

如果这些不起作用,那么你还有几个选择。如果&#34; 20&#34;固定,您可以改变索引的类型。如果您只是在登录和访问的页面之间寻找一个匹配项,那么也有一些技巧。

答案 1 :(得分:1)

此解决方案基于Gordon Linoff答案的评论。

首先,我们使用以下查询检索在与用户连接相同的时间片中访问的元组,或者在以下时间片中检索:

SELECT DISTINCT page_id, page_visited_at
FROM user_login
INNER JOIN user_page_visited ON login_time::INT / 20 = page_visited_at::INT / 20 OR login_time::INT / 20 = page_visited_at::INT / 20 - 1;

我们现在需要索引才能获得一个好的查询计划:

CREATE INDEX i_user_login_login_time_20 ON user_login ((login_time::INT / 20));
CREATE INDEX i_user_page_visited_page_visited_at_20 ON user_page_visited ((page_visited_at::INT / 20));
CREATE INDEX i_user_page_visited_page_visited_at_20_minus_1 ON user_page_visited ((page_visited_at::INT / 20 - 1));

如果使用这些索引探测查询,则会在两个位图索引扫描操作中获得一个BitmapOr,并且具有一些低常量成本。另一方面,如果没有这些索引,您会以更高的成本获得顺序扫描(我使用每个~100k元组的表进行测试)。

然而,此查询会产生太多结果。我们需要再次过滤它以获得最终结果:

SELECT DISTINCT page_id, page_visited_at
FROM user_login
INNER JOIN user_page_visited ON login_time::INT / 20 = page_visited_at::INT / 20 OR login_time::INT / 20 = page_visited_at::INT / 20 - 1
WHERE page_visited_at BETWEEN login_time AND login_time + 20;

在此查询上使用EXPLAIN显示PostgreSQL仍然使用位图索引扫描。

在user_login中有大约100k行,在user_page_visited中大约有200k行,查询需要~1.4s来检索~200k行而没有切片预过滤器需要3.5s。 (uname -a:Linux shepwork 4.4.26-gentoo#8 SMP Mon Nov 21 09:45:10 CET 2016 x86_64 AMD FX(tm)-6300六核处理器AuthenticAMD GNU / Linux)