Chapter3 SQL集合操作

NOT EXISTSEXCEPT 是 SQL 中用于处理集合差集的高级操作,它们在不同场景下非常有用。


一、 NOT EXISTS 用法

1. 基本语法

1
2
3
4
5
6
7
SELECT columns
FROM table1 t1
WHERE NOT EXISTS (
    SELECT 1 
    FROM table2 t2 
    WHERE t1.related_column = t2.related_column
);

2. 实际例子

例1:找出没有项目的员工

1
2
3
4
5
6
7
SELECT e.employee_id, e.name
FROM employees e
WHERE NOT EXISTS (
    SELECT 1 
    FROM projects p 
    WHERE p.leader_id = e.employee_id
);

例2:找出没有订单的客户

1
2
3
4
5
6
7
SELECT c.customer_id, c.customer_name
FROM customers c
WHERE NOT EXISTS (
    SELECT 1 
    FROM orders o 
    WHERE o.customer_id = c.customer_id
);

例3:找出从未被订购的产品

1
2
3
4
5
6
7
SELECT p.product_id, p.product_name
FROM products p
WHERE NOT EXISTS (
    SELECT 1 
    FROM order_items oi 
    WHERE oi.product_id = p.product_id
);

3. 复杂例子:找出没有下属的管理者

1
2
3
4
5
6
7
8
SELECT e1.employee_id, e1.name
FROM employees e1
WHERE e1.is_manager = 1
  AND NOT EXISTS (
    SELECT 1 
    FROM employees e2 
    WHERE e2.manager_id = e1.employee_id
);

二、 EXCEPT 用法

1. 基本语法

1
2
3
SELECT column1, column2 FROM table1
EXCEPT
SELECT column1, column2 FROM table2;

2. 实际例子

例1:找出只在A表不在B表的记录

1
2
3
4
-- 找出在employees表但不在former_employees表的员工
SELECT employee_id, name FROM employees
EXCEPT
SELECT employee_id, name FROM former_employees;

例2:找出有库存但从未被订购的产品

1
2
3
SELECT product_id FROM inventory
EXCEPT
SELECT product_id FROM order_items;

例3:多列比较

1
2
3
4
-- 找出在2023年有销售但2024年没有的客户
SELECT customer_id, product_id FROM sales_2023
EXCEPT
SELECT customer_id, product_id FROM sales_2024;

三、 NOT EXISTS vs EXCEPT 对比

相同需求的不同实现

需求:找出没有项目的部门

使用 NOT EXISTS:

1
2
3
4
5
6
7
SELECT d.department_id, d.department_name
FROM departments d
WHERE NOT EXISTS (
    SELECT 1 
    FROM projects p 
    WHERE p.department_id = d.department_id
);

使用 EXCEPT:

1
2
3
4
5
6
SELECT department_id, department_name 
FROM departments
EXCEPT
SELECT d.department_id, d.department_name
FROM departments d
JOIN projects p ON d.department_id = p.department_id;

四、 高级应用场景

1. 多层 NOT EXISTS(复杂逻辑)

找出所有产品都库存充足的产品类别

1
2
3
4
5
6
7
8
9
SELECT c.category_id, c.category_name
FROM categories c
WHERE NOT EXISTS (
    -- 找出该类别中库存不足的产品
    SELECT 1
    FROM products p
    WHERE p.category_id = c.category_id
      AND p.current_stock < p.minimum_stock
);

2. EXCEPT 用于数据验证

验证两个表的结构一致性

1
2
3
4
-- 找出在source_table中存在但在target_table中不存在的记录
SELECT id, name, value FROM source_table
EXCEPT
SELECT id, name, value FROM target_table;

3. 组合使用 NOT EXISTS 和 EXISTS

找出只订购过一次的客户

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
SELECT c.customer_id, c.customer_name
FROM customers c
WHERE EXISTS (
    -- 至少有一个订单
    SELECT 1 FROM orders o 
    WHERE o.customer_id = c.customer_id
)
AND NOT EXISTS (
    -- 没有第二个订单
    SELECT 1 FROM orders o1, orders o2
    WHERE o1.customer_id = c.customer_id
      AND o2.customer_id = c.customer_id
      AND o1.order_id <> o2.order_id
);

五、 性能考虑和最佳实践

1. NOT EXISTS 通常性能更好

1
2
3
4
5
6
7
-- 推荐:使用NOT EXISTS
SELECT * FROM table1 t1
WHERE NOT EXISTS (SELECT 1 FROM table2 t2 WHERE t2.id = t1.id);

-- 不推荐:使用NOT IN(对NULL值处理有问题)
SELECT * FROM table1 
WHERE id NOT IN (SELECT id FROM table2 WHERE id IS NOT NULL);

2. EXCEPT 自动去重

1
2
3
4
-- EXCEPT 会自动去除重复记录
SELECT department FROM employees
EXCEPT
SELECT department FROM former_employees;

3. 处理NULL值

1
2
3
4
5
6
-- NOT EXISTS 能正确处理NULL值
SELECT * FROM products p
WHERE NOT EXISTS (
    SELECT 1 FROM discontinued_products dp 
    WHERE dp.product_id = p.product_id
);

六、 实际业务场景

场景1:电商平台 - 找出从未被浏览的商品

1
2
3
4
5
6
7
SELECT product_id, product_name
FROM products
WHERE NOT EXISTS (
    SELECT 1 
    FROM user_browsing_history 
    WHERE product_id = products.product_id
);

场景2:学校系统 - 找出没有选课的学生

1
2
3
4
5
6
SELECT student_id, student_name
FROM students
EXCEPT
SELECT s.student_id, s.student_name
FROM students s
JOIN enrollments e ON s.student_id = e.student_id;

场景3:银行系统 - 找出有账户但从未交易的客户

1
2
3
4
5
6
7
8
SELECT c.customer_id, c.customer_name
FROM customers c
JOIN accounts a ON c.customer_id = a.customer_id
WHERE NOT EXISTS (
    SELECT 1 
    FROM transactions t 
    WHERE t.account_id = a.account_id
);

总结

  • NOT EXISTS: 更适合关联查询,性能通常较好,能正确处理NULL值
  • EXCEPT: 更适合比较两个结果集的差异,语法更直观,自动去重
  • 选择依据:
    • 需要关联条件时用 NOT EXISTS
    • 直接比较两个查询结果时用 EXCEPT
    • 考虑数据库优化器和具体数据量进行测试

两者都是处理"在A中但不在B中"这类需求的强大工具!

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy