我尝试获取分组数据集的 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'
位于子查询之外,因为在前半部分已经写入后,稍后会将其附加到查询中。
最佳答案
我们正在谈论有序集合聚合函数mode() ,在 Postgres 9.4 中引入。您可能看到了这条错误消息:
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)
为避免重复您的条件,将其放在子查询中并将其传播到连接子句中的外部查询 - 我选择了匹配的列名并在我的示例中使用 USING
连接。
您可以将条件移动到外部查询,甚至移动到后面的步骤。但是,它会不必要地更昂贵,因为必须计算 (group_id, type)
的 every 组合的模式,然后才能将所有其他类型的结果排除在外稍后的步骤。
有多种方法可以参数化您的查询。准备好的语句,PL/pgSQL 函数,参见:
或者,如果基础表没有太大变化,则可以选择每个 (group_id, type)
具有所有预计算模式的物化 View 替换子查询.
还有一个选择:首先使用 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'
不正确的行),您需要一个不同的查询。
您获得每个组中所有成员中最受欢迎的颜色。
将 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 查询,但基于基表中的所有组成员。
关于sql - 如何在 Postgres 的窗口函数中获取 mode()?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55542580/