Skip to content

MySQL - IN 运算符

MySQL 中的 IN 运算符允许你在 WHERE 子句中指定多个值。它提供了一种简洁的方式来检查列的值是否与给定列表中的任何值匹配。它在功能上等同于一系列 OR 条件,但通常更具可读性,并且可能性能更好。

SELECT column_name(s)
FROM table_name
WHERE column_name IN (value1, value2, ...);

这比写 WHERE column_name = value1 OR column_name = value2 OR ... 要简洁得多。

让我们设置一个 CUSTOMERS 表。

CREATE TABLE CUSTOMERS(
ID INT AUTO_INCREMENT PRIMARY KEY,
NAME VARCHAR(100) NOT NULL,
AGE INT NOT NULL,
ADDRESS VARCHAR(255),
SALARY DECIMAL(10, 2)
);
INSERT INTO CUSTOMERS(NAME, AGE, ADDRESS, SALARY) VALUES
('Ramesh', 32, 'Ahmedabad', 2000.00),
('Khilan', 25, 'Delhi', 1500.00),
('Kaushik', 23, 'Kota', 2000.00),
('Chaitali', 25, 'Mumbai', 6500.00),
('Hardik', 27, 'Bhopal', 8500.00),
('Komal', 22, 'Hyderabad', 4500.00),
('Muffy', 24, 'Indore', 10000.00);

要检索名为 ‘Khilan’、‘Hardik’ 或 ‘Muffy’ 的客户:

SELECT * FROM CUSTOMERS
WHERE NAME IN ('Khilan', 'Hardik', 'Muffy');
ID姓名年龄地址薪资
2Khilan25Delhi1500.00
5Hardik27Bhopal8500.00
7Muffy24Indore10000.00

正如你所预料的,NOT IN 执行相反的操作。它选择所有列值与列表中任何值都 不 匹配的行。

查找所有年龄不为 25、23 或 22 岁的客户:

SELECT * FROM CUSTOMERS
WHERE AGE NOT IN (25, 23, 22);

关于 NULL 的重要说明: 如果提供给 NOT IN 的列表中包含 NULL,或者被检查的列包含 NULL,则行为可能会与直觉相悖。col NOT IN (1, 2, NULL) 将永远不会为真,因为 col <> NULL 的结果是未知的。最好确保与 NOT IN 一起使用的列表和列不包含 NULL,或者明确地处理它们。

IN 的一个强大特性是它能够与子查询一起使用。子查询必须返回单列,然后外部查询将查找与子查询结果集中任何值匹配的行。

让我们找出所有居住在至少有一人收入超过 8000 美元的城市的客户。

-- 首先,子查询找到高收入者所在的城市。
-- SELECT ADDRESS FROM CUSTOMERS WHERE SALARY > 8000.00; -> 返回 ('Bhopal', 'Indore')
-- 现在,在主查询中使用它。
SELECT ID, NAME, ADDRESS FROM CUSTOMERS
WHERE ADDRESS IN (
SELECT ADDRESS FROM CUSTOMERS WHERE SALARY > 8000.00
);

虽然带有子查询的 IN 具有可读性,但它并非总是性能最佳的选择。现代 MySQL 优化器非常优秀,但从历史上看,JOIN 或 EXISTS 可能更快。

  • IN: 通常适用于小型、静态列表或子查询返回少量行的情况。
  • JOIN: 当子查询返回大量结果集时,通常比 IN 性能更好,因为它能更有效地利用索引。上述查询可以改写为 JOIN。
  • EXISTS: 最适合当你只需要检查子查询中是否存在匹配行,而不关心实际值时。它可以在找到第一个匹配项时立即停止。
-- 使用 JOIN 的相同查询(通常性能更好)
SELECT DISTINCT c1.ID, c1.NAME, c1.ADDRESS
FROM CUSTOMERS c1
JOIN CUSTOMERS c2 ON c1.ADDRESS = c2.ADDRESS
WHERE c2.SALARY > 8000.00;

最佳实践: 对于简单情况,IN 很好。对于复杂查询或性能关键路径,请始终使用 EXPLAIN 测试所有三种变体(IN、JOIN、EXISTS),以查看你的特定 MySQL 版本如何优化它们。

你可以使用 IN 和行构造函数一次性检查多列的匹配项。这为复杂的条件提供了非常简洁的语法。

-- 查找年龄为 25 岁来自孟买,或年龄为 32 岁来自艾哈迈达巴德的客户
SELECT NAME, AGE, ADDRESS
FROM CUSTOMERS
WHERE (AGE, ADDRESS) IN ((25, 'Mumbai'), (32, 'Ahmedabad'));

如果用户输入处理不当,IN 子句是 SQL 注入漏洞的常见来源。切勿 通过将用户提供的值直接拼接到 IN 列表中来构建查询。

// 危险 - 不要这样做!
const user_ids = "1, 2, 3) OR 1=1 --";
const sql = `SELECT * FROM users WHERE id IN (${user_ids})`; // 这将导致安全灾难

始终使用带占位符的预处理语句。大多数数据库库都要求你动态生成正确数量的占位符(?)。

在客户端应用程序中实现 IN(安全地)

Section titled “在客户端应用程序中实现 IN(安全地)”

以下是如何在现代 Node.js 中安全地使用带有动态值列表的 IN 子句。

Node.js(安全的动态 IN 子句)
此示例演示了从输入数组构建 `IN` 子句的正确、安全方法。它动态生成占位符,并将输入数组传递给 `execute` 方法,以实现安全的参数绑定。
```javascript
// secureInClause.js
const mysql = require('mysql2/promise');
async function getCustomersByIds(ids) {
// 1. 验证和清理输入。确保 `ids` 是一个数字数组。
if (!Array.isArray(ids) || ids.length === 0) {
console.log('未提供有效 ID。');
return;
}
const numericIds = ids.map(id => parseInt(id, 10)).filter(Number.isFinite);
if (numericIds.length === 0) {
console.log('未找到有效的数字 ID。');
return;
}
let connection;
try {
const pool = mysql.createPool({ host: 'localhost', user: 'root', password: 'password', database: 'TUTORIALS' });
connection = await pool.getConnection();
// 2. 动态创建占位符 (?, ?, ...)
const placeholders = numericIds.map(() => '?').join(',');
// 3. 创建安全的 SQL 查询
const sql = `SELECT * FROM CUSTOMERS WHERE ID IN (${placeholders})`;
// 4. 使用值数组执行。库会处理其余部分。
const [customers] = await connection.execute(sql, numericIds);
console.log('找到的客户:', customers);
} catch (error) {
console.error('数据库查询失败:', error);
} finally {
if (connection) connection.release();
}
}
// 使用 ID 数组的示例
getCustomersByIds([2, 4, 6]);
// 带有潜在不安全输入的示例
getCustomersByIds(['1', '3', '5; DROP TABLE customers']);