我有一个具有以下结构的表:
Contents (
id
name
desc
tdate
categoryid
...
)
我需要对此表中的数据进行一些统计。例如,我希望通过该类别的分组和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
选择其他行为一个:
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)
但是当我在db表中没有类别组时,我仍然会得到一个"其他"记录。是否有可能在一个查询中制作并制作"其他"记录可选吗?
答案 0 :(得分:4)
特定难度此处:SELECT
列表中没有GROUP BY
子句的一个或多个聚合函数的查询产生正好一行,即使没有行在基础表中找到。
在WHERE
子句中,您无法阻止该行。您必须在事实之后排除这样的行,即在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
如果多个类别在第7/8列中具有相同的计数,则需要断开关系。在我的示例中,categoryid
较小的类别赢得了这样的竞赛。
括号必须在LIMIT
查询的单个分支中包含ORDER BY
或UNION
子句。
您只需加入表格category
即可获得前7个类别。而且在这种情况下,首先聚合并稍后加入通常会更便宜。因此,请不要加入名为cte
的{{3}}中的基本查询,只加入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;
我的查询计划稍微快一点。但这是一个相当奇怪的黑客......
CTE (common table expression)展示所有。
相关答案以及UNION ALL
/ LIMIT
技术的更多解释:
答案 1 :(得分:1)
快速修复,使'Others'
行有条件的是为该查询添加一个简单的HAVING
子句。
HAVING COUNT(c.id) > 0
(如果contents
表中没有其他行,则COUNT(c.id)
将为零。)
这只回答了问题的一半,如何使该行的返回成为条件。
问题的后半部分涉及更多。
要在一个查询中获取整个结果集,您可以执行类似这样的操作
(这是尚未测试;仅限桌面检查..我不确定postgresql是否在内联视图中接受LIMIT子句...如果它不是&我们& #39; d需要实现一种不同的机制来限制返回的行数。
SELECT IFNULL(t.name,'Others') AS name
, t.catid AS catid
, COUNT(o.id) AS data
FROM contents o
LEFT
JOIN category oa
ON oa.id = o.category_id
LEFT
JOIN ( SELECT COALESCE(ca.name,'Unknown') AS name
, ca.id AS catid
, COUNT(c.id) AS data
FROM contents c
LEFT
JOIN category ca
ON ca.id = c.categoryid
GROUP
BY COALESCE(ca.name,'Unknown')
, ca.id
ORDER
BY COUNT(c.id) DESC
, ca.id DESC
LIMIT 7
) t
ON ( t.catid = oa.id OR (t.catid IS NULL AND oa.id IS NULL))
GROUP
BY ( t.catid = oa.id OR (t.catid IS NULL AND oa.id IS NULL))
, t.catid
ORDER
BY COUNT(o.id) DESC
, ( t.catid = oa.id OR (t.catid IS NULL AND oa.id IS NULL)) DESC
, t.catid DESC
LIMIT 7
内联视图t
基本上得到与第一个查询相同的结果,类别表中的(最多)7个id
值列表,或类别表中的6个id
值和一个NULL。
外部查询基本上做同样的事情,将content
加入category
,但也要检查t
中是否有匹配的行。因为t
可能返回NULL,所以我们有一个稍微复杂的比较,我们希望NULL值匹配NULL值。 (MySQL方便地为我们提供了简写操作符,null安全比较运算符<=>
,但我不认为它可以在postgresql中使用,所以我们必须以不同的方式表达。
a = b OR (a IS NULL AND b IS NULL)
下一步是让GROUP BY工作,我们希望按内联视图t
返回的7个值进行分组,或者,如果t
的值不匹配,分组&#34;其他&#34;一起排。我们可以通过在GROUP BY子句中使用布尔表达式来实现这一点。
我们基本上说&#34;如果有来自t&#39;&#34; (真或假)然后按来自&#39; t&#39;的行分组。获取计数,然后按计数降序排序。
这未经过测试,只有桌面检查。
答案 2 :(得分:0)
您可以使用嵌套聚合来处理此问题。内部聚合计算计数以及序号。您想要获取数量为7或更少的所有内容,然后将其他所有内容合并到others
类别中:
SELECT (case when seqnum <= 7 then label else 'others' end) as label,
(case when seqnum <= 7 then catid end) as catid, sum(cnt)
FROM (SELECT ca.name AS label, ca.id AS catid, COUNT(c.id) AS cnt,
row_number() over (partition by ca.name, catid order by count(c.id) desc) as seqnum
FROM contents c LEFT OUTER JOIN
category ca
ON ca.id = c.categoryid
GROUP BY label, catid
) t
GROUP BY (case when seqnum <= 7 then label else 'others' end),
(case when seqnum <= 7 then catid end)
ORDER BY cnt DESC ;