第二张子表存在记录时按父表字段汇总子表字段的SQL写法
问题背景
现有三张业务表,结构及测试数据如下:
tournament_oc(赛事主表)
| id_ |
|---|
| 1 |
| 2 |
match_oc(比赛明细表)
| id_ | tournament_oc_id |
|---|---|
| 1 | 1 |
| 2 | 1 |
stat_oc(赛事统计表)
| id_ | tournament_oc_id | value_ |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 1 | 1 |
| 3 | 2 | 1 |
需求
筛选存在至少1条关联match_oc记录的赛事,按赛事ID分组,统计对应stat_oc.value_的总和,预期输出:
| tournament_oc_id | sum_value |
|---|---|
| 1 | 2 |
错误写法与原因
最初尝试的SQL如下:
SELECT t.id_ AS tournament_oc_id, sum(s.value_) AS sum_value FROM tournament_oc AS t JOIN match_oc AS m ON m.tournament_oc_id = t.id_ JOIN stat_oc as s on s.tournament_oc_id = t.id_ GROUP BY t.id_
执行后返回错误结果sum_value=4,根因是tournament_oc.id_=1关联2条match_oc记录,JOIN后产生笛卡尔积,导致stat_oc的记录被重复复制,求和时数值翻倍。
正确写法
核心思路是避免多表一对多关联产生的行放大问题,两种常用实现:
写法1:EXISTS判断(推荐,性能最优)
直接用EXISTS子查询判断赛事是否存在关联比赛,不需要关联match表全量数据,避免笛卡尔积:
SELECT t.id_ AS tournament_oc_id, SUM(s.value_) AS sum_value FROM tournament_oc t JOIN stat_oc s ON s.tournament_oc_id = t.id_ WHERE EXISTS ( SELECT 1 FROM match_oc m WHERE m.tournament_oc_id = t.id_ ) GROUP BY t.id_;
写法2:match表预去重后关联
如果偏好JOIN写法,先将match表按赛事ID去重,保证每个赛事只保留1条匹配记录,再关联stat表求和:
SELECT t.id_ AS tournament_oc_id, SUM(s.value_) AS sum_value FROM tournament_oc t JOIN ( SELECT DISTINCT tournament_oc_id FROM match_oc ) m ON m.tournament_oc_id = t.id_ JOIN stat_oc s ON s.tournament_oc_id = t.id_ GROUP BY t.id_;
两种写法执行结果均符合预期:仅返回存在关联比赛的赛事ID=1,求和值为2。
附:复现测试环境的SQL脚本
CREATE DATABASE IF NOT EXISTS `test` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */; USE `test`; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!50503 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; DROP TABLE IF EXISTS `match_oc`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `match_oc` ( `id_` int NOT NULL AUTO_INCREMENT, `tournament_oc_id` int NOT NULL, PRIMARY KEY (`id_`), KEY `fk__match_oc__tournament_oc_id_idx` (`tournament_oc_id`), CONSTRAINT `fk__match_oc__tournament_oc_id` FOREIGN KEY (`tournament_oc_id`) REFERENCES `tournament_oc` (`id_`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; LOCK TABLES `match_oc` WRITE; /*!40000 ALTER TABLE `match_oc` DISABLE KEYS */; INSERT INTO `match_oc` VALUES (1,1),(2,1); /*!40000 ALTER TABLE `match_oc` ENABLE KEYS */; UNLOCK TABLES; DROP TABLE IF EXISTS `stat_oc`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `stat_oc` ( `id_` int NOT NULL AUTO_INCREMENT, `tournament_oc_id` int NOT NULL, `value_` tinyint DEFAULT NULL, PRIMARY KEY (`id_`), KEY `fk__stat_oc__tournament_oc_id_idx` (`tournament_oc_id`), CONSTRAINT `fk__stat_oc__tournament_oc_id` FOREIGN KEY (`tournament_oc_id`) REFERENCES `tournament_oc` (`id_`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; LOCK TABLES `stat_oc` WRITE; /*!40000 ALTER TABLE `stat_oc` DISABLE KEYS */; INSERT INTO `stat_oc` VALUES (1,1,1),(2,1,1),(3,2,1); /*!40000 ALTER TABLE `stat_oc` ENABLE KEYS */; UNLOCK TABLES; DROP TABLE IF EXISTS `tournament_oc`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `tournament_oc` ( `id_` int NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id_`) ) ENGINE=InnoDB AUTO_INCREMENT=26193 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; LOCK TABLES `tournament_oc` WRITE; /*!40000 ALTER TABLE `tournament_oc` DISABLE KEYS */; INSERT INTO `tournament_oc` VALUES (1),(2); /*!40000 ALTER TABLE `tournament_oc` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
内容的提问来源于stack exchange,提问作者Jossy
相关产品推荐
相关产品推荐

