sql - 关于如何替换MariaDB语句中的UNION子句的思考

标签 sql mariadb query-optimization union

(创建测试表的代码位于此消息的底部。)

给定两个表:

SELECT * FROM test_users;
+----+-------+
| ID | name  |
+----+-------+
|  1 | Tom   |
|  2 | Wendy |
|  3 | Fred  |
|  4 | Sandy |
+----+-------+
4 rows in set (0.000 sec)

SELECT * FROM test_hours;
+----+----------+------+-------+
| ID | users_ID | year | hours |
+----+----------+------+-------+
|  1 |        1 | 2018 |     3 |
|  2 |        1 | 2018 |     5 |
|  3 |        1 | 2019 |     7 |
|  4 |        1 | 2019 |     2 |
|  5 |        1 | 2019 |     9 |
|  6 |        1 | 2020 |     5 |
|  7 |        1 | 2020 |     9 |
|  8 |        2 | 2018 |     7 |
|  9 |        2 | 2018 |    11 |
| 10 |        2 | 2018 |     8 |
| 11 |        2 | 2019 |    10 |
| 12 |        2 | 2019 |    12 |
| 13 |        3 | 2018 |     4 |
| 14 |        3 | 2018 |     1 |
| 15 |        3 | 2018 |    15 |
| 16 |        3 | 2020 |    10 |
| 17 |        3 | 2020 |    12 |
| 18 |        4 | 2019 |     7 |
| 19 |        4 | 2019 |    11 |
| 20 |        4 | 2020 |     4 |
| 21 |        4 | 2020 |     6 |
+----+----------+------+-------+
21 rows in set (0.000 sec)

我可以使用一个非常简单的联接来按用户和年份获取小时数摘要:

SELECT name, year, SUM(hours) 
FROM test_hours 
JOIN test_users
  ON users_ID = test_users.ID
GROUP BY users_ID, year;
+-------+------+------------+
| name  | year | SUM(hours) |
+-------+------+------------+
| Tom   | 2018 |          8 |
| Tom   | 2019 |         18 |
| Tom   | 2020 |         14 |
| Wendy | 2018 |         26 |
| Wendy | 2019 |         22 |
| Fred  | 2018 |         20 |
| Fred  | 2020 |         22 |
| Sandy | 2019 |         18 |
| Sandy | 2020 |         10 |
+-------+------+------------+
9 rows in set (0.001 sec)

如果我只想要一年,我可以这样做:

SELECT name, year, SUM(hours) 
FROM test_hours 
JOIN test_users
  ON users_ID = test_users.ID
WHERE year = 2020
GROUP BY users_ID, year;
+-------+------+------------+
| name  | year | SUM(hours) |
+-------+------+------------+
| Tom   | 2020 |         14 |
| Fred  | 2020 |         22 |
| Sandy | 2020 |         10 |
+-------+------+------------+
3 rows in set (0.000 sec)

Wendy 退学了,因为她没有任何 2020 学时。不过,我真正想要的是:

+--------+------+------------+
| name   | year | SUM(hours) |
+--------+------+------------+
| Tom    | 2020 |         14 |
| Wendy  | 2020 |          0 |
| Fred   | 2020 |         22 |
| Sandy  | 2020 |         10 |
+--------+------+------------+

我可以使用 UNION 子句:

SELECT name, year, SUM(hours) 
FROM test_hours 
JOIN test_users
  ON users_ID = test_users.ID
WHERE year = 2020
GROUP BY users_ID, year

UNION

SELECT DISTINCT name, 2020, 0 
FROM test_hours 
JOIN test_users
  ON users_ID = test_users.ID
WHERE NOT EXISTS (
    SELECT *
    FROM test_hours
    WHERE users_ID = test_users.ID AND year = 2020);
+-------+------+------------+
| name  | year | SUM(hours) |
+-------+------+------------+
| Tom   | 2020 |         14 |
| Fred  | 2020 |         22 |
| Sandy | 2020 |         10 |
| Wendy | 2020 |          0 |
+-------+------+------------+
4 rows in set (0.001 sec)

但我想知道是否有更好的方法;完整的 SQL 语句已经包含许多 UNION 子句,我正在思考如何消除它们。

我不知道该怎么做。

有什么想法吗?

CREATE DATABASE IF NOT EXISTS test_db;
USE test_db;

DROP TABLE IF EXISTS test_hours;
CREATE TABLE test_hours (
  ID int(10) AUTO_INCREMENT PRIMARY KEY,
  users_ID int(10),
  year int(4),
  hours int(4)
);

DROP TABLE IF EXISTS test_users;
CREATE TABLE test_users (
  ID int(10),
  name varchar(60) 
);

INSERT INTO test_users (ID, name) VALUES
  (1, 'Tom'), (2, 'Wendy'), (3, 'Fred'), (4, 'Sandy');

INSERT INTO test_hours (users_ID, year, hours) VALUES
  (1, 2018, 3), (1, 2018, 5), (1, 2019, 7), (1, 2019, 2), (1, 2019, 9), (1, 2020, 5), (1, 2020, 9),
  (2, 2018, 7), (2, 2018, 11), (2, 2018, 8), (2, 2019, 10), (2, 2019, 12),
  (3, 2018, 4), (3, 2018, 1), (3, 2018, 15), (3, 2020, 10), (3, 2020, 12),
  (4, 2019, 7), (4, 2019, 11), (4, 2020, 4), (4, 2020, 6);

最佳答案

可能最简单的方法是使用条件聚合:

SELECT u.name, 2020 as year,
       SUM(CASE WHEN h.year = 2020 THEN h.hours ELSE 0 END) 
FROM test_hours h JOIN
     test_users u
     ON h.users_ID = u.ID
GROUP BY u.ID;

请注意,我更改了 GROUP BY 以使用 test_users 表,其中 ID 可能是主键 - 所以可以从同一个表中选择另一列,例如 name

另一种方法将使用 LEFT JOIN:

SELECT u.name, 2020 as year,
       SUM(h.hours)
FROM test_users u LEFT JOIN
     test_hours h
     ON h.users_ID = u.ID AND
        h.year = 2020
GROUP BY u.id, u.name;

关于sql - 关于如何替换MariaDB语句中的UNION子句的思考,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65765465/

相关文章:

sql - 如何在 MySQL 中搜索以 A 开头的名称?

sql - 测试网站是否易受 Sql 注入(inject)攻击

mysql - 如何在 MySQL 中查找共享相同(未知)前缀的字符串组?

mysql - 是否可以在 cPanel 上从 MySQL 5.5 迁移到 MariaDB 10?

mysql - 无法正常启动Mariadb?

mysql - 当有 IN 子句时,什么样的索引是有效的?

sql - 如何在 SQLite 中的特定条件下进行 SQL 查询,将一个表的结果行与另一表的行组合起来

mysql在查找重复项时获取所有记录

MySQL子查询在大表中非常慢

MySQL WHERE 中的多个 OR