我使用PostgreSQL 9.4,我有一个包含1300万行的表格,数据大致如下:
a | b | u | t
-----+---+----+----
foo | 1 | 1 | 10
foo | 1 | 2 | 11
foo | 1 | 2 | 11
foo | 2 | 4 | 1
foo | 3 | 5 | 2
bar | 1 | 6 | 2
bar | 2 | 7 | 2
bar | 2 | 8 | 3
bar | 3 | 9 | 4
bar | 4 | 10 | 5
bar | 5 | 11 | 6
baz | 1 | 12 | 1
baz | 1 | 13 | 2
baz | 1 | 13 | 2
baz | 1 | 13 | 3
md5(a)
,b
和(md5(a), b)
上有索引。 (实际上,a
可能包含超过4k字符的值。)还有一个类型SERIAL
的主键列,我在上面已省略。
我正在尝试构建一个返回以下结果的查询:
a | b | u | t | z
-----+---+----+----+---
foo | 1 | 1 | 10 | 3
foo | 1 | 2 | 11 | 3
foo | 2 | 4 | 1 | 3
foo | 3 | 5 | 2 | 3
bar | 1 | 6 | 2 | 5
bar | 2 | 7 | 2 | 5
bar | 2 | 8 | 3 | 5
bar | 3 | 9 | 4 | 5
bar | 4 | 10 | 5 | 5
bar | 5 | 11 | 6 | 5
在这些结果中,所有行都进行了重复数据删除,就像应用了GROUP BY a, b, u, t
一样,z
是对b
以上每个分区的a
的不同值的计数,并且仅包含z
值大于2的行。
我可以按照以下方式使用z
过滤器:
SELECT a, COUNT(b) AS z from (SELECT DISTINCT a, b FROM t) AS foo GROUP BY a
HAVING COUNT(b) > 2;
但是,我很难将其与表格中的其他数据相结合。
最有效的方法是什么?
答案 0 :(得分:3)
您的第一步可能已经更简单了:
SELECT md5(a) AS md5_a, count(DISTINCT b) AS z
FROM t
GROUP BY 1
HAVING count(DISTINCT b) > 2;
对于集成查询,我会采用稍微不同的方法(针对索引支持和模拟“松散索引扫描”):
SELECT t.*, a.z
FROM (
SELECT md5(a) AS md5_a, b, count(*) OVER (PARTITION BY a) AS z
FROM t
GROUP BY 1, 2
) a
, LATERAL (
SELECT *
FROM t
WHERE md5(a) = a.md5_a
AND b = a.b
ORDER BY u, t
LIMIT 1
) t
WHERE a.z > 2;
假设(缺少信息)您希望(u, t)
上每组重复项的(a, b)
行最小。
使用md5(a)
代替a
,因为a
显然非常很长,并且您已经拥有md5(a)
的索引等
count(*) OVER ...
在单个查询级别中工作,因为在聚合之后应用了窗口函数:
由于您的表格是 big ,因此您需要有效的查询。这应该是最快的解决方案之一 - 具有足够的索引支持。 (md5(a), b)
上的索引是有用的,但(md5(a), b, u, t)
上的索引对于查询的第二步(横向连接)来说会更好。
详细解释(尤其是章节 2.JOIN LATERAL ):
(a, b, u, t)
实际上这简单得多:
SELECT DISTINCT ON (md5(t.a), b, u, t)
t.a, t.b, t.u, t.t, a.z
FROM (
SELECT md5(a) AS md5_a, count(DISTINCT b) AS z
FROM t
GROUP BY 1
HAVING count(DISTINCT b) > 2
) a
JOIN t ON md5(t.a) = md5_a;
我再次避免向GROUP BY
大栏目(这将是昂贵的)。
关于DISTINCT ON
: