MySQL三表关联SUM求和错误排查:含Having子句的查询问题
问题:多表关联查询时活动分数总和计算错误
表结构与测试数据
accounts表
CREATE TABLE `accounts` ( `id` int(10) UNSIGNED NOT NULL, `companyName` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `is_duplicate` enum('yes','no') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'no', `cmpCodes` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `is_deleted` enum('yes','no') COLLATE utf8mb4_unicode_ci DEFAULT 'no', `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; INSERT INTO `accounts` (`id`, `companyName`, `is_duplicate`, `cmpCodes`, `is_deleted`, `created_at`, `updated_at`) VALUES (1024, 'Vodafone Germany','no', 'imli02-0323', 'no', '2023-10-20 09:36:02', '2021-11-18 15:35:39'), (1336, 'Microsoft', 'no', 'imli02-0323', 'no', '2023-11-23 20:02:03', '2021-12-20 14:16:29'), (1234, 'Microsoft test','no', 'imli02-0323', 'no', '2023-11-23 20:02:03', '2021-12-20 14:16:29');
contacts表
CREATE TABLE `contacts` ( `id` int(11) NOT NULL, `accountId` int(11) DEFAULT NULL, `name` varchar(250) DEFAULT NULL, `cmpMultiple` text DEFAULT NULL, `is_duplicate` enum('yes','no') NOT NULL DEFAULT 'no', `is_deleted` enum('yes','no') DEFAULT 'no', `created_at` timestamp NULL DEFAULT current_timestamp(), `updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() ) ENGINE=InnoDB DEFAULT CHARSET=latin1; INSERT INTO `contacts` (`id`, `accountId`, `name`, `cmpMultiple`, `is_duplicate`, `is_deleted`, `created_at`, `updated_at`) VALUES (4543, 1336, 'alex', 'imli02-0323', 'no', 'no', '2023-08-15 15:13:22', '2023-11-20 10:24:44'), (4545, 1024, 'julie', 'imli02-0323', 'no', 'no', '2023-08-16 10:26:22', '2024-01-08 08:33:23'), (4742, 1336, 'Matt', 'imli02-0323', 'no', 'no', '2023-11-14 11:23:21', '2023-11-23 20:02:03'), (4744, 1336, 'Martin', 'imli02-0323', 'no', 'no', '2023-11-14 11:23:21', '2023-11-15 16:39:33'), (4743, 1336, 'Andrew', 'imli02-0323', 'no', 'no', '2023-11-14 11:23:22', '2023-11-23 19:02:48'), (3434, 1234, 'jack', 'imli02-0323', 'no', 'no', '2023-08-16 10:26:22', '2024-01-07 12:09:04');
contact_activity表
CREATE TABLE `contact_activity` ( `id` int(11) NOT NULL, `contactID` int(11) NOT NULL, `accountId` int(11) DEFAULT NULL COMMENT 'PD org id', `cmpCode` varchar(255) DEFAULT NULL, `activityScore` int(11) DEFAULT NULL, `activityDate` datetime DEFAULT NULL, `created_at` datetime NOT NULL DEFAULT current_timestamp() ) ENGINE=InnoDB DEFAULT CHARSET=latin1; INSERT INTO `contact_activity` (`id`, `contactID`, `accountId`, `cmpCode`, `activityScore`, `activityDate`, `created_at`) VALUES (2442, 4543, 1336, 'imli02-0323', 3, '2023-08-15 16:13:35', '2023-08-15 17:13:36'), (2451, 4545, 1024, 'imli02-0323', 8, '2023-08-16 11:27:24', '2023-08-16 12:27:25'), (2695, 4543, 1336, 'imli02-0323', 3, '2023-10-09 16:24:50', '2023-10-09 17:24:51'), (3116, 0, 1024, 'imli02-0323', 4, '2023-12-27 12:55:47', '2023-12-29 13:47:53');
错误的查询语句
SELECT `accounts`.`companyName` AS `name`, `accounts`.`id` AS `accountId`, SUM(contact_activity.activityScore) AS totalActivityScore, contact_activity.id AS activityid FROM `accounts` LEFT JOIN `contacts` ON `accounts`.`id` = `contacts`.`accountId` AND( `contacts`.`cmpMultiple` = 'imli02-0323' AND( `accounts`.`cmpCodes` = 'imli02-0323' OR accounts.cmpCodes = '' OR accounts.cmpCodes IS NULL ) ) AND `contacts`.`is_duplicate` = 'no' AND `contacts`.`is_deleted` = 'no' INNER JOIN `contact_activity` ON ( `contact_activity`.`contactID` = `contacts`.`id` AND `contact_activity`.`cmpCode` = 'imli02-0323' AND `contact_activity`.`contactID` IS NOT NULL ) OR( contact_activity.accountId = accounts.id AND contact_activity.cmpCode = 'imli02-0323' AND `contact_activity`.`accountId` IS NOT NULL ) WHERE `accounts`.`is_duplicate` = 'no' AND `accounts`.`is_deleted` = 'no' AND `contact_activity`.`activityScore` != '' GROUP BY `contact_activity`.`accountId`, `accounts`.`id` HAVING SUM(contact_activity.activityScore) >= 6;
当前错误结果
| name | accountId | totalActivityScore | activityid |
|---|---|---|---|
| Vodafone Germany | 1024 | 12 | 2451 |
| Microsoft | 1336 | 24 | 2442 |
期望正确结果
| name | accountId | totalActivityScore | activityid |
|---|---|---|---|
| Vodafone Germany | 1024 | 12 | 2451 |
| Microsoft | 1336 | 6 | 2442 |
问题原因
查询中使用LEFT JOIN contacts后,contact_activity的每条记录会和该账户下所有符合条件的联系人匹配,形成笛卡尔积。以Microsoft(accountId=1336)为例,该账户有4个有效联系人,每条活动记录会被关联4次,原本2条记录的分数总和3+3=6,被重复计算4次后得到24。
修正后的查询语句
SELECT a.companyName AS name, a.id AS accountId, SUM(ca.activityScore) AS totalActivityScore, MIN(ca.id) AS activityid -- 取该账户最早的活动ID,可根据需求调整 FROM accounts a JOIN ( -- 先筛选并关联所有有效活动记录,确保每条活动只对应一个账户 SELECT ca.id, ca.activityScore, -- 优先取联系人关联的账户,否则取活动直接关联的账户 COALESCE(c.accountId, ca.accountId) AS accountId FROM contact_activity ca LEFT JOIN contacts c ON ca.contactID = c.id AND c.cmpMultiple = 'imli02-0323' AND c.is_duplicate = 'no' AND c.is_deleted = 'no' WHERE ca.cmpCode = 'imli02-0323' AND ca.activityScore IS NOT NULL -- 确保活动属于有效账户 AND (c.accountId IS NOT NULL OR ca.accountId IS NOT NULL) -- 去重,避免同一活动被多次关联 GROUP BY ca.id, ca.activityScore, COALESCE(c.accountId, ca.accountId) ) ca ON ca.accountId = a.id WHERE a.is_duplicate = 'no' AND a.is_deleted = 'no' AND (a.cmpCodes = 'imli02-0323' OR a.cmpCodes = '' OR a.cmpCodes IS NULL) GROUP BY a.id, a.companyName HAVING SUM(ca.activityScore) >= 6;
说明
- 子查询先处理
contact_activity与contacts的关联,通过COALESCE确定活动所属账户,同时通过GROUP BY确保每条活动记录只被保留一次,避免笛卡尔积导致的重复计算。 - 主查询将处理后的活动记录与
accounts关联,按账户分组计算分数总和,得到正确结果。
内容的提问来源于stack exchange,提问作者Gurdeep Singh





