我试图将mode()
用于分组数据集,但不对结果进行分组。 (使用Postgres 9.5,可以根据需要升级。)
例如用户拥有“最喜欢的颜色”,属于一个组。获取组内mode()
“最喜欢的颜色”的用户列表。
窗口函数适用于大多数聚合,但mode()
似乎是一个与窗口函数不兼容的异常。还有另一种方法可以解决这个问题吗?到目前为止,这就是我一直在玩的东西......
工作,但给出分组结果,我正在寻找未分组的结果:
SELECT group_id,
mode() WITHIN GROUP (ORDER BY color)
FROM users
GROUP BY group_id;
语法无效(只是我想要完成的一个例子):
SELECT id, color, group_id,
mode(color) OVER (PARTITION BY group_id)
FROM users;
要么:
SELECT id, color, group_id,
mode() WITHIN GROUP (ORDER BY color) OVER (PARTITION BY group_id)
FROM users;
我尝试使用横向连接,但是如果没有在连接的内部和外部重新迭代我的WHERE
子句,它就无法正常工作(当这个查询变得更复杂时,我不想这样做):
SELECT u1.id, u1.group_id, u1.color, mode_color
FROM users u1
LEFT JOIN LATERAL
(SELECT group_id, mode() WITHIN GROUP (ORDER BY color) as mode_color
FROM users
WHERE group_id = d1.group_id
GROUP BY group_id)
u2 ON u1.group_id = u2.group_id
WHERE u1.type = 'customer';
重要的是WHERE u1.type = 'customer'
保留在子查询之外,因为在它的前半部分已经写入之后,它将在稍后附加到查询中。
我们正在谈论Postgres 9.4引入的有序集合函数mode()。您可能看到此错误消息:
ERROR: OVER is not supported for ordered-set aggregate mode
我们可以解决它。但究竟是哪种模式?
(所有假设group_id
和type
都是NOT NULL
,否则你需要做更多。)
这将仅基于过滤集(使用type = 'customer'
)计算模式。
您可以在“客户”中获得每组最受欢迎的颜色。
普通JOIN
中的子查询(在这种情况下没有LEFT
和LATERAL
)可以完成这项任务 - 每组计算一次模式,而不是每一行计算:
SELECT u1.id, u1.group_id, u1.color, u2.mode_color
FROM users u1
JOIN ( -- not LATERAL
SELECT group_id, type -- propagate out for the join
, mode() WITHIN GROUP (ORDER BY color) AS mode_color
FROM users
WHERE type = 'customer' -- place condition in subquery (cheap)
GROUP BY group_id, type
) u2 USING (group_id, type); -- shorthand syntax for matching names
-- WHERE type = 'customer' -- or filter later (expensive)
为避免重复您的条件,将其放在子查询中并将其传播到join子句中的外部查询 - 我选择了匹配的列名,并在我的示例中与USING
连接。
您可以将条件移动到外部查询,甚至可以移动到稍后的步骤。然而,由于必须计算(group_id, type)
的每种组合的模式,然后在后续步骤中排除每种其他类型的结果之前,这将是不必要的更昂贵。
有一些方法可以参数化您的查询。准备好的语句,PL / pgSQL函数,请参阅:
或者,如果基础表没有太大变化,那么每个(group_id, type)
替换子查询的所有预先计算模式的物化视图都是一个选项。
还有一个选择:首先使用CTE过滤符合条件的行,然后WHERE
条件可以保留在您请求的子查询之外:
WITH cte AS ( -- filter result rows first
SELECT id, group_id, color
FROM users u1
WHERE type = 'customer' -- predicate goes here
)
SELECT *
FROM cte u1
LEFT JOIN ( -- or JOIN, doesn't matter here
SELECT group_id
, mode() WITHIN GROUP (ORDER BY color) AS mode_color
FROM cte -- based on only qualifying rows
GROUP BY 1
) u2 USING (group_id);
我们可以简化SELECT *
,因为USING
在结果集中只能方便地放置一个group_id
。
如果要将模式基于所有行(包括type = 'customer'
不为true的那些行),则需要不同的查询。
您可以在所有成员中获得每组最受欢迎的颜色。
将WHERE
子句移动到外部查询:
SELECT u1.id, u1.group_id, u1.color, u2.mode_color
FROM users u1
LEFT JOIN ( -- or JOIN, doesn't matter here
SELECT group_id
, mode() WITHIN GROUP (ORDER BY color) AS mode_color
FROM users
GROUP BY group_id
) u2 USING (group_id)
WHERE u1.type = 'customer';
如果您的谓词(type = 'customer'
)足够有选择性,那么为所有组计算模式可能是一种浪费。首先过滤小子集,仅计算包含组的模式。为此添加CTE:
WITH cte AS ( -- filter result rows first
SELECT id, group_id, color
FROM users u1
WHERE type = 'customer'
)
SELECT *
FROM cte u1
LEFT JOIN ( -- or JOIN
SELECT group_id
, mode() WITHIN GROUP (ORDER BY color) AS mode_color
FROM (SELECT DISTINCT group_id FROM cte) g -- only relevant groups
JOIN users USING (group_id) -- but consider all rows for those
GROUP BY 1
) u2 USING (group_id);
与上面的CTE查询类似,但基于基表中的所有组成员。