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

查询是这样执行的:

  1. JOIN table。假设 table 不是平凡的 1:1,这将导致比 table 中的任何一个都多的行。
  2. 针对该温度table进行聚合(例如COUNT)。
  3. 只有这样 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 0Sum( ) 10:

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;