这道题目来自LeetCode数据库分类的第1757题,要求我们从一个产品表中筛选出同时满足"低脂"和"可回收"两个条件的产品编号。我们先来理解题目给出的数据结构:
产品表Products包含三个字段:
product_id:产品唯一标识,是表的主键low_fats:枚举类型,取值为'Y'或'N',表示是否为低脂产品recyclable:枚举类型,取值为'Y'或'N',表示是否可回收在实际业务场景中,这种设计非常常见。枚举类型(ENUM)在数据库中特别适合用来存储固定选项的状态值,比单纯的字符或数字更具可读性。例如电商平台的产品属性、用户系统的状态标记等都会用到类似设计。
提示:在真实项目中,ENUM类型虽然可读性好,但也要注意它的扩展性问题。当需要新增选项时,修改ENUM定义可能比修改普通字段更复杂。
题目给出的解决方案非常简洁:
sql复制SELECT product_id
FROM Products
WHERE low_fats = 'Y' AND recyclable = 'Y';
这个查询语句由三个关键部分组成:
SELECT product_id:指定只返回产品ID字段FROM Products:指定查询的数据源是Products表WHERE low_fats = 'Y' AND recyclable = 'Y':设置两个必须同时满足的过滤条件AND运算符表示两个条件必须同时为真才会被选中。这是SQL中最基本的逻辑运算符之一。
当数据库引擎执行这个查询时,大致会经过以下步骤:
在数据量大的情况下,这种全表扫描效率较低。实际项目中,我们通常会考虑在过滤字段上建立适当的索引。
对于这个特定查询,可以考虑以下优化方案:
复合索引:创建一个包含(low_fats, recyclable)的复合索引
sql复制CREATE INDEX idx_low_fats_recyclable ON Products(low_fats, recyclable);
覆盖索引:如果只需要product_id,可以创建包含所有三个字段的索引
sql复制CREATE INDEX idx_covering ON Products(low_fats, recyclable, product_id);
统计信息更新:确保数据库统计信息是最新的,帮助优化器选择最佳执行计划
sql复制ANALYZE TABLE Products;
注意:索引虽然能提高查询速度,但会增加写入操作的开销。需要根据实际业务读写比例来决定是否添加索引。
虽然题目中的SQL语法是标准SQL,但不同数据库系统在处理ENUM类型时有些差异:
MySQL的ENUM类型有以下特点:
PostgreSQL没有原生的ENUM类型,但可以通过以下方式实现类似功能:
使用CHECK约束:
sql复制CREATE TABLE Products (
product_id INT PRIMARY KEY,
low_fats CHAR(1) CHECK (low_fats IN ('Y', 'N')),
recyclable CHAR(1) CHECK (recyclable IN ('Y', 'N'))
);
使用自定义类型:
sql复制CREATE TYPE yes_no AS ENUM ('Y', 'N');
CREATE TABLE Products (
product_id INT PRIMARY KEY,
low_fats yes_no,
recyclable yes_no
);
SQL Server通常使用CHAR(1)配合CHECK约束:
sql复制CREATE TABLE Products (
product_id INT PRIMARY KEY,
low_fats CHAR(1) NOT NULL CHECK (low_fats IN ('Y', 'N')),
recyclable CHAR(1) NOT NULL CHECK (recyclable IN ('Y', 'N'))
);
在实际电商系统中,产品筛选条件往往更加复杂。例如:
sql复制SELECT product_id
FROM Products
WHERE low_fats = 'Y'
AND recyclable = 'Y'
AND price BETWEEN 10 AND 100
AND stock_quantity > 0
AND category_id = 5;
这种多条件查询尤其需要注意索引设计和查询优化。
在应用程序中,我们经常需要根据用户选择动态构建WHERE条件。例如Java中使用PreparedStatement:
java复制StringBuilder sql = new StringBuilder("SELECT product_id FROM Products WHERE 1=1");
List<Object> params = new ArrayList<>();
if (request.isLowFats()) {
sql.append(" AND low_fats = ?");
params.add("Y");
}
if (request.isRecyclable()) {
sql.append(" AND recyclable = ?");
params.add("Y");
}
// 执行查询...
实际应用中,我们通常需要对结果进行排序和分页:
sql复制SELECT product_id
FROM Products
WHERE low_fats = 'Y' AND recyclable = 'Y'
ORDER BY product_name -- 按产品名称排序
LIMIT 10 OFFSET 20; -- 分页查询
新手常犯的错误是忽略ENUM值的大小写敏感性。例如:
sql复制-- 可能不返回任何结果,因为'enum'值通常是大小写敏感的
SELECT product_id FROM Products WHERE low_fats = 'y';
解决方案:
sql复制SELECT product_id FROM Products WHERE UPPER(low_fats) = 'Y';
如果字段允许NULL值,查询时需要特别注意:
sql复制-- 不会返回low_fats为NULL的记录
SELECT product_id FROM Products WHERE low_fats = 'Y';
-- 如果需要包含NULL值
SELECT product_id FROM Products WHERE low_fats = 'Y' OR low_fats IS NULL;
某些写法会导致索引失效,例如:
sql复制-- 对字段使用函数会导致索引失效
SELECT product_id FROM Products WHERE UPPER(low_fats) = 'Y';
-- 使用OR而非AND连接不同字段的条件
SELECT product_id FROM Products WHERE low_fats = 'Y' OR recyclable = 'Y';
对于复杂查询,可以使用EXPLAIN命令查看执行计划:
sql复制EXPLAIN SELECT product_id FROM Products WHERE low_fats = 'Y' AND recyclable = 'Y';
执行计划会显示:
分析执行计划可以帮助我们发现性能瓶颈并进行优化。
假设我们在开发一个电商平台的后台系统,需要实现类似的产品筛选功能。完整的实现可能包括:
sql复制CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
low_fat ENUM('Y','N') NOT NULL DEFAULT 'N',
recyclable ENUM('Y','N') NOT NULL DEFAULT 'N',
organic ENUM('Y','N') NOT NULL DEFAULT 'N',
price DECIMAL(10,2) NOT NULL,
stock INT NOT NULL DEFAULT 0,
INDEX idx_eco (low_fat, recyclable, organic),
INDEX idx_price (price)
);
java复制public List<Product> findProducts(ProductFilter filter) {
StringBuilder sql = new StringBuilder("SELECT id, name, price FROM products WHERE 1=1");
List<Object> params = new ArrayList<>();
if (filter.isLowFat()) {
sql.append(" AND low_fat = ?");
params.add("Y");
}
if (filter.isRecyclable()) {
sql.append(" AND recyclable = ?");
params.add("Y");
}
if (filter.getMaxPrice() != null) {
sql.append(" AND price <= ?");
params.add(filter.getMaxPrice());
}
// 执行查询...
}
java复制sql.append(" LIMIT ? OFFSET ?");
params.add(pageSize);
params.add(pageSize * (pageNumber - 1));
编写SQL查询后,应该进行充分测试:
基础测试用例:
边界测试:
性能测试:
例如,我们可以创建测试数据:
sql复制-- 插入100万条测试数据
INSERT INTO Products (product_id, low_fats, recyclable)
SELECT
n,
IF(RAND() > 0.5, 'Y', 'N'),
IF(RAND() > 0.5, 'Y', 'N')
FROM (
SELECT a.N + b.N * 10 + c.N * 100 + d.N * 1000 + e.N * 10000 + f.N * 100000 AS n
FROM (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) a
CROSS JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) b
CROSS JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) c
CROSS JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) d
CROSS JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) e
CROSS JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) f
LIMIT 1000000
) numbers;
然后测试查询性能:
sql复制-- 测试基础查询
SELECT COUNT(*) FROM Products WHERE low_fats = 'Y' AND recyclable = 'Y';
-- 测试带索引的查询
CREATE INDEX idx_filter ON Products(low_fats, recyclable);
SELECT COUNT(*) FROM Products WHERE low_fats = 'Y' AND recyclable = 'Y';