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 表中没有任何类别组时,我仍然会得到一个“其他”记录。是否可以在一个查询中进行,并使“其他”记录可选?
这里的具体困难是:即使在基础表中没有找到任何行,在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: