☰
LEFT JOIN 和 INNER JOIN 差在哪?4 种 JOIN 同一份数据跑一遍
2026/10/18 8:10:07 网站建设 项目流程

6 个用户、8 条订单,同一份数据换成四种连表方式,结果行数分别是 7、8、8、9。

多出来的行不是随便凑的。

LEFT JOIN 多的是一个从没下过单的用户,RIGHT JOIN 多的是一条查不到用户的订单。

在线案例与源码:https://atomgit.com/deli007/demo_park/tree/main/codearts-sql-join-visualizer
成品只有一个 index.html,双击就能用。

一、为什么值得做:这题天天有人搜,但很少有人把四种放一起数

搜 left join 和 inner join 的区别,前面几页答案长得差不多:各给一张结果表,再补一句「左表全保留」。

看完还是答不上来:同一份数据,换一种连法,到底差在哪几行。

所以我把它做成一个页面,四种连法压在同一份数据上,行数直接可数,多出来的行点一下就知道是谁。

二、准备环境:打开码道 Web,把需求写成一段话

浏览器打开码道 Web 版:https://devcloud.cn-north-4.huaweicloud.com/chat?source=dmzntgwsf&sourcead=dmzntgwwbwz ,登录后在对话框里写需求,就能拿到一个能跑的单文件应用。

码道有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用 WebUI 版演示。

需求我写了 8 条,事后看最值钱的是第 4 条和第 5 条。

第 4 条要求点结果表任意一行能高亮它的来源组合,第 5 条要求顶部一张表同时列出四种连法的结果行数。

有了这两条,页面就从一张静态结果表,变成能自己数、自己点的东西。

三、需求原文(可以照着复制)

做一个单文件网页小应用 index.html,主题是「SQL JOIN 连表可视化实验室」,中文界面,全部代码内联在 index.html 里,不引用任何 CDN 或外部资源(断网也能跑)。 要求: 1. 内置两份固定示例数据,直接写在代码里:用户表 users(6 行,列为 id、name、city,其中 1 行的 city 为空),订单表 orders(8 行,列为 id、user_id、amount,其中 1 行的 user_id 在 users 表里不存在)。 2. 支持切换 4 种连表方式:INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN,连表条件都是 users.id = orders.user_id。 3. 主区域并排显示「左表 users」「右表 orders」「结果表」三块,结果表逐行列出连表后的真实记录,把 NULL 用醒目的样式标出来。 4. 点结果表里任意一行,能在左右表中高亮出这一行是由哪几行组合出来的。 5. 顶部放一张统计表,一次列出四种 JOIN 各自的结果行数,能一眼看出哪种 JOIN 比 INNER JOIN 多出几行。 6. 内置自检面板:页面加载后自动跑一组断言(四种 JOIN 的行数、外连接里是否出现 NULL 等),显示断言总数与通过数。 7. 提供「自定义数据」输入区:可以粘贴自己的两张表(CSV 文本)后重新计算;如果粘贴的文本格式不对(列名不匹配、没有数据行),用中文明确提示原因,并保留上一次的有效结果。 8. 所有计算都在浏览器本地完成,不发起任何网络请求。 完成后打开预览,让我直接看到运行效果。

四、页面长什么样:三张表并排,点一行就看来源

上半部分是连表方式切换按钮和行数对比表,下半部分是左表、右表、结果表三块并排。

点结果表任意一行,左表和右表里对应的行一起高亮,底部提示条写出这一行是从哪几行拼出来的。

页面用颜色区分四种来源:绿色是双方匹配上的,黄色是仅左表保留的,蓝色是仅右表保留的,红色是补位的 NULL。

五、同一份数据,四种连法就是四个数

连表方式结果行数比 INNER JOIN 多多出来的那一行是谁
INNER JOIN7基准—
LEFT JOIN8+1用户 David,他一条订单都没有
RIGHT JOIN8+1订单 #107,它的 user_id 是 7,用户表里没有 7 号
FULL JOIN9+2上面两行都在

6 乘 8 一共 48 种组合,真正匹配上的只有 7 种,这就是 INNER JOIN 的 7 行。

LEFT JOIN 和 RIGHT JOIN 都是 8 行,但多出来的不是同一行,这是最容易记混的地方。

六、多出来的行,一个来自左表,一个来自右表

David 在用户表里,但订单表里没有他的 user_id,所以 LEFT JOIN 把他留下来,右侧三列补 NULL。

订单 #107 是反过来那种:它在订单表里,但用户表里没有 7 号用户,RIGHT JOIN 把它留下来,左侧补 NULL。

一句话概括:谁写在 JOIN 左边,就决定补 NULL 往哪边补。

七、核心那段代码:一次双循环,四种连法共用

joinTables() 先老老实实穷举所有匹配,再用两个标记数组记下谁被匹配过。

functionjoinTables(left,right,t){varrows=[];varleftMatched=newArray(left.length).fill(false);varrightUsed=newArray(right.length).fill(false);varkeysL=left.map(function(r){returnkeyOf(r.id);});varkeysR=right.map(function(r){returnkeyOf(r.user_id);});for(vari=0;i<left.length;i++){for(varj=0;j<right.length;j++){if(keysEqual(keysL[i],keysR[j])){rows.push({li:i,ri:j,left:left[i],right:right[j]});leftMatched[i]=true;rightUsed[j]=true;}}}if(t==="LEFT"||t==="FULL"){for(i=0;i<left.length;i++){if(!leftMatched[i])rows.push({li:i,ri:-1,left:left[i],right:null});}}if(t==="RIGHT"||t==="FULL"){for(j=0;j<right.length;j++){if(!rightUsed[j])rows.push({li:-1,ri:j,left:null,right:right[j]});}}returnrows;}

四种连法的差别全在后面两段:LEFT 补 leftMatched 里没被标记的行,RIGHT 补 rightUsed 里没被标记的,FULL 两边都补。

被匹配过的行记在 leftMatched、rightUsed 里,所以补出来的行不会重复。

八、验证与踩坑

页面加载后会自己跑一遍断言,内置数据下是 18/18 通过。

原文几条:INNER JOIN 行数 = 独立统计的匹配对数(7)、LEFT JOIN 行数 = INNER 行数 + 左表未匹配行数(1)、FULL JOIN 中含 NULL 的行数 = 左右未匹配总数(2)。

前 12 条是通用关系式,换成任何数据都该成立;最后 6 条才针对内置数据写死行数。

第一个边界是空值,用户 Eve 的 city 是空字符串,结果表里显示成红色 NULL。

也就是说这个页面把空串当 NULL 处理,而真实 SQL 里 ‘’ 和 NULL 并不等价。

第二个边界是键的比较方式。

我拿一版自定义数据试过:左表 id 写 ‘01’ 和 ‘1’,右表 user_id 写 ‘1’ 和 ‘001’。

四种连法都给 4 行,因为页面把键归一化成数字再比,2 乘 2 全叉乘。

真实 SQL 里字符串列的 ‘01’ = ‘1’ 是假,只有等值的那一对会连上。

第三个边界在自检面板上,换成自定义数据后它显示 12/13,不是 18/18。

原因是那条固定期望断言被跳过了,但仍然算在分母里。

失败路径我也点了一遍,往左表文本框里粘一份列数对不上的 CSV。

页面给的原话是「users 表 第 3 行列数少于表头(表头 3 列,该行只有 2 列)」,后面跟一句「已保留上一次有效结果,请修正后重试」。

它没白屏,也没静默失败,结果表里还是上一次的 6 行数据。

九、实测数据汇总

项目实测值
内置左表 users6 行,列为 id、name、city
内置右表 orders8 行,列为 id、user_id、amount
四种连法行数7 / 8 / 8 / 9
页面自检18/18 通过
单个 HTML 体积34959 字符,无外链
页面网络请求0 条
失败提示原文users 表 第 3 行列数少于表头(表头 3 列,该行只有 2 列)

最后一行是我用浏览器网络面板复核的,打开页面到来回切四种连法,请求列表一直是空的。

十、本地复现

双击 index.html 就能用,不需要装任何依赖。

想走静态服务:python -m http.server 8000 ,然后访问 http://localhost:8000 。

想看它有没有偷偷联网,按 F12 打开网络面板再切几次连表方式,列表里不会多出请求。

十一、说点不足

MySQL 8 及以前的版本没有 FULL JOIN,页面演示的是 SQL 标准里的语义。

真要在 MySQL 里写全连接,得用 LEFT JOIN 和 RIGHT JOIN 拼一个 UNION。

连接条件只支持单列等值,多条件 ON 和不等值连接没做。

键会先按数字归一化再比较,用字符串主键的场景要留意上面说的那条边界。

直接下载试玩

这个 Demo 已经完整开源(源代码、本文档、全部截图),托管在公开仓库,可直接下载来玩:

  • Demo Park 仓库:https://atomgit.com/deli007/demo_park
  • 本案例目录:https://atomgit.com/deli007/demo_park/tree/main/codearts-sql-join-visualizer

打开上面的链接,进入 codearts-sql-join-visualizer 文件夹,把 index.html 下载到本地,用浏览器打开即可运行,无需安装任何依赖。

十二、用码道做这个页面的体会

第一条是需求里要写清验收方式,我把「完成后打开预览」固定加在需求末尾,省掉了追问的来回。

第二条是数字要写进需求,行数对比表是我点名要的,多出来的行数直接变成页面上的 +1、+2。

第三条是失败路径要单独交代,我写了格式错误要给中文原因并保留上一次结果,交回来的就是列数不符的提示。

第四条是自检这种东西得先想清楚关系式,写死数字只能证明这一份数据对,关系式才证明引擎没算错。

第五条是生成物要自己验,四种连法的行数、空值边界、自定义数据的叉乘,都是我在浏览器里一条条点出来的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询