在分组 SELECT 语句中限制 COUNT?

Restricting COUNT in a grouped SELECT statement?

假设我有以下 table 结构:

+--------------------------------------------+     +------------------------+     +--------------------+
|                   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

我想select从男性和女性借用的数量按流派分开和分组。

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

我的第一次尝试只是 selects 按流派分组的借阅数量

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这样的概念。 我想知道是否有办法在计数内使用 SELECT 执行此操作,或者是否有更好的方法来获得所需的结果?

最快的解决方案:

这通常是您获得所需结果的方式

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(...)操作可以在join后在内存中完成。

您正在寻找的解决方案:

为了完整起见,这里是您要查找的子查询语法:

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 的解决方案都慢得多,因为通常,必须对顶级查询的每一行执行子查询。

有多种可能性取决于 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 语句