告别CRUD Boy!SQL子查询:从头疼到真香的进化之路


告别CRUD Boy!SQL子查询:从头疼到真香的进化之路 😎

大家好,我是你们的老朋友,一个在代码世界里摸爬滚打了多年的老兵。

今天不聊高大上的架构,也不谈什么分布式、微服务。我们就来聊点每个开发者都绕不开,但很多人又没用好的基础知识------SQL子查询

你可能会说:"切,子查询不就是 SELECT 里套个 SELECT 吗?我早会了!"

别急,当年我也这么想的。直到项目里的一个个"坑"把我埋了,我才恍然大悟,原来小小的子查询,蕴含着改变数据处理思维的大能量!🤯


一、梦魇的开始:跨多表的数据"粘合"难题

还记得那是个做电商后台的项目,产品经理笑眯眯地走过来说:"咱们需要个报表,要筛选出所有在最近一个月下过单的'黄金会员'。"

听起来很简单,对吧?当时我的第一反应是这样的:

  1. 先去 orders 表查出最近一个月下过单的 user_id 列表。

    sql 复制代码
    SELECT user_id FROM orders WHERE created_at >= '2023-10-01';
  2. 在后端代码里(比如Java或Python),拿到这个 user_id 的List。

  3. 再拼一个SQL,去 users 表里把这些用户信息捞出来。

    sql 复制代码
    SELECT * FROM users WHERE user_level = 'gold' AND id IN (1, 5, 23, 45, ...); // id列表是上一步查出来的

看起来没毛病,功能也能实现。但很快问题就来了:

  • 性能拉垮 📉:如果最近一个月有几万个用户下单,我的应用内存要先撑爆一次,然后数据库接收一个巨长无比的 IN 列表,查询速度慢成龟速。
  • 代码耦合 🤢:数据逻辑被拆散在了SQL和业务代码里,一个简单的查询需要动两个地方,维护起来简直是灾难。

当时我看着这段代码,头疼得不行。这根本不是一个"资深"开发者该有的样子!我必须在数据库层面解决它!

二、灵光乍现:子查询,我的救世主!

就在我抓耳挠腮的时候,脑子里闪过一个念头:能不能让数据库自己去完成第一步和第二步的衔接呢?当然可以!这就是子查询最经典的用武之地。

场景一:用"多行单列"子查询拯救 IN 子句

我把上面那坨又慢又丑的操作,用一条SQL给搞定了:

sql 复制代码
SELECT *
FROM users
WHERE
    user_level = 'gold' AND id IN (
        -- 这里就是子查询!它返回一个包含多个user_id的单列结果集
        SELECT DISTINCT user_id FROM orders WHERE created_at >= '2023-10-01'
    );

💡 恍然大悟的瞬间:

看到这条SQL成功跑出结果的那一刻,我悟了!子查询的本质,就是把一个查询的结果,当作另一个查询的"输入条件"。我不再需要把数据从数据库搬到应用层,再搬回数据库,大大减少了网络IO和内存开销,代码也变得无比清爽。

这,就是最常见的 多行单列子查询 。它返回的是一列(user_id)多行(很多个用户)的结果,完美适配 INNOT INANYALL 这些操作符。

踩坑经验 📌

  1. 性能 :虽然 IN + 子查询很好用,但当子查询返回的结果集非常大时(比如几十上百万),有些数据库的优化器可能会犯傻,性能还不如 JOIN。我的经验是,对于大数据量的场景,可以改写成 INNER JOIN,性能通常更稳定。比如这样:

    sql 复制代码
    SELECT u.*
    FROM users u
    INNER JOIN (
        SELECT DISTINCT user_id FROM orders WHERE created_at >= '2023-10-01'
    ) o ON u.id = o.user_id
    WHERE u.user_level = 'gold';
  2. NULL 值 :如果你用的是 NOT IN,要特别小心子查询结果里有 NULL 值!... NOT IN (1, 2, NULL) 的结果永远是空的,会让你怀疑人生。这是因为 NULL 代表未知,任何值跟 NULL 比较都是未知。


三、能力进阶:当子查询成为一个"值"或一张"表"

解决了第一个问题,我尝到了甜头。很快,新的挑战又来了。

场景二:用"单行单列"子查询进行动态比较

财务部门需要一个数据:找出所有工资高于公司平均工资的员工

如果是你,怎么做?不会又是先 SELECT AVG(salary) FROM employees; 查出平均工资(比如是 8000),然后再写 SELECT * FROM employees WHERE salary > 8000; 吧?那可不行,万一明天平均工资变了呢?你还得改代码。

这时候,单行单列子查询 闪亮登场!它返回的结果就是一个单独的值 ,可以直接用在 WHERE 条件里。

sql 复制代码
SELECT employee_name, salary
FROM employees
WHERE salary > (
    -- 这个子查询返回一个值:公司的平均工资
    SELECT AVG(salary) FROM employees
);

💡 恍然大悟的瞬间:

太优雅了!这条SQL是"活"的,无论员工数据怎么变,它总能动态地计算出最新的平均工资并进行比较。它不仅能用在WHERE子句,甚至能用在SELECT列表里,直接当作一个字段来显示。

比如,我想看每个员工的工资和他与平均工资的差距:

sql 复制代码
SELECT
    employee_name,
    salary,
    (SELECT AVG(salary) FROM employees) AS avg_salary,
    salary - (SELECT AVG(salary) FROM employees) AS diff_from_avg
FROM employees;

踩坑经验 📌

  • 必须是单行单列! 这是铁律。如果你的子查询不小心返回了多行或多列,数据库会立刻给你一个大大的Error。用之前,先确保你的子查询逻辑只会产生唯一一个值。
场景三:用"多列子查询"构建动态的"虚拟表"

这是我认为子查询最强大的用法,也是区分SQL新手和老手的关键。

需求来了: 我们要做一个用户活跃度分析报表,需要展示每个用户的ID、姓名、他的总订单数,以及他最近一次登录的日期

这些信息分散在 usersorderslogin_logs 三张表里。用一堆复杂的 LEFT JOINGROUP BY 也能做,但SQL会写得像一坨意大利面,难以阅读和维护。

看我的"骚操作"------在 FROM 子句里使用子查询!

sql 复制代码
SELECT
    u.id,
    u.name,
    order_info.total_orders,
    login_info.last_login_time
FROM
    users u
LEFT JOIN (
    -- 子查询1: 把它当成一张叫 order_info 的虚拟表,预先计算好每个用户的订单总数
    SELECT user_id, COUNT(*) AS total_orders FROM orders GROUP BY user_id
) AS order_info ON u.id = order_info.user_id
LEFT JOIN (
    -- 子查询2: 把它当成一张叫 login_info 的虚拟表,预先计算好每个用户的最后登录时间
    SELECT user_id, MAX(login_time) AS last_login_time FROM login_logs GROUP BY user_id
) AS login_info ON u.id = login_info.user_id;

💡 恍然大悟的瞬间:

这简直是打开了新世界的大门!我可以在 FROM 子句里,通过子查询凭空造出我需要的"中间表" 。这个"表"是动态生成的,包含了预处理好的聚合数据。主查询的逻辑就变得非常清晰:只是简单地把 users 主表和两张"虚拟表"连接起来就行了。

这种用法,我们称之为 派生表(Derived Table) ,是 多列子查询 最常见的应用。它返回的结果集就像一张真正的表一样,有行有列。

延伸一下

  • DDL里的应用:这种"造表"的思路还能用在数据定义语言(DDL)里。比如,你可以基于一个复杂的查询结果,直接创建一张新表或一个视图(VIEW)。

    sql 复制代码
    -- 创建一张汇总表
    CREATE TABLE user_summary AS
    SELECT u.id, u.name, COUNT(o.id) as total_orders
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    GROUP BY u.id, u.name;
    
    -- 或者创建一个视图,视图本身不存数据,但像一张表一样可以被随时查询
    CREATE VIEW v_user_summary AS
    SELECT ...; -- (和上面CREATE TABLE里的查询一样)

四、总结:子查询是门艺术

从最初的回避和嫌弃,到现在在各种复杂查询中运用自如,我发现掌握子查询,不只是多会了一种SQL技巧,更是数据处理思维的一次升级

它教会我:

  1. 原子化思考:将复杂问题分解成一个个小的查询,然后像搭积木一样组合起来。
  2. 数据内聚:尽可能让数据处理的逻辑留在数据库内部完成,减少应用层和数据库之间不必要的交互。
  3. 代码优雅:写出更具可读性和维护性的SQL。

所以,下次再遇到需要多步操作才能获取的数据时,别急着写代码!先停下来想一想🤔:这个问题,一个漂亮的子查询能不能搞定?

相信我,当你熟练掌握它之后,你会爱上这种在数据世界里运筹帷幄的感觉。好了,今天的分享就到这里,希望对你有帮助!下次见!👋

相关推荐
酷ku的森4 分钟前
Redis中的hash数据类型
数据库·redis·哈希算法
Arva .16 分钟前
Redis
数据库·redis·缓存
DemonAvenger16 分钟前
MySQL与应用程序的高效交互模式:从基础到实战的最佳实践
数据库·mysql·性能优化
博一波33 分钟前
Redis 集群:连锁银行的 “多网点智能协作系统”
数据库·redis·缓存
HashData酷克数据39 分钟前
官宣:Apache Cloudberry (Incubating) 2.0.0 发布!
数据库·开源·apache·cloudberry
秋难降39 分钟前
SQL 索引突然 “罢工”?快来看看为什么
数据库·后端·sql
TDengine (老段)1 小时前
TDengine 时间函数 TODAY() 用户手册
大数据·数据库·物联网·oracle·时序数据库·tdengine·涛思数据
码界奇点1 小时前
KingbaseES一体化架构与多层防护体系如何保障企业级数据库的持续稳定与弹性扩展
数据库·架构·可用性测试
Access开发易登软件2 小时前
Access开发导出PDF的N种姿势,你get了吗?
后端·低代码·pdf·excel·vba·access·access开发
悟乙己2 小时前
数据科学家如何更好地展示自己的能力
大数据·数据库·数据科学家