7、SQLite高级查询:多表连接、子查询、UNION、DISTINCT、CASE WHEN

说实话,做Android开发这么多年,我见过太多人在数据库查询上栽跟头了。单表查询谁都会写,但一旦涉及到多张表、复杂条件,很多人就开始头皮发麻。今天咱们就把这些高级查询技巧一次性讲透。

我个人习惯把SQLite的高级查询分成两类:一类是「表与表之间的操作」,比如连接、合并;另一类是「行与行之间的处理」,比如去重、条件分支。咱们一个一个来。

SQLite 高级查询体系 多表连接 INNER JOIN LEFT JOIN 子查询 WHERE / FROM / SELECT 子查询 集合与行处理 UNION DISTINCT CASE WHEN 条件表达式 掌握这些,复杂查询不再头疼

7.1 多表连接:INNER JOIN 与 LEFT JOIN

先说说多表连接。你在实际项目里,数据很少只存在一张表里。比如用户表、订单表、商品表,它们之间通过外键关联。这时候就需要 JOIN 了。

INNER JOIN 说白了就是「取交集」——只返回两张表中匹配成功的行。我早期做电商项目时,查「已下单用户及其订单详情」就用这个。

-- 查询所有有订单的用户及其订单信息
SELECT 
    u.user_id,
    u.user_name,
    o.order_id,
    o.amount,
    o.order_date
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id;

你看,只有那些下过单的用户才会出现在结果里。没下过单的?对不起,不显示。

LEFT JOIN 则不同。它保留左表的所有行,右表没有匹配的,就用 NULL 填充。我在项目中遇到过这样一个场景:要展示所有用户列表,同时显示他们最近一次登录时间。没登录过的用户也要显示,只是登录时间为空。这时候 LEFT JOIN 就派上用场了。

-- 查询所有用户及其订单(包括没有订单的用户)
SELECT 
    u.user_id,
    u.user_name,
    o.order_id,
    o.amount
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id;
💡 我的小技巧: 写 JOIN 时,先把小表放在左边,大表放在右边。SQLite 优化器虽然会做重排序,但养成好习惯总没错。另外,ON 后面的关联字段一定要有索引,否则数据量一上来,查询慢到你怀疑人生。

7.2 子查询:查询里的查询

子查询,就是在一个 SQL 语句里再嵌套一个 SELECT。你想想看,有时候你需要先查出一个中间结果,再基于这个结果做二次过滤。子查询就是干这个的。

子查询可以出现在三个地方:WHERE 子句、FROM 子句、SELECT 子句。

WHERE 子查询 最常见。比如查「下单金额超过平均值的用户」:

SELECT user_id, user_name, total_amount
FROM users
WHERE total_amount > (
    SELECT AVG(total_amount) FROM users
);

FROM 子查询 相当于把子查询结果当成一张临时表。我记得有一次做报表,需要先聚合再关联,用这个特别顺手:

SELECT t.user_id, t.order_count, u.user_name
FROM (
    SELECT user_id, COUNT(*) AS order_count
    FROM orders
    GROUP BY user_id
) t
INNER JOIN users u ON t.user_id = u.user_id;

SELECT 子查询 用得少一些,但某些场景很巧妙。比如查每个用户及其最新订单日期:

SELECT 
    user_id,
    user_name,
    (SELECT MAX(order_date) FROM orders WHERE orders.user_id = users.user_id) AS last_order_date
FROM users;
⚠️ 注意: 子查询在数据量大时性能可能不佳。我曾经在一个百万级数据的表里用了三层嵌套子查询,结果查询耗时从 0.2 秒飙升到 8 秒。后来改成 JOIN + 临时表,瞬间降到 0.5 秒。所以,能不用子查询就别用,尤其是 WHERE IN 后面跟子查询,尽量换成 JOIN。

7.3 UNION 合并结果集

UNION 的作用很简单:把两个 SELECT 的结果上下拼在一起。但有个硬性要求——两个 SELECT 的列数必须相同,对应列的数据类型要兼容。

我举个例子。假设你要查「所有 VIP 用户和所有本月新注册用户」,这两个条件可能有重叠,但你想去重后展示:

SELECT user_id, user_name, 'VIP' AS source
FROM users
WHERE is_vip = 1
UNION
SELECT user_id, user_name, 'New' AS source
FROM users
WHERE reg_date >= '2025-01-01';

UNION 默认会去重。如果你不想去重,或者你知道两个结果集本来就没有重复,用 UNION ALL 会更快,因为它省去了排序去重的步骤。

📌 性能对比: UNION 比 UNION ALL 多一步排序去重操作。数据量大时,UNION ALL 可能快 3-5 倍。所以,确定没有重复数据时,请用 UNION ALL。

7.4 DISTINCT 去重

DISTINCT 很简单,就是去掉结果集中完全重复的行。但很多人用错了地方。

-- 查询所有不重复的城市
SELECT DISTINCT city FROM users;

-- 多列去重:城市和性别都相同的才视为重复
SELECT DISTINCT city, gender FROM users;

嗯,这里要注意:DISTINCT 是对整个结果集的行做去重,不是只对某一列。你写 SELECT DISTINCT name, age,它去重的是 (name, age) 这个组合,而不是单独 name。

我曾经见过有人这么写:

SELECT DISTINCT name, age, address, phone FROM users;

结果发现数据根本去不掉——因为每个人的地址和电话都不一样。你想想看,这 DISTINCT 等于没起作用。正确的做法是只查你需要去重的列,或者用 GROUP BY 替代。

💡 替代方案: 很多时候 GROUP BY 比 DISTINCT 更灵活。比如你想去重的同时还统计数量,DISTINCT 就做不到了,但 GROUP BY 可以。

7.5 CASE WHEN 条件表达式

CASE WHEN 是 SQL 里的 if-else。它可以在查询结果里根据条件动态生成新的列值。我个人觉得这是 SQLite 里最被低估的功能之一。

基本语法有两种:

-- 简单 CASE 表达式
SELECT 
    user_name,
    CASE level
        WHEN 1 THEN '普通用户'
        WHEN 2 THEN '银卡用户'
        WHEN 3 THEN '金卡用户'
        ELSE '未知'
    END AS level_name
FROM users;

-- 搜索 CASE 表达式(更灵活)
SELECT 
    user_name,
    total_amount,
    CASE 
        WHEN total_amount > 10000 THEN 'VIP'
        WHEN total_amount > 5000 THEN '高级'
        WHEN total_amount > 1000 THEN '中级'
        ELSE '初级'
    END AS user_grade
FROM users;

我在项目中用 CASE WHEN 做过一个很有意思的功能:统计每个用户的订单状态分布。一条 SQL 就搞定了:

SELECT 
    user_id,
    COUNT(*) AS total_orders,
    SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count,
    SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count,
    SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_count
FROM orders
GROUP BY user_id;

你看,本来需要写四条 SQL 或者用代码循环处理的事情,一条 CASE WHEN 就解决了。这就是 SQL 的魅力。

📌 核心要点: CASE WHEN 返回的是标量值,可以放在 SELECT、WHERE、ORDER BY、GROUP BY 等任何你能放表达式的地方。比如你可以用 ORDER BY CASE WHEN ... THEN ... END 实现自定义排序。

7.6 综合实战:把这些技巧串起来

光讲理论没意思。我拿一个真实场景来串一下这些知识点。

假设我们有三张表:users(用户)、orders(订单)、products(商品)。需求是:查每个用户的「高价值订单」数量(金额 > 500 的订单),并按数量降序排列,只显示前 10 名。

SELECT 
    u.user_id,
    u.user_name,
    COUNT(DISTINCT o.order_id) AS high_value_orders,
    CASE 
        WHEN COUNT(DISTINCT o.order_id) >= 5 THEN '超级买家'
        WHEN COUNT(DISTINCT o.order_id) >= 3 THEN '活跃买家'
        ELSE '普通买家'
    END AS buyer_level
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id AND o.amount > 500
GROUP BY u.user_id
HAVING high_value_orders > 0
ORDER BY high_value_orders DESC
LIMIT 10;

这条 SQL 里用到了 LEFT JOIN(保留所有用户)、DISTINCT(去重订单)、CASE WHEN(分级)、GROUP BY + HAVING(分组过滤)、ORDER BY + LIMIT(排序取前 N)。你看,高级查询就是这么组合出来的。

⚠️ 避坑指南: 我曾经在 LEFT JOIN 的 ON 条件里加了过滤条件(比如上面的 o.amount > 500),结果发现有些用户明明有订单但金额不满足,他们的 high_value_orders 是 0,但 LEFT JOIN 仍然保留了这些用户。如果你只想统计有高价值订单的用户,应该把金额条件放到 WHERE 里,或者用 INNER JOIN。这个坑我踩过,分享给你。

好了,这一章的内容就到这里。SQLite 的高级查询其实没那么可怕,关键是理解每种操作的「语义」——它到底是在做什么样的数据变换。多写、多试、多踩坑,慢慢就熟练了。


公众号:蓝海资料掘金营,微信deep3321