sql - 如何在 Postgres 的窗口函数中获取 mode()?

标签 sql postgresql aggregate-functions greatest-n-per-group window-functions

我尝试获取分组数据集的 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_idtype 都是 NOT NULL,否则你需要做更多。)

限定行的模式

这会单独根据过滤后的集合(使用 type = 'customer')计算模式。
您会获得“客户”中每个组最流行的颜色。

普通 JOIN 中的子查询(在这种情况下没有 LEFTLATERAL)可以完成这项工作 - 计算模式 每组一次,而不是每一行:

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/

相关文章:

java - 如何从 Netbeans 中的 pl/sql 函数获取结果

mysql - SELECT * 与 SELECT * LIMIT(性能)

python - 代表成员(member)级别的 Django 模型

mongodb - 使用 $let 聚合 $lookup 不起作用

php - 获取 id 列表的所有字段的值,而不在 mysql 查询中指定它们的 id?

mysql - 查看[时间长度]是否可用于多行预订的最佳方法是什么?

php - SQL 表可以有符号链接(symbolic link)或别名吗?

python - "ProgrammingError: column "genre_id "of relation "music_album "does not exist"而该列确实存在

sql - 查找尚未在表中的下一个空闲时间戳

mysql - SQL - 使用组合总和计算记录数