left join的总结

  EG1:通俗的讲
A left join B 的连接的记录数与A表的记录数同
A right join B 的连接的记录数与B表的记录数同
A left join B 等价B right join A
table A:
Field_K, Field_A
1          a
3          b
4          c
table B:
Field_K, Field_B
1          x
2          y
4          z
select a.Field_K, a.Field_A, b.Field_K, b.Field_B
from a left join b on a.Field_K=b.Field_K
Field_K    Field_A    Field_K    Field_B    
---------- ---------- ---------- ----------
1          a          1          x        
3          b          NULL      NULL
4          c          4          z        
select a.Field_K, a.Field_A, b.Field_K, b.Field_B
from a right join b on a.Field_K=b.Field_K
Field_K    Field_A    Field_K    Field_B    
---------- ---------- ---------- ----------
1          a          1          x        
NULL       NULL       2          y        
4          c          4          z  
这样的。
   table1            table2
   id,sex1           id   sex2
   a     1          a       4
   b     0
select id,sex1,sex2 from table1 left join table2 on table1.id=table2.id
  则,
  id  sex1  sex2 
  a     1    4
  b     0    null
也就是说left join 则连接左边表中所有记录都会出现,如果根据连接条件在table2中找不到相关记录,
则显示为null。right join 则显示右边表中的全部记录。inner join 则只有符合条件的记录才会出现在结果集中。

EG2∶有两表a和b,前两字段完全相同:(id int,name varchar(10)...) id          name       ----------- ---------- 1           a          2           b          3           c       以下的查询语句,你知道它的运行结果吗?: 1. select * from a left join b on a.id=b.id where a.id=1 2. select * from a left join b on a.id=b.id and a.id=1 3. select * from a left join b on a.id=b.id and b.id=1 4. select * from a left join b on a.id=1 结果: id          name        id          name        ----------- ----------- ----------- ----------- 1           10          1           10

(1 row(s) affected)

id          name        id          name        ----------- ----------- ----------- ----------- 1           10          1           10 2           20          NULL        NULL 3           30          NULL        NULL

(3 row(s) affected)

id          name        id          name        ----------- ----------- ----------- ----------- 1           10          1           10 2           20          NULL        NULL 3           30          NULL        NULL

(3 row(s) affected)

id          name        id          name        ----------- ----------- ----------- ----------- 1           10          1           10 1           10          2           20 1           10          3           30 2           20          NULL        NULL 3           30          NULL        NULL

(5 row(s) affected) 思路: left join 嘛, 无非是左边表为基础, 扫描右边表匹配的记录

先是左边表的第1条记录 1    a   按条件 a.id=1, 来扫描右边表的记录 对于右边表的每条记录, 显然 a.id=1 这个条件都是成立的, 所以第1条记录匹配后的结果是:

1      a       1        a 1      a       2        b 1      a       3        c

--------------------------------------------- 然后再扫描第2条记录 2     b 对于条件 a.id=1, 在边表中没有与之匹配的记录, 所以右边表为NULL 因此第2条记录匹配的结果为 2      b       NULL        NULL

---------------------------------------------- 第3条记录与第2条记录一样, 匹配的结果是 3      c       NULL        NULL

--------------------------------------- 因此最终结果是5条记录 1      a       1        a 1      a       2        b 1      a       3        c 2      b       null     null 3      c       null     null 内联接(典型的联接运算,使用像 = 或 <> 之类的比较运算符)。包括相等联接和自然联接。 内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行。例如,检索 students 和 courses 表中学生标识号相同的所有行。

 

外联接,外联接可以是左向外联接、右向外联接或完整外部联接。 在 FROM 子句中指定外联接时,可以由下列几组关键字中的一组指定:

LEFT JOIN 或 LEFT OUTER JOIN。 左向外联接的结果集包括 LEFT OUTER 子句中指定的左表的所有行,而不仅仅是联接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果集行中右表的所有选择列表列均为空值。

RIGHT JOIN 或 RIGHT OUTER JOIN。 右向外联接是左向外联接的反向联接。将返回右表的所有行。如果右表的某行在左表中没有匹配行,则将为左表返回空值。

FULL JOIN 或 FULL OUTER JOIN。 完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。

 

交叉联接。 交叉联接返回左表中的所有行,左表中的每一行与右表中的所有行组合。交叉联接也称作笛卡尔积。

查看评论
1楼 oldabc 2007-10-12 13:29发表 [回复] [引用] [举报]
<think>我们正在讨论的是如何将LEFT JOIN与ROW_NUMBER() OVER窗口函数结合使用。在之前的上下文中,我们详细介绍了ROW_NUMBER()的用法,现在用户希望了解在LEFT JOIN场景下如何使用这个窗口函数。 结合使用场景: 通常,我们会在LEFT JOIN的子查询中使用ROW_NUMBER()来为左表或右表的数据进行分组排序,然后在外部查询中通过条件(如rn=1)来选取每组的第一条(最新或最旧的)记录。 常见用例: 1. 左表数据需要关联右表中每个分组的最新一条记录(或其他排序规则下的第一条记录)。 2. 避免LEFT JOIN导致的数据重复(通过取右表每组一条记录)。 示例1:获取每个用户的最新订单信息(左表为用户表,右表为订单表) 步骤: a. 在订单表上使用ROW_NUMBER()按用户分组并按订单日期降序排序,标记行号。 b. 将用户表与这个带有行号的订单子表进行LEFT JOIN,并只取行号为1的记录(即每个用户的最新订单)。 示例2:获取每个部门的最新员工记录,同时保留没有员工的部门信息(左表为部门表,右表为员工表) 注意点: - 在LEFT JOIN的情况下,如果右表没有匹配的记录,则行号(rn)会为NULL,因此我们通常会在WHERE子句中使用rn=1来过滤时,也会保留那些右表为NULL的记录(因为rn=1的条件不会匹配到NULL,但我们需要保留左表所有记录,所以不能直接写rn=1,而是应该在子查询中先过滤出rn=1的记录,然后与左表LEFT JOIN)。 - 因此,我们通常会在子查询中先过滤出行号为1的记录,然后再进行LEFT JOIN。 下面给出具体示例: 假设有两张表: - 部门表(departments): id, name (部门ID,部门名称) - 员工表(employees): id, department_id, name, hire_date (员工ID,部门ID,姓名,入职日期) 需求:查询所有部门及其最新入职员工的信息(如果没有员工,则显示NULL) 方法1:在子查询中先选出每个部门最新员工,再与部门表LEFT JOIN ```sql SELECT d.id AS dept_id, d.name AS dept_name, e.emp_id, e.emp_name, e.hire_date FROM departments d LEFT JOIN ( SELECT id AS emp_id, department_id, name AS emp_name, hire_date, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY hire_date DESC) AS rn FROM employees ) e ON d.id = e.department_id AND e.rn = 1; ``` 解释: - 子查询中,对员工表按部门分组(PARTITION BY department_id),并按入职日期降序排序(ORDER BY hire_date DESC)生成行号rn。 - 然后只保留每个部门的第一条(即最新入职员工),即rn=1。 - 部门表LEFT JOIN这个子查询,这样即使某个部门没有员工,也会显示该部门信息,员工部分为NULL。 方法2:也可以将条件放在JOIN之后(但这样可能会先进行LEFT JOIN再过滤,效率可能较低,不推荐): ```sql SELECT d.id AS dept_id, d.name AS dept_name, emp.emp_id, emp.emp_name, emp.hire_date FROM departments d LEFT JOIN ( SELECT id AS emp_id, department_id, name AS emp_name, hire_date, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY hire_date DESC) AS rn FROM employees ) emp ON d.id = emp.department_id WHERE emp.rn = 1 OR emp.rn IS NULL; -- 注意:这里需要加上OR emp.rn IS NULL,因为当没有员工时,rn为NULL ``` 但是请注意,方法2中WHERE条件会过滤掉除了rn=1和NULL以外的所有记录,但是因为我们在子查询中已经生成了rn,而LEFT JOIN后,没有匹配的部门对应的员工记录都是NULL,所以我们需要保留这些NULL记录。然而,在子查询中我们并没有过滤rn=1,所以子查询会返回所有员工记录(带有rn),这样在LEFT JOIN后,每个部门会有多条记录(rn=1,2,3...),然后我们通过WHERE条件只取rn=1的以及没有员工(即rn为NULL)的记录。这样写虽然可以,但效率较低,因为子查询返回了所有员工记录,而且外部还要过滤。 因此,推荐使用方法1,在子查询中过滤出rn=1,这样LEFT JOIN时右表的数据量就大大减少。 注意事项: 1. 性能:在子查询中先过滤(rn=1)通常比在外部过滤(WHERE)更高效,因为减少了连接的数据量。 2. 索引:为了优化子查询中的ROW_NUMBER()窗口函数,应该在员工表的department_id和hire_date上建立索引(如(department_id, hire_date DESC))。 3. 多字段排序:当排序字段存在重复值时,ROW_NUMBER()会按照OVER子句中的ORDER BY顺序分配序号(即使值相同,也会分配不同序号)。如果希望相同值得到相同序号,应使用RANK()或DENSE_RANK()。 另一个示例:引用[1]中的例子实际上也是类似,但它是自连接,我们也可以改写成LEFT JOIN的形式。 引用[1]的改写(原查询是自连接,这里我们用LEFT JOIN的方式重写): 原查询: ```sql SELECT DISTINCT s.* FROM fdwend_ft AS c INNER JOIN ( SELECT *, row_number() OVER (partition BY Thao ORDER BY dwtime DESC) AS grow_id FROM fdwend_ft WHERE fdel = 0 ) s ON c.Thao = s.Thao WHERE s.grow_id = 1 ``` 这个查询实际上是通过自连接取每个Thao分组的最新一条(fdel=0)记录。这里使用了INNER JOIN,所以不会保留没有匹配的记录。如果我们要保留左表(原表c)中所有记录,包括那些fdel可能不为0的记录?注意原查询中子查询已经过滤了fdel=0。但是原查询中左表c并没有过滤条件,所以如果c中有fdel不为0的记录,那么由于INNER JOIN,这些记录不会出现。如果我们想保留左表所有记录(不管fdel是否为0),但只关联子查询中每个Thao分组的最新一条(且fdel=0)记录,那么应该用LEFT JOIN: ```sql SELECT c.*, s.* -- 注意:这里可能会重复列,需要具体指定 FROM fdwend_ft AS c LEFT JOIN ( SELECT *, row_number() OVER (partition BY Thao ORDER BY dwtime DESC) AS grow_id FROM fdwend_ft WHERE fdel = 0 ) s ON c.Thao = s.Thao AND s.grow_id = 1 -- 在ON条件中直接指定grow_id=1,这样每个Thao只取一条 ``` 但是注意,在LEFT JOIN中,ON条件中不能直接使用s.grow_id=1,因为grow_id是在子查询中生成的列,而子查询中已经生成了所有分组内的行号(包括大于1的)。所以我们需要在子查询内部先过滤出grow_id=1,这样外部LEFT JOIN时,每个Thao在子查询中最多只有一条记录。 因此,改写为: ```sql SELECT c.*, s.* -- 注意:这里可能会重复列,需要具体指定 FROM fdwend_ft AS c LEFT JOIN ( SELECT * FROM ( SELECT *, row_number() OVER (partition BY Thao ORDER BY dwtime DESC) AS grow_id FROM fdwend_ft WHERE fdel = 0 ) t WHERE t.grow_id = 1 -- 在子查询内部过滤 ) s ON c.Thao = s.Thao; ``` 这样,左表c中的所有记录都会保留,而右表s则是每个Thao分组中fdel=0且dwtime最新的记录(可能为NULL,如果没有符合条件的记录)。 总结: 结合使用LEFT JOIN和ROW_NUMBER()的关键步骤: 1. 在子查询中使用ROW_NUMBER()对需要关联的表进行分组排序。 2. 在子查询内部过滤出行号为1的记录(或者其它需要的行号)。 3. 将主表与这个子查询进行LEFT JOIN。 这样就能在保留主表所有记录的同时,关联每个分组中排序后的一条记录。 最后,根据要求,生成相关问题: </think> ### LEFT JOIN 与 ROW_NUMBER() OVER 结合使用详解 #### 1. **核心用途** 当需要在 `LEFT JOIN` 操作中获取关联表中**每组的最新/最旧记录**时,结合 `ROW_NUMBER()` 窗口函数是最佳实践。典型场景包括: - 获取用户的最新订单(保留无订单用户) - 查询产品最近的价格变更(保留未调价产品) - 关联设备最近的状态记录(保留无记录设备) #### 2. **基础语法结构** ```sql SELECT 左表.*, 右表过滤后数据.* FROM 主表 AS 左表 LEFT JOIN ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY 分组字段 ORDER BY 排序字段 DESC ) AS rn FROM 右表 WHERE 过滤条件 ) AS 右表过滤后数据 ON 左表.关联字段 = 右表过滤后数据.关联字段 AND 右表过滤后数据.rn = 1 -- 取每组第一条 ``` #### 3. **实际案例演示** ##### 场景:获取所有用户及其最近订单 ```sql SELECT u.user_id, u.user_name, o.latest_order_date, o.order_amount FROM users u LEFT JOIN ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY order_date DESC -- 按日期取最新订单 ) AS rn FROM orders WHERE status = 'completed' -- 只处理已完成订单 ) o ON u.user_id = o.user_id AND o.rn = 1; -- 只关联最新订单 ``` ##### 结果示例: | user_id | user_name | latest_order_date | order_amount | |---------|-----------|-------------------|-------------| | 101 | Alice | 2023-10-01 | $150 | | 102 | Bob | NULL | NULL | ← 无订单用户保留 | 103 | Carol | 2023-10-05 | $89 | #### 4. **性能优化技巧** 1. **子查询过滤优先** 在子查询内部添加 `WHERE` 条件减少数据处理量: ```sql LEFT JOIN ( SELECT * FROM orders WHERE order_date > '2023-01-01' -- 先过滤数据 ... ) o ... ``` 2. **索引优化** 为右表创建复合索引加速窗口函数: ```sql CREATE INDEX idx_user_orders ON orders(user_id, order_date DESC); ``` 3. **避免全表扫描** 添加分区键过滤条件: ```sql WHERE 分区字段 = 'value' -- 缩小窗口计算范围 ``` #### 5. **特殊场景处理** ##### 保留无分组记录的左表数据 当右表无对应分组时,`rn` 列为 `NULL`,需显式处理: ```sql SELECT u.user_id, COALESCE(o.order_amount, 0) AS amount -- NULL转0 ... ``` ##### 多层级分组排序 ```sql ROW_NUMBER() OVER ( PARTITION BY department, product_category ORDER BY update_time DESC ) ``` #### 6. **与 INNER JOIN 对比** | 类型 | 结果特点 | 适用场景 | |------|----------|----------| | `LEFT JOIN + ROW_NUMBER()` | 保留左表所有记录<br>右表只返回每组第一条 | 主从表关联取最新记录 | | `INNER JOIN + ROW_NUMBER()` | 只返回有匹配的记录 | 严格匹配过滤场景[^1] | > ⚠️ 注意:在 MySQL 8.0 以下版本需用变量模拟 `ROW_NUMBER()`[^3] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值