您好!欢迎访问家园网-www.jy.wang!

家园网

行查询

网络 作者:本站 点击:


SELECT goods_id,goods_name,shop_price FROM (SELECT TOP 6 * FROM goods) aa
WHERE NOT EXISTS (SELECT * FROM (SELECT TOP 5 * FROM goods) bb 
WHERE aa.goods_id=bb.goods_id);

这段 SQL 非常有意思,它展示了一个古老但经典的分页查询技巧——利用 TOP 和 NOT EXISTS 实现"取第 6 到第 10 条"的数据。让我从执行逻辑、设计意图和局限性三个角度为你拆解。

🎯 这段 SQL 在做什么?

目标:从 goods 表中取出 第 6 到第 10 条 记录(按某种隐含顺序)。

输出:商品ID、名称、价格。

📝 逐层拆解执行逻辑

这是一个嵌套子查询的典型写法,执行顺序是从内向外。我们一步步推演:

第 1 步:最内层子查询(bb)

sql

(SELECT TOP 5 * FROM goods) bb

  • 取出 goods 表的 前 5 条 记录(按表的物理存储顺序或主键顺序)

  • 这 5 条记录被命名为临时表 bb

  • 关键:此时还不确定具体是哪 5 条,取决于数据库的默认排序(通常按主键或插入顺序)

第 2 步:外层子查询(aa)

sql

(SELECT TOP 6 * FROM goods) aa

  • 取出 goods 表的 前 6 条 记录,命名为 aa

第 3 步:主查询的 NOT EXISTS 过滤

sql

WHERE NOT EXISTS (
    SELECT * FROM bb 
    WHERE aa.goods_id = bb.goods_id)

NOT EXISTS 的逻辑是:只保留 aa 中不在 bb 里的那些记录。

用集合论来解释:

  • aa = 第 1 条 + 第 2 条 + 第 3 条 + 第 4 条 + 第 5 条 + 第 6 条

  • bb = 第 1 条 + 第 2 条 + 第 3 条 + 第 4 条 + 第 5 条

  • aa - bb = 第 6 条

所以最终结果只返回 第 6 条记录

🤔 等等,题目说"第 6 到第 10 条"?

这里有个重大误解!让我仔细看你的原 SQL:

sql

SELECT TOP 6 * FROM goods    -- aa: 取前6条SELECT TOP 5 * FROM goods    -- bb: 取前5条

按照集合减法逻辑,这只能得到 第 6 条,而不是第 6 到第 10 条。

要获取第 6-10 条(共5条记录),正确的写法应该是

sql

-- 方法1:使用 TOP 配合子查询(SQL Server 2000 时代的标准写法)SELECT * FROM (
    SELECT TOP 10 * FROM goods ORDER BY goods_id) aaWHERE NOT EXISTS (
    SELECT * FROM (
        SELECT TOP 5 * FROM goods ORDER BY goods_id    ) bb    WHERE aa.goods_id = bb.goods_id)

或者更现代的写法(SQL Server 2012+):

sql

SELECT * FROM goodsORDER BY goods_idOFFSET 5 ROWSFETCH NEXT 5 ROWS ONLY

🔍 为什么这段 SQL 现在很少见了?

这种写法流行于 SQL Server 2000 及更早版本(那时还没有 ROW_NUMBER() 和 OFFSET...FETCH),它有明显的缺陷

1. ❌ 没有 ORDER BY,结果不确定

你的原始 SQL 中两个子查询都没有 ORDER BY,这意味着"前 5 条"和"前 6 条"的定义是随机的(取决于表的物理存储顺序)。同一个查询在不同时间执行可能返回不同的结果。

正确做法:必须加 ORDER BY 保证顺序一致:

sql

SELECT TOP 6 * FROM goods ORDER BY goods_id

2. ❌ 性能极差

  • 子查询 aa 和 bb 各自独立扫描表(或索引)

  • NOT EXISTS 对 aa 的每一条记录都要检查 bb

  • 随着数据量增大,性能指数级下降

3. ❌ 逻辑晦涩难懂

即使是有经验的开发者,看到这种写法也需要在脑子里画集合图才能理解意图。远不如现代的 ROW_NUMBER() 或 OFFSET...FETCH 直观。

📊 现代替代方案(按数据库分类)

SQL Server 2012+

sql

SELECT goods_id, goods_name, shop_priceFROM goodsORDER BY goods_idOFFSET 5 ROWSFETCH NEXT 5 ROWS ONLY;

SQL Server 2005-2012

sql

SELECT * FROM (
    SELECT *, ROW_NUMBER() OVER (ORDER BY goods_id) AS rn    FROM goods) tWHERE rn BETWEEN 6 AND 10;

MySQL / PostgreSQL

sql

SELECT goods_id, goods_name, shop_priceFROM goodsORDER BY goods_idLIMIT 5 OFFSET 5;

🎓 历史价值与学习意义

虽然这段代码在现代开发中不应该被使用,但理解它对学习 SQL 有独特价值:

知识点在这段代码中的体现
TOP 关键字SQL Server 特有的限制返回行数的语法
子查询嵌套三层子查询的嵌套执行顺序
NOT EXISTS半连接(semi-join)的典型用法
集合运算思维用集合减法模拟 MINUS/EXCEPT 操作
分页演进史了解分页技术从 TOP+NOT EXISTS 到 ROW_NUMBER() 再到 OFFSET...FETCH 的演变

💡 总结

  • 你的 SQL:返回第 6 条记录(如果加了 ORDER BY),而不是第 6-10 条

  • 历史定位:SQL Server 2000 时代的分页 hack,现代应避免使用

  • 主要问题:缺少排序导致结果不确定、性能差、可读性差

  • 推荐方案:SQL Server 2012+ 用 OFFSET...FETCH,旧版本用 ROW_NUMBER()

如果你在学习老代码或者维护遗留系统,这种写法还会遇到。但在新项目中,请毫不犹豫地使用现代分页语法,它们更清晰、更高效、更易维护


后边2个where的内容再讲下

SELECT goods_id,goods_name,shop_price FROM (SELECT TOP 6 * FROM goods) aa
WHERE NOT EXISTS (SELECT * FROM (SELECT TOP 5 * FROM goods) bb 
WHERE aa.goods_id=bb.goods_id);


好的,我们来深入拆解 NOT EXISTS 及其内部的子查询。这是理解整条 SQL 的关键。

为了更清晰,我们假设 goods 表有 10 条数据,goods_id 从 1 到 10,并且假设表的默认顺序就是按 goods_id 升序。

核心逻辑:找“差集”

这条 SQL 的本质是:从“前 6 条”中,去掉“也存在于前 5 条”的那些,剩下的就是第 6 条。


第一步:理解最内层的 WHERE

看最核心的匹配条件:

sql

WHERE aa.goods_id = bb.goods_id

这很简单:就是判断 aa 里的某条记录,它的 goods_id,在 bb 里能不能找到一样的。

第二步:理解 NOT EXISTS

EXISTS 的意思是“存在”。NOT EXISTS 就是“不存在”。

整个 WHERE NOT EXISTS (...) 的读法是:

对于 aa 中的每一条记录,检查括号里的子查询。如果子查询查不到任何数据,那么这条 aa 的记录就符合条件,会被选中。


第三步:完整的执行过程(模拟数据库思维)

数据库会逐行扫描 aa(前 6 条),对每一行都去问 NOT EXISTS 那个问题。

360截图20260416113419531.jpg

结果:只输出了 goods_id = 6 的那一条记录。


为什么这个写法容易让人困惑?

  1. 子查询里的 SELECT *:你可能会想,“它选了 bb 的所有列,怎么比较的?”

    • 重点EXISTS 子句里的 SELECT * 是不参与比较的。它只是语法上的占位符。数据库只关心子查询有没有返回行,根本不关心返回的是什么内容。真正的比较全靠后面的 WHERE aa.goods_id = bb.goods_id

  2. 三层嵌套aa 和 bb 都是子查询,这让大脑需要多转几个弯。可以把它简化为两步思维:

    • 第一步bb 是“前 5 条”,aa 是“前 6 条”。

    • 第二步:从 aa 里剔除 bb

用更直观的集合语言翻译

如果用白话文翻译这个 WHERE,就是:

从 aa(前6条)里,选出那些 goods_id 不在 bb(前5条)的 goods_id 集合里 的记录。

在支持 EXCEPT(差集运算符)的数据库里,这个查询本可以写成:

sql

SELECT * FROM (SELECT TOP 6 * FROM goods) aaEXCEPTSELECT * FROM (SELECT TOP 5 * FROM goods) bb;

逻辑上完全一样,但 NOT EXISTS 是更通用、更底层的实现方式。

总结

  • aa.goods_id = bb.goods_id:定义“什么是相同的记录”(关联条件)。

  • NOT EXISTS:执行“排除”动作,只保留那些在子查询里找不到匹配的记录。

  • 两者配合:实现了集合减法(A - B)的效果,最终从 6 条里减掉 5 条,剩下 1 条。

这段 SQL 巧妙地用关系运算实现了分页,是 SQL 发展史上一个很有趣的写法


标签: