不同数据库中的 SQL 大小写敏感性
第 1.9 课 · 阅读时间:约 9 分钟
SQL 的大小写敏感性分为三个层面:关键字、对象名称和数据。像 SELECT 这样的关键字在任何数据库中都不区分大小写,但 Customer 和 customer 是不是同一张表,取决于数据库、引号,有时甚至取决于操作系统。在本课中,你将了解各主流数据库的规则、带引号名称的经典陷阱、字符串比较如何对待大小写,以及一种能避开所有这些问题的命名风格。
在第 1.8 课中,你看到各数据库在遵循 SQL 标准的同时都有自己的方言。大小写是最明显的差异之一:在 SQL Server 中能运行的查询,在 PostgreSQL 中可能报错 relation "Customer" does not exist;在 Windows 笔记本上写的查询,到了 Linux 服务器上可能无法运行。
在写第一批查询之前了解这些规则,可以省下好几个小时去找一张“明明存在”的表。
SQL 中大小写的三个层面
当有人问“SQL 区分大小写吗?”时,可能指的是三件不同的事:
- 关键字和函数名:
SELECT、select、COUNT、count。 - 标识符:数据库、表、列和别名的名称,你在第 1.7 课中已经接触过。
- 数据:在
WHERE条件中,字符串'abc'是否等于'ABC'。
每个层面都有自己的规则,我们逐一来看。
SQL 关键字不区分大小写
在所有数据库中,关键字和内置函数名都可以用任意大小写书写:
sElEcT title
FrOm film
WhErE film_id = 1;
结果:ACADEMY DINOSAUR。MySQL、PostgreSQL、SQL Server、Oracle、Firebird 和 SQLite 都能执行大小写任意混合的查询。
把关键字写成大写只是一种约定:它把语言本身和表名、列名区分开,让长查询更易读。本课程用大写书写关键字、用小写书写名称,大多数风格指南也是这样建议的。
不带引号的标识符:每个数据库的转换方式不同
SQL 标准规定,不带引号的名称会转换为大写,因此 customer、Customer 和 CUSTOMER 是同一个名称。各数据库以不同方式实现这一规则。
PostgreSQL:名称变成小写
PostgreSQL 会把每个不带引号的名称转换为小写,与标准正好相反:
SELECT Model, AIRCRAFT_CODE
FROM Aircrafts
WHERE aircraft_code = '773';
结果:Boeing 777-300。表以 aircrafts 的名称保存,列标题返回为 model 和 aircraft_code。
别名同样会被转换:SELECT model AS TotalModel 返回名为 totalmodel 的列。你输入的大小写在查询解析时就消失了。
Oracle 和 Firebird:名称变成大写
Oracle 和 Firebird 遵循标准,把不带引号的名称转换为大写:
select first_name, Last_Name
from Employee
where emp_no = 2;
Firebird 中的结果:Robert Nelson,列标题为 FIRST_NAME 和 LAST_NAME。在 Oracle 中使用 hr.Employees 也是一样。
这就是为什么各种工具都用大写字母显示 Oracle 和 Firebird 的表名:EMPLOYEES、EMPLOYEE。
SQL Server:由排序规则决定
SQL Server 完全不转换名称。它保留 CREATE TABLE 中使用的大小写,并按照数据库的排序规则 (collation)(比较文本的一组规则)来比较名称。
默认排序规则(例如 SQL_Latin1_General_CP1_CI_AS)不区分大小写 (CI),因此下面这些名称是相同的:
select name, listprice
from PRODUCT
where productid = 680;
结果:HL Road Frame - Black, 58。在 AdventureWorks 中,列标题按你输入的样子返回:name 和 listprice。
使用区分大小写的排序规则 (_CS_) 创建的数据库会把 Product 和 PRODUCT 视为不同的名称。这样的数据库很少见,但确实存在,所以在做任何假设之前,先检查 DATABASEPROPERTYEX(DB_NAME(), 'Collation')。
MySQL 和 MariaDB:表名取决于操作系统
在 MySQL 和 MariaDB 中,数据库是一个文件夹,表是一个文件,所以数据库和表的大小写规则来自文件系统。由 lower_case_table_names 设置控制:
| 值 | 默认于 | 表名 |
|---|---|---|
0 | Linux | 按书写保存,比较时区分大小写 |
1 | Windows | 以小写保存,比较时不区分大小写 |
2 | macOS | 按书写保存,比较时不区分大小写 |
本课程的练习环境运行在 Linux 上,因此表名大小写写错就会报错:
SELECT title
FROM FILM
WHERE film_id = 1;
结果:Table 'sakila.FILM' doesn't exist。改成 FROM film 后查询就能运行。
在 Linux 上,Customer 和 customer 甚至可以是同一个数据库中的两张不同的表。表别名也遵循同样的规则:FROM film F 配合 WHERE f.film_id = 1 会报错 Unknown column 'f.title'。
而 MySQL 和 MariaDB 中的列名在任何系统上都不区分大小写:SELECT TITLE, Film_ID FROM film 可以正常运行。
SQLite 和 DuckDB:始终不区分大小写
SQLite 和 DuckDB 比较名称时不区分大小写,即使带引号也一样,并会记住创建对象时使用的大小写:
CREATE TEMP TABLE "Customer" ("FullName" text);
INSERT INTO "Customer" VALUES ('Orlando Gee');
SELECT fullname
FROM CUSTOMER;
SQLite 和 DuckDB 中的结果:Orlando Gee,列标题为 FullName。无法再创建名为 customer 的第二张表:这个名称已被占用。
带引号的标识符保留大小写
引号会把名称变成定界标识符 (delimited identifier):数据库会完全按照书写的样子使用它,包括大小写、空格和保留字。标准使用双引号;MySQL 使用反引号,SQL Server 还接受方括号:
| 数据库 | 带引号的名称 |
|---|---|
| PostgreSQL、Oracle、Firebird、SQLite、DuckDB | "Customer" |
| MySQL、MariaDB | `Customer` |
| SQL Server | [Customer] 或 "Customer" |
在 PostgreSQL、Oracle 和 Firebird 中,带引号的名称区分大小写。这正是最常见的大小写错误的根源。
PostgreSQL 中的 "Customer" 陷阱
许多工具、ORM 和迁移脚本会用带引号的名称创建表。接下来会发生这样的事:
CREATE TEMP TABLE "Customer" ("CustomerID" int, "FullName" text);
INSERT INTO "Customer" VALUES (1, 'Orlando Gee');
SELECT *
FROM Customer;
结果:relation "customer" does not exist。不带引号的 Customer 变成了 customer,而表名是 Customer。
一旦名称是用引号和大写字母创建的,每个查询都必须同样给它加上引号,并保持完全相同的大小写:
SELECT "FullName"
FROM "Customer";
结果:Orlando Gee。不加引号时,SELECT FullName 会报错 column "fullname" does not exist。
PostgreSQL 甚至会给出提示:Perhaps you meant to reference the column "Customer.FullName"。Oracle 和 Firebird 的问题正好相反:hr."employees" 会报错,因为表以 EMPLOYEES 的名称保存。
在 MySQL 中双引号表示字符串
在 MySQL 和 MariaDB 中,双引号根本不表示名称:默认情况下它和单引号一样表示字符串:
SELECT "title"
FROM film
WHERE film_id = 1;
结果:单词 title,而不是电影标题。MySQL 为每一行返回文本常量 'title'。
查询不会报错,所以这个错误很容易被忽略。在 MySQL 中请用反引号书写名称,或者开启 ANSI_QUOTES 模式,让双引号按标准工作。
数据中的大小写:比较字符串
第三个层面是数据本身。'abc' = 'ABC' 是否为真,取决于数据库和列的排序规则:
| 数据库 | 默认 'abc' = 'ABC' | LIKE | 不区分大小写的搜索 |
|---|---|---|---|
| MySQL、MariaDB | 真 | 不区分大小写 | 默认即是;精确比较用 BINARY 或 _bin 排序规则 |
| SQL Server | 真(CI 排序规则) | 不区分大小写 | 默认即是;精确比较用 COLLATE ..._CS_AS |
| PostgreSQL | 假 | 区分大小写 | ILIKE、LOWER() |
| Oracle | 假 | 区分大小写 | UPPER()、LOWER() |
| Firebird | 假 | 区分大小写 | CONTAINING、UPPER() |
| SQLite | 假 | 不区分大小写(拉丁字母) | COLLATE NOCASE、LOWER() |
| DuckDB | 假 | 区分大小写 | ILIKE、COLLATE NOCASE |
同一个条件会得到不同的结果。在 PostgreSQL 的 Bookings 数据库中,没有城市 'moscow':
SELECT COUNT(*)
FROM airports
WHERE city = 'moscow';
结果:0。使用 WHERE lower(city) = 'moscow' 或 WHERE city ILIKE 'moscow',查询能找到莫斯科的 3 个机场。
在 MySQL 中,对 Sakila 执行类似的查询会直接找到演员:
SELECT COUNT(*)
FROM actor
WHERE last_name = 'davis';
结果:3,尽管姓氏是以 DAVIS 保存的。使用 WHERE BINARY last_name = 'davis' 时结果为 0。
所以,从 MySQL 或 SQL Server 迁移到 PostgreSQL 的查询可能会悄无声息地少返回一些行。如果数据的大小写不重要,就在查询中明确说明:用 LOWER(列) 与小写值比较。排序规则的作用远不止大小写:它还决定排序和重音符号的处理,课程后面会有专门的一课。
各数据库的大小写规则
| 数据库 | 不带引号的名称 | 带引号的名称 | 'abc' = 'ABC' |
|---|---|---|---|
| PostgreSQL | 小写 | 区分大小写 | 假 |
| Oracle | 大写 | 区分大小写 | 假 |
| Firebird | 大写 | 区分大小写 | 假 |
| SQL Server | 按书写保存,按排序规则比较(通常不区分大小写) | 与不带引号相同 | 使用 CI 排序规则时为真 |
| MySQL、MariaDB | 表和数据库:取决于 lower_case_table_names(在 Linux 上区分大小写);列:不区分大小写 | 与不带引号相同 | 真 |
| SQLite | 不区分大小写 | 不区分大小写 | 假 |
| DuckDB | 不区分大小写 | 不区分大小写 | 假 |
在所有这些数据库中,关键字和函数名都不区分大小写。
避免大小写问题的命名规则
只要遵守几条规则,就不必记住整张表:
- 表名和列名使用小写字母加下划线:
customer_order、total_amount。这样的名称在任何数据库中含义都相同,永远不需要引号。 - 不要创建带大写字母或空格的带引号名称。如果是工具创建的,就在所有地方用完全相同的大小写给它加上引号。
- 每个名称在所有地方都用同样的大小写书写,即使数据库允许不一致:这样在 Windows 或 SQL Server 中写的查询,在 Linux 和 PostgreSQL 中也能运行。
- 为了可读性用大写书写关键字;数据库并不在意。
- 在文本条件中,先决定大小写是否重要,并用
LOWER()、UPPER()或ILIKE明确写出来,而不是依赖默认的排序规则。
更多命名建议汇总在第 10.1 课中。记住这些规则,你就可以进入下一个模块,开始用 SELECT 查询数据了。
要点:
- SQL 关键字和函数名在任何数据库中都不区分大小写;大写只是一种风格约定。
- 不带引号的名称在 PostgreSQL 中转换为小写,在 Oracle 和 Firebird 中转换为大写;SQL Server 按数据库的排序规则比较它们。
- 在 MySQL 和 MariaDB 中,表名在 Linux 上区分大小写(
lower_case_table_names = 0),列名则不区分。 - 带引号的名称保留大小写:在 PostgreSQL 中以
"Customer"创建的表,必须始终写成"Customer"。 'abc' = 'ABC'在 MySQL 和 SQL Server 中默认为真,在 PostgreSQL、Oracle、Firebird、SQLite 和 DuckDB 中为假。- 不带引号的小写
snake_case名称在任何地方都表现一致。
常见问题
SQL 区分大小写吗?
SQL 关键字不区分:SELECT 和 select 在任何地方都一样。表名和列名取决于数据库:PostgreSQL、Oracle 和 Firebird 会把不带引号的名称转换为统一的大小写,SQL Server 使用排序规则,MySQL 的表名取决于操作系统。字符串比较取决于数据的排序规则。
MySQL 中的表名区分大小写吗?
在 Linux 上区分:在默认的 lower_case_table_names = 0 下,FILM 和 film 是不同的表。在 Windows 和 macOS 上它们是同一张表。列名在任何系统上都不区分大小写,所以为了保险,表名请使用小写。
为什么表明明存在,PostgreSQL 却说 relation does not exist?
这张表很可能是用带引号、含大写字母的名称创建的,例如 "Customer"。PostgreSQL 会把你查询中不带引号的 Customer 转换为 customer,这是另一个名称。请用双引号并保持完全相同的大小写书写名称:FROM "Customer"。
如何在 PostgreSQL 中进行不区分大小写的搜索?
用 ILIKE 代替 LIKE(WHERE city ILIKE 'moscow'),或者比较小写后的值:WHERE lower(city) = 'moscow'。对于频繁的搜索,在 lower(city) 上建立索引可以让第二种写法更快。
SQL 关键字应该大写吗?
数据库并不在意,所以这是风格问题。关键字大写、名称小写是最常见的约定,因为这样查询的结构一目了然。重要的是在一个项目中保持一致。