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

问题描述 投票:1回答:1

我试图将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'保留在子查询之外,因为在它的前半部分已经写入之后,它将在稍后附加到查询中。

sql postgresql aggregate-functions greatest-n-per-group window-functions
1个回答
2
投票

我们正在谈论Postgres 9.4引入的有序集合函数mode()。您可能看到此错误消息:

ERROR:  OVER is not supported for ordered-set aggregate mode

我们可以解决它。但究竟是哪种模式?

(所有假设group_idtype都是NOT NULL,否则你需要做更多。)

Mode of qualifying rows

这将仅基于过滤集(使用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)

为避免重复您的条件,将其放在子查询中并将其传播到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

Mode of all rows

如果要将模式基于所有行(包括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查询类似,但基于基表中的所有组成员。

© www.soinside.com 2019 - 2024. All rights reserved.