获取 n 个分组类别并将其他类别合二为一

iam*_*eek 6 sql postgresql aggregate query-optimization sql-limit

我有一个具有以下结构的表:

Contents (
  id
  name
  desc
  tdate
  categoryid
  ...
)
Run Code Online (Sandbox Code Playgroud)

我需要对这个表中的数据做一些统计。例如,我想通过分组和该类别的 id 来获取具有相同类别的行数。此外,我想n按降序限制它们的行,如果有更多可用类别,我想将它们标记为“其他”。到目前为止,我已经对数据库提出了 2 个查询:

n按降序选择行:

SELECT COALESCE(ca.NAME, 'Unknown') AS label
    ,ca.id AS catid
    ,COUNT(c.id) AS data
FROM contents c
LEFT OUTER JOIN category ca ON ca.id = c.categoryid
GROUP BY label
    ,catid
ORDER BY data DESC LIMIT 7
Run Code Online (Sandbox Code Playgroud)

选择其他行作为一:

SELECT 'Others' AS label
    ,COUNT(c.id) AS data
FROM contents c
LEFT OUTER JOIN category ca ON ca.id = c.categoryid
WHERE c.categoryid NOT IN ($INCONDITION)
Run Code Online (Sandbox Code Playgroud)

但是当我在 db 表中没有任何类别组时,我仍然会得到一个“其他”记录。是否可以在一个查询中进行,并使“其他”记录可选?

Erw*_*ter 4

这里的具体困难是:即使在基础表中没有找到任何行,在SELECT列表中使用一个或多个聚合函数并且没有子句的GROUP BY查询只会生成一行。

您无法在子句中执行任何操作WHERE来隐藏该行。您必须在 fact 之后排除这样的行,即在HAVING子句中或在外部查询中。

根据文档:

如果查询包含聚合函数调用,但没有GROUP BY子句,则仍然会发生分组:结果是单个组行(或者可能根本没有行,如果单个行随后被 消除HAVING)。HAVING如果它包含子句,即使没有任何聚合函数调用或GROUP BY子句,情况也是如此。

应该注意的是,添加GROUP BY仅包含常量表达式的子句(否则完全没有意义!)也是可行的。请参阅下面的示例。但我宁愿不使用这个技巧,即使它很短、便宜且简单,因为它的作用很难显而易见。

以下查询仅需要一次表扫描并返回按计数排序的前 7 个类别。当(且仅当)有更多类别时,其余的将汇总为“其他”:

WITH cte AS (
   SELECT categoryid, count(*) AS data
        , row_number() OVER (ORDER BY count(*) DESC, categoryid) AS rn
   FROM   contents
   GROUP  BY 1
   )
(  -- parentheses required again
SELECT categoryid, COALESCE(ca.name, 'Unknown') AS label, data
FROM   cte
LEFT   JOIN category ca ON ca.id = cte.categoryid
WHERE  rn <= 7
ORDER  BY rn
)
UNION ALL
SELECT NULL, 'Others', sum(data)
FROM   cte
WHERE  rn > 7         -- only take the rest
HAVING count(*) > 0;  -- only if there actually is a rest
-- or: HAVING  sum(data) > 0
Run Code Online (Sandbox Code Playgroud)
  • 如果多个类别在第 7/8 排名中的计数相同,则需要打破平局。在我的例子中,较小的类别categoryid赢得了这样的比赛。

  • 需要使用括号将LIMITorORDER BY子句包含到查询的各个分支中UNION

  • 您只需加入category前 7 个类别的表即可。在这种情况下,先聚合然后再加入通常会更便宜。因此,不要加入名为 的CTE(公共表表达式)中的基本查询cte,只加入查询SELECT的第一个UNION,这样更便宜。

  • 不知道为什么你需要COALESCE. 如果您有一个从contents.categoryid到的外键category.id,并且两者contents.categoryid都已category.name定义NOT NULL(就像它们可能应该那样),那么您就不需要它。

奇怪的GROUP BY true

这也可以工作:

...

UNION ALL
SELECT NULL , 'Others', sum(data)
FROM   cte
WHERE  rn > 7
GROUP BY true; 
Run Code Online (Sandbox Code Playgroud)

我什至得到了稍微更快的查询计划。但这是一个相当奇怪的黑客......

SQL Fiddle演示了所有内容。

UNION ALL相关答案以及对/技术的更多解释LIMIT