在分组的SELECT语句中限制COUNT?

时间:2016-03-13 20:03:33

标签: sql group-by aggregate-functions

假设我有以下表结构:

+--------------------------------------------+     +------------------------+     +--------------------+
|                   videos                   |     |       borrowings       |     |     customers      |
+-----+------------------------+-------------+     +----------+-------------+     +-----+--------+-----+
| id  | title                  | genre       |     | video_id | customer_id |     | id  | name   | sex |
+-----+------------------------+-------------+     +----------+-------------+     +-----+--------+-----+
| 101 | Transformers III       | Action      |     | 101      | 101         |     | 101 | Alfred | m   |
+-----+------------------------+-------------+     +----------+-------------+     +-----+--------+-----+
| 102 | DNS - The Code of Life | Documentary |     | 102      | 102         |     | 102 | Agathe | f   |
+-----+------------------------+-------------+     +----------+-------------+     +-----+--------+-----+
                                                   video_id    -> videos.id
                                                   customer_id -> customers.id

我想选择按男女分类和分组的男性和女性借款数量。

+-------------+-------+---------+
| Genre       | Males | Females |
+-------------+-------+---------+
| Action      | 1     | 0       |
+-------------+-------+---------+
| Documentary | 0     | 1       |
+-------------+-------+---------+

我的第一次尝试只选择按流派分组的借款数

SELECT
  v.genre "Genre",
  COUNT(c.id) "Count"
FROM videos v
INNER JOIN borrowings b ON v.id = b.video_id
INNER JOIN customers c ON b.customer_id = c.id
GROUP BY v.genre
ORDER BY v.genre ASC;

我知道我可以在COUNT函数(或任何其他聚合函数)中执行SELECT,所以我的想法是做类似的事情(伪代码):

SELECT
  v.genre "Genre",
  COUNT(SELECT c.id FROM parent_selection_set WHERE c.sex = "m") "Borrowings from males",
  ...

但我怀疑有一个像parent_selection_set这样的概念。 我想知道是否有办法在计数中使用SELECTs,或者是否有更好的方法来获得所需的结果?

2 个答案:

答案 0 :(得分:2)

最快的解决方案:

这就是您通常会得到您正在寻找

的结果的方法
SELECT
  v.genre "Genre",
  COUNT(CASE WHEN c.sex = 'm' THEN 1 END) "Males",
  COUNT(CASE WHEN c.sex = 'f' THEN 1 END) "Females"
FROM videos v
INNER JOIN borrowings b ON v.id = b.video_id
INNER JOIN customers c ON b.customer_id = c.id
GROUP BY v.genre
ORDER BY v.genre ASC;

您可以在Stack Overflow上找到很多问题,寻找如何创建PIVOT tables in SQL。这个解决方案很快,因为在连接之后可以在内存中完成两个COUNT(...)操作。

您正在寻找的解决方案:

为了完整性'为了这个,您使用子查询寻找语法:

SELECT
  v.genre "Genre",
  (SELECT COUNT(*) FROM customers c WHERE b.customer_id = c.id AND c.sex = 'm') "Males",
  (SELECT COUNT(*) FROM customers c WHERE b.customer_id = c.id AND c.sex = 'f') "Females",
FROM videos v
INNER JOIN borrowings b ON v.id = b.video_id
GROUP BY v.genre
ORDER BY v.genre ASC;

这些被称为correlated subqueries,它们比任何基于JOIN的解决方案慢得多,因为通常,必须为顶级查询的每一行执行子查询。 / p>

答案 1 :(得分:1)

有多种可能性取决于RDBMS。 既然你没有使用RDBMS,这里有一个通用的SQL(通常适用于所有RDBMS,但有点长)

SELECT T.gr, T.cnt AS Total, TM.cnt AS Males, TF.cnt AS Females
FROM
(
    SELECT  v.genre AS gr, COUNT(c.id) AS cnt
    FROM videos v
    INNER JOIN borrowings b ON v.id = b.video_id
    INNER JOIN customers c ON b.customer_id = c.id
    GROUP BY v.genre
) T
LEFT JOIN 
(
    SELECT  v.genre AS gr, COUNT(c.id) AS cnt
    FROM videos v
    INNER JOIN borrowings b ON v.id = b.video_id
    INNER JOIN customers c ON b.customer_id = c.id
    WHERE c.sex = 'm'
    GROUP BY v.genre
) TM ON T.gr=TM.gr
LEFT JOIN 
(
    SELECT  v.genre AS gr, COUNT(c.id) AS cnt
    FROM videos v
    INNER JOIN borrowings b ON v.id = b.video_id
    INNER JOIN customers c ON b.customer_id = c.id
    WHERE c.sex = 'f'
    GROUP BY v.genre
) TF ON T.gr=TF.gr
ORDER BY T.gr ASC

在MySQL或Oracle中,您可以执行以下操作:

SELECT
  v.genre "Genre",
  COUNT(c.id) AS Tot,
  COUNT(CASE WHEN (c.sex='m') THEN c.id ELSE 0 END) AS Males,
  COUNT(CASE WHEN (c.sex='f') THEN c.id ELSE 0 END) AS Females
FROM videos v
INNER JOIN borrowings b ON v.id = b.video_id
INNER JOIN customers c ON b.customer_id = c.id
GROUP BY v.genre
ORDER BY v.genre ASC;

在Access或SQL Server中,您可以使用PIVOT语句