SQL 计数:不稳定的行为
SQL Count: erratic behaviour
我写的一段 SQL 没有按预期运行。一个重要的逻辑部分涉及计算有多少客人是 VIP,但 SQL 似乎总是得到错误的答案。
以下数据库有6位嘉宾,其中3位是VIP。
CREATE TABLE `guest` (
`GuestID` int(11) NOT NULL DEFAULT '0',
`fullname` varchar(255) DEFAULT NULL,
`vip` tinyint(1) DEFAULT '0',
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Dumping data for table `guest`
--
INSERT INTO `guest` (`GuestID`, `fullname`, `vip`) VALUES
(912, 'Sam', 0),
(321, 'Sev', 0),
(629, 'Joe', 0),
(103, 'Tom', 1),
(331, 'Cao', 1),
(526, 'Conor', 1);
最初 SQL 返回一个值说有 5 个 VIP,这是不正确的,因为只有 3 个 VIP。这是一个相当复杂的数据库,为了这个问题(有一个可重现的错误)生成一个最小可行的例子,脚本现在声明只有 2 个 VIP。同样,这是不正确的。
有问题的SQL是
SELECT slotguest.FK_SlotNo, Count(CASE WHEN guest.vip = 1 THEN 1 END) AS guest_count
FROM guest
INNER JOIN slotguest ON guest.GuestID = slotguest.FK_guest
GROUP BY slotguest.FK_SlotNo;
slotguest结构及内容如下
CREATE TABLE `slotguest` (
`FK_SlotNo` int(11) NOT NULL,
`FK_guest` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Dumping data for table `slotguest`
--
INSERT INTO `slotguest` (`FK_SlotNo`, `FK_guest`) VALUES
(396, 912),
(396, 321),
(396, 629),
(396, 103),
(396, 331),
(396, 526);
是什么导致 Count 的答案始终不正确?
如评论中所述(来自用户@Fábio Amorim、@Rajat 的检查),您的查询似乎按预期工作。由于您使用 CASE WHEN
设置了一个值,因此使用 SUM
.
可能会更好
如果您使用不同 VIP 类别的计数来查找可能存在数据泄漏的位置,可能会更加明显。
SELECT guest.vip, slotguest.FK_SlotNo, COUNT(*) AS guest_per_category
FROM guest
INNER JOIN slotguest ON guest.GuestID = slotguest.FK_guest
GROUP BY guest.vip,slotguest.FK_SlotNo;
闻起来像“爆炸-内爆”。鉴于
SELECT ... COUNT(*)
FROM a JOIN b ...
GROUP BY ...
查询是这样执行的:
JOIN
table。假设 table 不是平凡的 1:1,这将导致比 table 中的任何一个都多的行。
- 针对该温度table进行聚合(例如
COUNT
)。
- 只有这样
GROUP BY
才会收缩回原来需要的行数。
解决方案是避免对包含数据 counted/summed 的多个 table 进行聚合。有时模式是
SELECT ...
FROM ( SELECT x, COUNT(*) AS ct FROM a GROUP BY x ) AS b
JOIN c ON ...
解释问题所在,并给出更接近O.P.的查询的答案...
(我假设 O.P. 是错误的缩减示例,实际查询更复杂。如果我们了解大局,我想我不会像这样编写代码那个。)
在O.P。查询,CASE WHEN guest.vip = 1 THEN 1 END
格式错误。那是一个条件表达式;它应该 return 查询检索到的所有行的特定值——即 guest.vip <> 1
.
的行
实际上,行为是未定义的;正如评论所说,它会在某些 DBMS 上产生预期的答案;根据 O.P,它不会影响其他人。我想对于那些产生预期答案的人来说,DBMS 将 CASE
视为 returning Null
,然后 Count( )
将忽略空值。这是 SQL.
中 Null
的更可怕的后果之一
所以根据@Fábio Amorim 的评论,CASE
需要一个 ELSE
,因此 Count( )
给出了一个无用的结果,所以将 ELSE
改为 return 0
和 Sum( )
1
或 0
:
SELECT slotguest.FK_SlotNo, Sum(CASE WHEN guest.vip = 1 THEN 1 ELSE 0 END) AS guest_count
FROM guest
INNER JOIN slotguest ON guest.GuestID = slotguest.FK_guest
GROUP BY slotguest.FK_SlotNo;
我写的一段 SQL 没有按预期运行。一个重要的逻辑部分涉及计算有多少客人是 VIP,但 SQL 似乎总是得到错误的答案。
以下数据库有6位嘉宾,其中3位是VIP。
CREATE TABLE `guest` (
`GuestID` int(11) NOT NULL DEFAULT '0',
`fullname` varchar(255) DEFAULT NULL,
`vip` tinyint(1) DEFAULT '0',
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Dumping data for table `guest`
--
INSERT INTO `guest` (`GuestID`, `fullname`, `vip`) VALUES
(912, 'Sam', 0),
(321, 'Sev', 0),
(629, 'Joe', 0),
(103, 'Tom', 1),
(331, 'Cao', 1),
(526, 'Conor', 1);
最初 SQL 返回一个值说有 5 个 VIP,这是不正确的,因为只有 3 个 VIP。这是一个相当复杂的数据库,为了这个问题(有一个可重现的错误)生成一个最小可行的例子,脚本现在声明只有 2 个 VIP。同样,这是不正确的。
有问题的SQL是
SELECT slotguest.FK_SlotNo, Count(CASE WHEN guest.vip = 1 THEN 1 END) AS guest_count
FROM guest
INNER JOIN slotguest ON guest.GuestID = slotguest.FK_guest
GROUP BY slotguest.FK_SlotNo;
slotguest结构及内容如下
CREATE TABLE `slotguest` (
`FK_SlotNo` int(11) NOT NULL,
`FK_guest` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Dumping data for table `slotguest`
--
INSERT INTO `slotguest` (`FK_SlotNo`, `FK_guest`) VALUES
(396, 912),
(396, 321),
(396, 629),
(396, 103),
(396, 331),
(396, 526);
是什么导致 Count 的答案始终不正确?
如评论中所述(来自用户@Fábio Amorim、@Rajat 的检查),您的查询似乎按预期工作。由于您使用 CASE WHEN
设置了一个值,因此使用 SUM
.
如果您使用不同 VIP 类别的计数来查找可能存在数据泄漏的位置,可能会更加明显。
SELECT guest.vip, slotguest.FK_SlotNo, COUNT(*) AS guest_per_category
FROM guest
INNER JOIN slotguest ON guest.GuestID = slotguest.FK_guest
GROUP BY guest.vip,slotguest.FK_SlotNo;
闻起来像“爆炸-内爆”。鉴于
SELECT ... COUNT(*)
FROM a JOIN b ...
GROUP BY ...
查询是这样执行的:
JOIN
table。假设 table 不是平凡的 1:1,这将导致比 table 中的任何一个都多的行。- 针对该温度table进行聚合(例如
COUNT
)。 - 只有这样
GROUP BY
才会收缩回原来需要的行数。
解决方案是避免对包含数据 counted/summed 的多个 table 进行聚合。有时模式是
SELECT ...
FROM ( SELECT x, COUNT(*) AS ct FROM a GROUP BY x ) AS b
JOIN c ON ...
解释问题所在,并给出更接近O.P.的查询的答案...
(我假设 O.P. 是错误的缩减示例,实际查询更复杂。如果我们了解大局,我想我不会像这样编写代码那个。)
在O.P。查询,CASE WHEN guest.vip = 1 THEN 1 END
格式错误。那是一个条件表达式;它应该 return 查询检索到的所有行的特定值——即 guest.vip <> 1
.
实际上,行为是未定义的;正如评论所说,它会在某些 DBMS 上产生预期的答案;根据 O.P,它不会影响其他人。我想对于那些产生预期答案的人来说,DBMS 将 CASE
视为 returning Null
,然后 Count( )
将忽略空值。这是 SQL.
Null
的更可怕的后果之一
所以根据@Fábio Amorim 的评论,CASE
需要一个 ELSE
,因此 Count( )
给出了一个无用的结果,所以将 ELSE
改为 return 0
和 Sum( )
1
或 0
:
SELECT slotguest.FK_SlotNo, Sum(CASE WHEN guest.vip = 1 THEN 1 ELSE 0 END) AS guest_count
FROM guest
INNER JOIN slotguest ON guest.GuestID = slotguest.FK_guest
GROUP BY slotguest.FK_SlotNo;