【SQL NULL入门】掌握IS NULL / IS NOT NULL,精通数据提取
开始学习SQL时,你必然会遇到一个神秘的存在,那就是`NULL`。它用于表示数据不存在的状态,例如“电话号码未注册的用户”或“备注栏中没有任何内容的产品”。然而,处理`NULL`的方式非常特殊,许多初学者都会遇到“为什么无法按预期获取数据…”的难题。
最大的原因是,人们常常试图用=(等于)来比较`NULL`,就好像它与“0”或“空字符串('')”是同样的东西。实际上,在SQL的世界里,存在一条哲学性的规则:`NULL`不等于任何其他值,甚至不等于`NULL`自身。
本文将通过可即拷即用的代码,从`NULL`的本质,到用于正确处理它的专用运算符`IS NULL`和`IS NOT NULL`,进行全面而详尽的解说。毫不夸张地说,掌握了`NULL`,就等于掌握了SQL。让我们借此机会,切实掌握数据存在性检查的技能吧!
准备工作:创建包含NULL的会员数据
为了实际确认`NULL`的行为,我们首先需要准备一些示例数据。这次,我们将创建一个简单的`members`表来管理会员信息。关键在于特意注册一些包含`NULL`(数据为空的状态)的记录,比如电话号码或最后登录日期。
-- 如果members表已存在,则删除(以便重复测试)
DROP TABLE IF EXISTS members;
-- 创建一个新的members表
CREATE TABLE members (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
phone_number TEXT, -- 允许NULL
last_login DATE, -- 允许NULL
points INTEGER DEFAULT 0
);
-- 插入初始数据
INSERT INTO members (id, name, email, phone_number, last_login, points) VALUES
(1, '山田 太郎', 'yamada@example.com', '090-1111-2222', '2025-06-25', 150),
(2, '鈴木 花子', 'suzuki@example.com', NULL, '2025-07-01', 50),
(3, '佐藤 次郎', 'sato@example.com', '080-3333-4444', NULL, 200),
(4, '伊藤 さくら', 'ito@example.com', NULL, NULL, 0),
(5, '渡辺 健太', 'watanabe@example.com', '', '2025-06-28', 300); -- 电话号码为空字符串
这些数据包括电话号码为`NULL`的用户(ID 2, 4)和最后登录日期为`NULL`的用户(ID 3, 4)。另外,请注意ID为5的渡边先生的电话号码不是`NULL`,而是一个**空字符串**('')。这个区别稍后会变得很重要。
最大的陷阱:为什么不能用 `= NULL` 进行搜索?
毫不夸张地说,SQL初学者百分之百会掉进这个坑里,那就是写出WHERE phone_number = NULL这样的语句。虽然看起来似乎能正常工作,但执行这个查询后,一条数据也返回不了。
-- 【错误!】这个查询不会按预期工作
SELECT * FROM members WHERE phone_number = NULL;
这是为什么呢?因为`NULL`表示的是“没有值、未知”这样一个特殊的状态。由于`NULL`不是一个值,所以它与任何值进行比较(甚至与`NULL`自身比较),结果既不是`TRUE`也不是`FALSE`,而是第三种状态——`UNKNOWN`(未知)。WHERE子句只提取条件为`TRUE`的记录,因此结果为`UNKNOWN`的记录永远不会被选中。
这就像在一张调查问卷上,对于“喜欢的食物”这个问题,你想找那些“未填写”的人,你不能通过“寻找那些写了‘未填写’三个字的人”的方法来找到他们。你需要寻找的是“未填写”这个状态本身。
【基础】`IS NULL` 和 `IS NOT NULL` 的正确用法
SQL为判断`NULL`这个特殊状态准备了专用的运算符,那就是`IS NULL`和`IS NOT NULL`。
`IS NULL`:提取值为NULL的记录
如果你想查找某一列的值为`NULL`(数据为空)的记录,应该使用`IS NULL`,而不是= NULL。
场景:“查找没有注册电话号码(`phone_number`为`NULL`)的会员。”
SELECT * FROM members WHERE phone_number IS NULL;
执行结果:
id | name | email | phone_number | last_login | points
---|-----------|-------------------|--------------|------------|-------
2 | 鈴木 花子 | suzuki@example.com| NULL | 2025-07-01 | 50
4 | 伊藤 さくら | ito@example.com | NULL | NULL | 0
这次,ID为2和4的用户被正确地提取出来了,符合预期。请注意,ID为5的渡边先生没有出现在结果中,因为他的电话号码是“空字符串”,而不是`NULL`。
`IS NOT NULL`:提取值不为NULL的记录
反之,如果你想查找值不为空,即有数据录入的记录,就使用IS NOT NULL。
场景:“查找有最后登录记录(`last_login`不为`NULL`)的会员。”
SELECT * FROM members WHERE last_login IS NOT NULL;
执行结果:
id | name | email | phone_number | last_login | points
---|-----------|---------------------|----------------|------------|-------
1 | 山田 太郎 | yamada@example.com | 090-1111-2222 | 2025-06-25 | 150
2 | 鈴木 花子 | suzuki@example.com | NULL | 2025-07-01 | 50
5 | 渡辺 健太 | watanabe@example.com| | 2025-06-28 | 300
像这样,使用`IS NOT NULL`就可以高效地只选出那些没有数据缺失的记录。
应用:理解`NULL`与“空字符串”、“0”的区别
初学者容易将“空字符串('')”和“数字0”与`NULL`混为一谈。在SQL中,它们被明确区分为不同的东西。
- `NULL`: 表示“未知”或“不存在”的状态。
- 空字符串(
''): 是一个长度为0的字符串“值”。 0: 是一个数值为零的“值”。
ID为5的渡边先生的电话号码是空字符串,而不是`NULL`。因此,下面的查询只会匹配到ID为5的用户。
SELECT * FROM members WHERE phone_number = '';
同样,ID为4的伊藤女士的积分是`0`,这也与`NULL`不同。使用像`COUNT`这样的聚合函数时,这个区别会更加明显。
-- 总会员数、已注册电话号码的会员数、以及积分总和
SELECT COUNT(*), COUNT(phone_number), SUM(points) FROM members;
执行结果:
COUNT(*) | COUNT(phone_number) | SUM(points)
--------|---------------------|------------
5 | 3 | 650
COUNT(*)会统计所有记录,所以是5;而COUNT(phone_number)会忽略`NULL`值,所以是3(ID为1, 3, 5的用户)。同样,SUM(points)也会从计算中排除值为`NULL`的记录。这个行为在数据分析中非常重要,请务必记住。
【体验环节】在浏览器中运行SQL,确认NULL的行为!
现在,是时候把知识变成技能了!将下面的HTML代码完整复制,保存为sql_null_test.html之类的文件名,然后在浏览器中打开它。你专属的SQL执行环境将会启动,并且本文中使用的members表已经为你准备好了。
亲手试试,看看= NULL和IS NULL的结果有何不同,以及为什么COUNT(*)和COUNT(列名)的结果会不一样吧!
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SQL NULL 在线执行环境</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; line-height: 1.7; color: #333; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
h1 { color: #34495e; }
textarea { width: 100%; height: 180px; font-family: "SF Mono", "Consolas", monospace; font-size: 16px; padding: 12px; border: 1px solid #ccc; border-radius: 6px; box-sizing: border-box; margin-bottom: 1rem; }
button { background-color: #34495e; color: white; border: none; padding: 12px 22px; font-size: 16px; border-radius: 6px; cursor: pointer; transition: background-color 0.2s; }
button:hover { background-color: #2c3e50; }
button:disabled { background-color: #bdc3c7; cursor: not-allowed; }
#result-container { margin-top: 2rem; border: 1px solid #ddd; padding: 1rem; border-radius: 6px; background: #fdfdfd; min-height: 50px; }
#error-message { color: #e74c3c; font-weight: bold; }
table { border-collapse: collapse; width: 100%; margin-top: 1rem; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
th { background-color: #f2f2f2; }
tr:nth-child(even) { background-color: #f9f9f9; }
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/sql-wasm.js"></script>
</head>
<body>
<h1>来试试SQL吧!</h1>
<p>在下方的文本框中输入SQL语句,然后点击“执行”按钮。试试不同的条件吧!</p>
<textarea id="sql-input">-- 查找从未登录过的会员(last_login为NULL)
SELECT * FROM members WHERE last_login IS NULL;
</textarea>
<button id="execute-btn">执行</button>
<div id="result-container">
<p id="error-message"></p>
<div id="result-output"></div>
</div>
<script>
const sqlInput = document.getElementById('sql-input');
const executeBtn = document.getElementById('execute-btn');
const errorMsg = document.getElementById('error-message');
const resultOutput = document.getElementById('result-output');
let db;
async function initDb() {
executeBtn.disabled = true;
executeBtn.textContent = '数据库准备中...';
try {
const SQL = await initSqlJs({
locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/${file}`
});
db = new SQL.Database();
const setupSql = `
DROP TABLE IF EXISTS members;
CREATE TABLE members (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, phone_number TEXT, last_login DATE, points INTEGER DEFAULT 0);
INSERT INTO members (id, name, email, phone_number, last_login, points) VALUES
(1, '山田 太郎', 'yamada@example.com', '090-1111-2222', '2025-06-25', 150),
(2, '鈴木 花子', 'suzuki@example.com', NULL, '2025-07-01', 50),
(3, '佐藤 次郎', 'sato@example.com', '080-3333-4444', NULL, 200),
(4, '伊藤 さくら', 'ito@example.com', NULL, NULL, 0),
(5, '渡辺 健太', 'watanabe@example.com', '', '2025-06-28', 300);
`;
db.run(setupSql);
executeBtn.disabled = false;
executeBtn.textContent = '执行';
resultOutput.innerHTML = '<p>准备就绪!请随意尝试你自己的SQL查询。</p>';
} catch (err) {
errorMsg.textContent = '数据库初始化失败:' + err.message;
console.error(err);
}
}
function executeSql() {
if (!db) return;
const sql = sqlInput.value;
errorMsg.textContent = '';
resultOutput.innerHTML = '';
try {
const results = db.exec(sql);
if (results.length === 0) {
resultOutput.innerHTML = '<p>查询成功执行,但没有返回结果集。</p>';
return;
}
results.forEach(result => {
const table = document.createElement('table');
const thead = document.createElement('thead');
const tbody = document.createElement('tbody');
const headerRow = document.createElement('tr');
result.columns.forEach(colName => {
const th = document.createElement('th');
th.textContent = colName;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
result.values.forEach(row => {
const bodyRow = document.createElement('tr');
row.forEach(cellValue => {
const td = document.createElement('td');
td.textContent = cellValue === null ? 'NULL' : cellValue;
bodyRow.appendChild(td);
});
tbody.appendChild(bodyRow);
});
table.appendChild(thead);
table.appendChild(tbody);
resultOutput.appendChild(table);
});
} catch (err) {
errorMsg.textContent = 'SQL错误:' + err.message;
console.error(err);
}
}
executeBtn.addEventListener('click', executeSql);
initDb();
</script>
</body>
</html>
总结
这次,我们学习了在SQL中正确处理`NULL`的方法,包括其概念和具体的判断方式。
- `NULL`的本质: `NULL`是表示“没有值”或“未知”的特殊状态,与“0”或“空字符串”完全是两码事。
- 比较规则: 与`NULL`进行比较(
=,<>)的结果永远是`UNKNOWN`,因此在WHERE子句中无法得到预期的结果。 - 正确的判断方法: 判断是否为`NULL`的唯一正确方法是使用
IS NULL,判断不为`NULL`则使用IS NOT NULL。 - 与聚合函数的关系: 像
COUNT(列名)、SUM()、AVG()这样的聚合函数,会自动从计算对象中排除值为`NULL`的记录。
正确理解`NULL`的概念,是熟练运用SQL的必经之路。一开始可能会有些困惑,但只要你意识到“`NULL`不是一个值,而是一种状态”,就应该能逐渐习惯。这项技能对于正确处理数据缺失、开发bug少的健壮应用程序至关重要。你的SQL水平又上了一个新台阶!