SQL CASE WHEN 语句:语法、实例和常见错误

  1. 首页
  2. 博客
  3. 数据库和 SQL

数据库与 SQL

SQL CASE WHEN 语句:语法、实例和常见错误

CASE WHEN 是在 SQL 中编写 if-then 逻辑的方式。学习这两种形式,然后将其用于标签、数据透视式报告、自定义排序和安全更新,同时避免 NULL 和排序陷阱。

阅读约 8 分钟
SQL CASE WHEN 语句:语法、实例和常见错误

要点速览

  • CASE 根据 first 条件为 true 返回一个值。
  • 把最具体的条件放在第一位,否则后面的条件可能永远无法达到。
  • 如果没有 ELSE,则不匹配的行将返回 NULL。
  • 在 SUM 或 COUNT 中,CASE 将一个查询转换为一份紧凑的数据透视式报告。

大多数报告需要一些逻辑:将订单标记为大或小,并排计算已付款订单和待处理订单,或者首先显示紧急项目。 SQL CASE WHEN 直接在查询内处理所有这些,并且它适用于所有主要数据库。

我们在 SQLite 3.50 中运行了每个查询,显示的结果来自该运行。 CASE 是标准 SQL 的一部分,因此对于这些示例,它在 PostgreSQL、MySQL、SQL Server 和 Oracle 中的行为方式相同。

什么是“案例何时”?

CASE 是一个表达式,而不是一个单独的命令。它按顺序评估条件并返回一个值,因此它可以到达允许值的任何位置:在 SELECT、WHERE、GROUP BY、ORDER BY 中或在函数内部。它总是以 END 结尾。

CASE
  WHEN condition1 THEN result1
  WHEN condition2 THEN result2
  ELSE fallback
END

样本表

CREATE TABLE orders (id INT, customer TEXT, total INT, status TEXT);

INSERT INTO orders VALUES
 (1,'Asha',1200,'paid'), (2,'Ben',300,'pending'),
 (3,'Asha',800,'paid'), (4,'Chitra',2500,'shipped'),
 (5,'Dev',150,'cancelled'), (6,'Ben',NULL,'pending');

请注意,订单 6 没有总计。该行稍后很重要。

搜索案例

这是更灵活的形式,因为每个 WHEN 都有自己的完整条件:

SELECT id, total,
       CASE
         WHEN total >= 1000 THEN 'large'
         WHEN total >= 500 THEN 'medium'
         ELSE 'small'
       END AS size
FROM orders;

-- 1 | 1200 | large
-- 2 |  300 | small
-- 3 |  800 | medium
-- 4 | 2500 | large
-- 5 |  150 | small
-- 6 | NULL | small

看最后一行。订单 6 没有总数,但标记为“小”。与 NULL 的两次比较都不正确,因此落到了 ELSE。我们展示了如何在 NULL 部分修复此问题。始终使用 AS 为结果命名,否则您可能会得到一个尴尬的列名称。

简单案例

当您将一列与固定值进行比较时,较短的形式读起来更好:

SELECT id,
       CASE status
         WHEN 'paid' THEN 'Complete'
         WHEN 'shipped' THEN 'Complete'
         WHEN 'pending' THEN 'Waiting'
       END AS label
FROM orders;

-- 1 | Complete
-- 2 | Waiting
-- 3 | Complete
-- 4 | Complete
-- 5 | NULL
-- 6 | Waiting
形式 比较 可以使用范围吗? 最适合
简单 平等的一种表达 否 将代码映射到标签
已搜索 每个分支的任何条件 是的 范围、多列、NULL 检查

订单事宜

CASE 在第一个为真条件时停止,这是最常见的逻辑错误。交换我们之前查询中的两个条件:

SELECT id, total,
       CASE
         WHEN total >= 500 THEN 'medium'
         WHEN total >= 1000 THEN 'large'
         ELSE 'small'
       END AS size
FROM orders WHERE id IN (1, 4);

-- 1 | 1200 | medium
-- 4 | 2500 | medium

现在没有什么可以是“大”的。总共 2500 也是至少 500,因此第一个分支获胜,而第二个分支永远不会达到。没有出现错误,只有错误的标签。对于范围,将最严格的条件放在前面,或者写出完整的范围,例如 total >= 500 AND total < 1000。

没有 ELSE 会发生什么?

在上面的简单 CASE 示例中,订单 5 被取消,并且没有分支处理该问题,因此结果是 NULL。有时这正是您想要的,如下一节所示。但在构建标签时,清晰的 ELSE 'other' 通常更好,因为它使意外的值可见,而不是在报告中留下空白。

提示:所有分支应返回相同的类型。在 SQLite 中混合文本和数字的工作方式比较松散,但更严格的数据库(例如 PostgreSQL)会引发错误,或者尝试以您不希望的方式转换值。

使用 SUM 的数据透视式报告

这是日常 SQL 中最有用的模式之一。将 CASE 包装在聚合中会将行转换为列,每个客户一行:

SELECT customer,
       SUM(CASE WHEN status = 'paid' THEN total ELSE 0 END) AS paid,
       SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_orders
FROM orders
GROUP BY customer
ORDER BY customer;

-- Asha   | 2000 | 0
-- Ben    |    0 | 2
-- Chitra |    0 | 0
-- Dev    |    0 | 0

现在,一个查询即可提供已付费收入和挂单数量,无需单独的查询或联接。该模式几乎适用于所有数据库,与只有某些数据库提供的专用 PIVOT 语法不同。如果 GROUP BY 对您来说是新的,请阅读 SQL GROUP BY 和 HAVING。

条件计数

COUNT 会跳过 NULL,因此没有 ELSE 的 CASE 仅计算匹配的行:

SELECT COUNT(*) AS all_orders,
       COUNT(CASE WHEN status = 'paid' THEN 1 END) AS paid_orders,
       COUNT(CASE WHEN total >= 1000 THEN 1 END) AS large_orders
FROM orders;

-- 6 | 2 | 2

请注意:如果在 COUNT 内添加 ELSE 0,则每一行都会被计数,因为 0 不为 NULL。将 ELSE 0 与 SUM 一起使用,并与 COUNT 一起省略。 PostgreSQL 和 SQLite 也支持 COUNT(*) FILTER (WHERE ...),读起来很好,但并不是所有地方都可用。

按 CASE 分组

您可以创建自己的存储桶,然后总结它们:

SELECT CASE WHEN total >= 1000 THEN 'large' ELSE 'other' END AS bucket,
       COUNT(*)
FROM orders
GROUP BY bucket
ORDER BY bucket;

-- large | 2
-- other | 4

按别名分组适用于 SQLite、PostgreSQL 和 MySQL。 SQL Server 不允许这样做,因此您必须重复整个表达式,或者首先将查询放入 CTE 或子查询中。

使用 ORDER BY 自定义排序

按字母顺序排列很少是企业想要的。 CASE 允许您定义自己的顺序:

SELECT id, status FROM orders
ORDER BY CASE status
           WHEN 'pending' THEN 1
           WHEN 'paid' THEN 2
           ELSE 3
         END, id;

-- 2 | pending
-- 6 | pending
-- 1 | paid
-- 3 | paid
-- 4 | shipped
-- 5 | cancelled

待处理订单排在第一位,这对于工作队列来说很方便。第二个排序列 id 使每组中的行保持可预测的顺序。

更新中的案例

CASE 允许一条语句将不同的更改应用于不同的行。此处,小额挂单被标记以供审核:

UPDATE orders
SET status = CASE
               WHEN status = 'pending' AND total < 500 THEN 'review'
               ELSE status
             END;

SELECT id, status FROM orders WHERE customer = 'Ben';

-- 2 | review
-- 6 | pending

ELSE status 保持其他行不变。 忽略它是危险的,因为不匹配的行将被设置为 NULL。如果没有 WHERE 子句,此语句也会触及每一行,因此在大型表上添加一个过滤器,并考虑将更改包装在 事务 中,以便您可以在提交之前检查结果。还要注意,顺序 6 没有改变,因为它的 NULL 总数不满足 total < 500。

空陷阱

NULL 导致 CASE 出现两个不同的问题。首先,简单形式使用相等进行比较,没有任何东西等于 NULL,甚至 NULL 本身也不等于:

SELECT CASE NULL WHEN NULL THEN 'matched' ELSE 'not matched' END;

-- not matched

所以 CASE col WHEN NULL 永远不会匹配。请使用带有 IS NULL 的搜索形式。其次,正如我们在顺序 6 中看到的那样,NULL 值跳过了比较并落在 ELSE 中。修复方法是首先检查它:

SELECT id,
       CASE
         WHEN total IS NULL THEN 'unknown'
         WHEN total >= 1000 THEN 'large'
         ELSE 'other'
       END AS size
FROM orders WHERE id IN (1, 2, 6);

-- 1 | large
-- 2 | other
-- 6 | unknown

Order 6 现在获得了诚实标签。我们的 SQL NULL 值指南 完整地涵盖了这种行为。

CASE 与其他选项

选项 工作于 当
案例 所有主要数据库 你想要便携式逻辑
合并 所有主要数据库 您只需要 NULL 的后备
IF() MySQL 简单的双向选择
IIF() SQL Server、SQLite 简单的双向选择
DECODE() 甲骨文 较旧的 Oracle 代码
查找表 全部 映射很长或经常更改

最后一个选择值得考虑。如果 CASE 增长到 30 个分支并出现在 5 个查询中,请将映射移动到表中并 join 到它。那么更改就是单行更新,而不是五个地方的代码更改。

常见错误

  • 条件顺序错误。 宽泛的条件首先隐藏了较窄的条件。
  • 忘记END。这是最常见的语法错误。
  • 使用WHEN NULL。它永远不会匹配,所以使用WHEN col IS NULL。
  • 在COUNT中添加ELSE 0。它计算所有行。
  • 在更新中省略ELSE。它可以用NULL擦除值。
  • WHERE中按CASE过滤。它可以阻止索引被使用。请参阅我们的 SQL 查询优化指南。

最后的话

CASE WHEN 是一个小功能,但可以完成大量工作。请记住三个规则:第一个 true 分支获胜,不匹配的行在没有 ELSE 的情况下变为 NULL,并且 NULL 需要自己的检查。为了进行练习,请获取您当前使用多个查询构建的报告,并尝试使用 SUM(CASE ...) 将其生成为一个查询。

常见问题

SQL 中 CASE WHEN 的作用是什么?

它将 if-then 逻辑添加到查询中。 SQL 按顺序检查每个条件并返回第一个为 true 的值,如果没有则返回 ELSE 值。

如果 CASE 语句中没有 ELSE 会发生什么?

不匹配任何条件的行返回 NULL。这在 COUNT 中很有用,但在标签和更新中可能会导致空白或数据丢失。

我可以在 WHERE 子句中使用 CASE 吗?

是的,因为它是一种表达。然而,简单的 AND 和 OR 条件通常更清晰,并且更有可能使用索引。

简单CASE和搜索CASE有什么区别?

简单 CASE 将一个表达式与固定值进行比较以确定是否相等。搜索的 CASE 在每个 WHEN 中都有完整的条件,因此它可以处理范围和 NULL 检查。

CASE语句可以嵌套吗?

是的,一个 CASE 可以出现在另一个 CASE 中。不过,深层嵌套很难阅读,因此请考虑使用 AND 组合条件或使用查找表。

继续学习

与Stack Quora合作

需要测试、WordPress 或 SEO 方面的帮助?

我们帮助团队交付更好的软件并发展他们的网站。告诉我们您正在做什么,我们将建议明确的下一步。

继续阅读

更多指南 测试、人工智能和现代开发

喜欢这篇文章吗?选择一个主题并直接跳至我们最新的教程。

人工智能

人工智能幻觉解释:语言模型为何会产生幻觉以及如何减少它

人工智能幻觉解释:它们是什么,为什么语言模型产生它们,常见类型,如何通过基础和提示来减少它们,以及我们进行的简单检查。

阅读文章 →
DevOps

环境变量解释:.env 文件、秘密和配置正确完成

使用我们运行的 Python 示例解释环境变量:读取和设置它们、为什么每个值都是字符串、.env 文件、进程继承、秘密和验证。

阅读文章 →
软件开发

API 速率限制解释:算法、标头和处理 429 错误

API 速率限制通过我们运行的模拟进行了解释:令牌桶、固定窗口、滑动窗口、429 响应、速率限制标头、带回退的重试和测试限制。

阅读文章 →
测试自动化

Python 中的模拟:unittest.mock、补丁以及导致测试谎言的错误

使用unittest.mock 在Python 中进行模拟,经过测试:模拟和return_value、side_effect、修补使用名称的位置、spec 和autospec、模拟日期以及常见错误。

阅读文章 →
软件测试

代码覆盖率解释:行覆盖率与分支覆盖率以及 100% 的真正含义

用真实的coverage.py运行来解释代码覆盖率:行、分支和条件覆盖率,阅读报告,为什么100%覆盖率仍然会错过错误,以及合理的目标。

阅读文章 →
数据库与 SQL

SQL 中的 DELETE、TRUNCATE 和 DROP:差异、回滚以及何时使用它们

DELETE、TRUNCATE 和 DROP 通过经过测试的 SQL 进行了解释:各自删除什么、回滚、触发器、身份重置、速度,以及 PostgreSQL、MySQL、SQL Server 和 SQLite 有何不同。

阅读文章 →
数据库与 SQL

SQL 视图解释:创建、使用和避免陷阱(附示例)

SQL 视图通过我们运行的查询进行解释:创建视图、实时结果、隐藏列、性能、可更新和物化视图、破坏的依赖关系和最佳实践。

阅读文章 →
软件开发

TypeScript 接口与类型:真正的差异,使用编译器进行测试

TypeScript 接口与类型,使用真实编译器进行检查:两者都可以做什么,只有一个可以做什么,声明合并,扩展与交集以及要遵循的简单规则。

阅读文章 →
软件开发

Python *args 和 **kwargs 解释:灵活的函数和清晰的示例

Python *args 和 **kwargs 用我们运行的代码进行了解释:打包和解包、参数顺序、仅关键字和仅位置参数、装饰器和常见错误。

阅读文章 →
软件开发

Python Lambda 函数:语法、实际用途以及何时不使用它们

Python lambda 函数用我们运行的代码进行了解释:语法、使用键排序、映射和过滤器、闭包、后期绑定陷阱,以及何时普通 def 是更好的选择。

阅读文章 →
人工智能

什么是人工智能代理?它们是如何工作的,只需运行一个简单的代理循环

什么是人工智能代理?关于代理循环、工具、内存和护栏的简单指南,以及我们运行的一个小型 Python 循环、真正的风险以及何时简单的聊天机器人是更好的选择。

阅读文章 →
DevOps

监控与可观察性:真正的区别,包括日志、指标和跟踪

监控与可观察性解释:已知和未知的故障、日志、指标和跟踪、检查事件演练、黄金信号、OpenTelemetry 和有用的警报。

阅读文章 →
DevOps

Docker Volumes 与 Bind Mounts:以正确的方式保留容器数据

Docker 卷与绑定挂载解释:为什么容器数据消失、命名卷、绑定挂载、tmpfs、Compose 示例、备份、权限以及何时使用它们。

阅读文章 →
测试自动化

TDD 与 BDD:主要区别、以两种方式构建的一个功能以及何时使用每种方式

TDD 与 BDD 的解释是通过一种双向构建的折扣功能:使用我们运行的代码进行红绿重构、Given-When-Then 场景、工具、权衡以及何时使用每种方案。

阅读文章 →
软件测试

用户验收测试 (UAT):流程、清单和真实示例

用户验收测试解释:谁运行它、它与 QA 有何不同、七步流程、UAT 测试用例模板、清单、签核标准和常见错误。

阅读文章 →
数据库与 SQL

SQL NULL 值解释:IS NULL、COALESCE、NULLIF 和要避免的陷阱

通过我们运行的查询解释了 SQL NULL 值:三值逻辑、IS NULL、COALESCE、NULLIF,聚合、连接和排序如何处理 NULL,以及它悄悄引起的错误。

阅读文章 →
数据库与 SQL

SQL CASE WHEN 语句:语法、实例和常见错误

SQL CASE WHEN 解释了我们运行的查询:简单和搜索表单、条件顺序、SUM 透视、COUNT 技巧、自定义排序、更新和要避免的 NULL 陷阱。

阅读文章 →
软件开发

什么是智威汤逊?通过工作示例解释 JSON Web 令牌

什么是智威汤逊? JSON Web Tokens 解释:这三个部分,在我们运行的代码中构建和篡改的令牌,JWT 与会话,存储,刷新令牌和安全错误。

阅读文章 →
软件开发

JavaScript 事件循环解释:调用堆栈、微任务和计时器以及示例

JavaScript 事件循环用我们运行的代码进行了解释:调用堆栈、任务和微任务队列、为什么 setTimeout 0 不是立即执行、阻塞、饥饿和面试测验。

阅读文章 →
软件开发

Python 生成器和 Yield 解释:通过真实示例节省内存

Python 生成器和yield 用我们运行的代码进行了解释:它们如何暂停和恢复、测量内存节省、管道、无限序列、yield from、发送和常见陷阱。

阅读文章 →
人工智能

什么是嵌入?向量和余弦相似度简单解释

什么是嵌入?关于向量、余弦相似度和语义搜索的简单语言指南,其中包含我们运行的一个小示例、常见用途、选择模型和已知限制。

阅读文章 →
DevOps

蓝绿部署与金丝雀部署:差异、权衡以及如何选择

蓝绿部署与金丝雀部署解释:每种部署的工作原理、成本、风险和回滚、我们测试的稳定用户分桶、数据库迁移、滚动更新以及如何选择。

阅读文章 →
测试自动化

不稳定的测试:造成这些问题的原因以及如何彻底解决它们

我们运行的演示解释了不稳定的测试:主要原因、如何确认和诊断、正确的时间修复、共享状态和日期以及隔离过程。

阅读文章 →
软件测试

负载测试与压力测试:差异、指标和 k6 示例

负载测试与压力测试的解释:各自证明什么、峰值和浸泡测试、重要的指标、为什么平均值会误导、k6 个示例以及如何计划测试。

阅读文章 →
软件测试

测试用例与测试场景:主要区别、示例以及何时使用它们

测试用例与测试场景的解释:清晰的定义、并排的表格、从场景到案例的密码重置功能、模板以及何时使用每个功能。

阅读文章 →
数据库与 SQL

SQL 查询优化:读取执行计划并修复慢速查询(带计时)

对一百万行进行 SQL 查询优化测试:读取执行计划、添加正确的索引、修复不可控制的过滤器、使用复合索引和键集分页。

阅读文章 →
数据库与 SQL

SQL UNION 与 UNION ALL:差异、示例和性能测试

SQL UNION 与 UNION ALL 通过我们运行的查询进行了解释:重复、都遵循的规则、真实的数据错误、测量的性能、INTERSECT 和 EXCEPT 以及何时使用它们。

阅读文章 →
软件开发

JavaScript Fetch API 教程:GET、POST、错误处理和超时

针对本地服务器测试的 JavaScript Fetch API 教程:GET 和 POST、为什么不抛出 404、可重用的帮助程序、查询字符串、超时、CORS 和常见错误。

阅读文章 →
软件开发

Python 文件处理:读取、写入、CSV、JSON 和 pathlib 以及示例

使用我们运行的代码解释了 Python 文件处理:打开模式、使用、读取大文件、CSV、JSON、pathlib、二进制文件、安全写入、错误处理和常见错误。

阅读文章 →
软件开发

Python 类和对象:带有实际示例的 OOP 教程

用我们运行的代码解释 Python 类和对象:__init__、方法、类与实例属性、继承、属性、dunder 方法、数据类和常见错误。

阅读文章 →
人工智能

什么是大语言模型?法学硕士如何运作,在没有炒作的情况下进行解释

什么是大语言模型?法学硕士用简单的语言解释:标记、嵌入、注意力、训练、温度和上下文窗口、一个微小的下一个单词演示和真正的限制。

阅读文章 →
DevOps

适合初学者的 Linux 命令:带有示例的基本终端指南

适合初学者的 Linux 命令,按任务分组:导航、文件、查看日志、grep、管道、权限、进程和档案,并附有实用的日志分析示例。

阅读文章 →
测试自动化

Selenium 与 Playwright:主要区别、代码比较以及选择

Selenium 与 Playwright 比较:架构、等待、浏览器和语言支持、调试、两种工具中的相同测试、迁移建议以及如何选择。

阅读文章 →
软件测试

等价划分:通过实例解释测试设计技术

等效分区解释:如何找到有效和无效分区,年龄、密码和优惠券的工作示例,我们运行的测试,以及为什么将其与边界配对。

阅读文章 →
软件测试

软件测试中的严重性与优先级:差异、矩阵和实例

软件测试中的严重性与优先级解释:清晰的定义、带有示例的四向矩阵、级别尺度、谁设置每个级别以及如何运行错误分类。

阅读文章 →
数据库与 SQL

SQL 子查询教程:类型、相关子查询和 EXISTS 以及示例

SQL 子查询教程,其中包含我们运行的查询:标量、IN、FROM 和 SELECT 子查询、相关子查询、EXISTS、NOT IN NULL 陷阱以及子查询与 JOIN 与 CTE。

阅读文章 →
数据库与 SQL

SQL 事务和 ACID 解释:COMMIT、ROLLBACK 和隔离级别

SQL 事务和 ACID 通过我们运行的银行转账进行了解释:COMMIT、ROLLBACK、保存点、锁、隔离级别、脏读、死锁和最佳实践。

阅读文章 →
软件开发

Git merge 与 rebase 的区别:示例及如何选择

用真实的 log 输出、对比表、rebase 黄金法则和安全的团队工作流,讲清 Git merge 与 rebase 的区别。

阅读文章 →
软件开发

Python 列表推导式:语法、示例与何时避免使用

Python 列表推导式详解:语法、过滤、if-else、嵌套循环、实测耗时,以及何时普通 for 循环更合适。

阅读文章 →
数据库与 SQL

SQL JOIN 详解:附示例(INNER、LEFT、RIGHT、FULL)

通过示例表和查询,一步步讲解 SQL JOIN:INNER、LEFT、RIGHT、FULL、CROSS 和 SELF,并介绍常见错误与性能技巧。

阅读文章 →