让 LEFT OUTER JOIN 工作的问题
Posted
技术标签:
【中文标题】让 LEFT OUTER JOIN 工作的问题【英文标题】:Problems getting LEFT OUTER JOIN to work 【发布时间】:2012-02-18 16:14:06 【问题描述】:我以为我了解了左外连接的工作原理,但我遇到了一种无法正常工作的情况,而且我不能 100% 确定我的查询结构是否不正确,或者是否是数据问题。
作为背景,我有以下 mysql 表结构:
mysql> describe achievement;
+-------------+----------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+----------------------+------+-----+---------+-------+
| id | varchar(64) | NO | PRI | NULL | |
| game_id | varchar(10) | NO | PRI | NULL | |
| name | varchar(64) | NO | | NULL | |
| description | varchar(255) | NO | | NULL | |
| image_url | varchar(255) | NO | | NULL | |
| gamerscore | smallint(5) unsigned | NO | | 0 | |
| hidden | tinyint(1) | NO | | 0 | |
| base_hidden | tinyint(1) | NO | | 0 | |
+-------------+----------------------+------+-----+---------+-------+
8 rows in set (0.00 sec)
和
mysql> describe gamer_achievement;
+----------------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+---------------------+------+-----+---------+-------+
| game_id | varchar(10) | NO | PRI | NULL | |
| achievement_id | varchar(64) | NO | PRI | NULL | |
| gamer_id | varchar(36) | NO | PRI | NULL | |
| earned_epoch | bigint(20) unsigned | NO | | 0 | |
| offline | tinyint(1) | NO | | 0 | |
+----------------+---------------------+------+-----+---------+-------+
5 rows in set (0.00 sec)
至于数据,这是我在这里填写的(为简洁起见,仅包括相关列):
+----+------------+------------------------------+
| id | game_id | name |
+----+------------+------------------------------+
| 1 | 1480656849 | Cluster Buster |
| 2 | 1480656849 | Star Gazer |
| 3 | 1480656849 | Flower Child |
| 4 | 1480656849 | Oyster-meister |
| 5 | 1480656849 | Big Cheese of the South Seas |
| 6 | 1480656849 | Hexic Addict |
| 7 | 1480656849 | Collapse Master |
| 8 | 1480656849 | Survivalist |
| 9 | 1480656849 | Tick-Tock Doc |
| 10 | 1480656849 | Marathon Mogul |
| 11 | 1480656849 | Millionaire Extraordinaire |
| 12 | 1480656849 | Grand Pearl Pooh-Bah |
+----+------------+------------------------------+
12 rows in set (0.00 sec)
和
+----------------+------------+--------------+---------+
| achievement_id | game_id | earned_epoch | offline |
+----------------+------------+--------------+---------+
| 1 | 1480656849 | 0 | 1 |
| 2 | 1480656849 | 0 | 1 |
| 3 | 1480656849 | 0 | 1 |
| 4 | 1480656849 | 1149789371 | 0 |
| 7 | 1480656849 | 1149800406 | 0 |
| 8 | 1480656849 | 0 | 1 |
| 9 | 1480656849 | 1149794790 | 0 |
| 10 | 1480656849 | 1149792417 | 0 |
+----------------+------------+--------------+---------+
8 rows in set (0.02 sec)
在这种特殊情况下,achievement
表是“主”表,将包含我一直想查看的信息。 gamer_achievement
表仅包含实际获得的成就信息。对于任何特定玩家的任何特定游戏,gamer_achievement
表中可以有任意数量的行 - 如果该游戏没有获得任何成就,则不包括任何行。例如,在上面的示例数据中,id 为 5、6、11 和 12 的成就尚未获得。
我目前写的是
select a.id,
a.name,
ga.earned_epoch,
ga.offline
from achievement a
LEFT OUTER JOIN gamer_achievement ga
ON (a.id = ga.achievement_id and a.game_id = ga.game_id)
where ga.gamer_id = 'fba8fcaa-f57b-44c6-9431-4ab78605b024'
and a.game_id = '1480656849'
order by convert (a.id, unsigned)
但这只是返回那些实际获得的成就的完整信息 - 右侧表 (gamer_achievement
) 中未获得的成就信息没有像我期望的那样显示为 NULL 值询问。这是我期望看到的:
+----+-------------------------------+--------------+---------+
| id | name | earned_epoch | offline |
+----+-------------------------------+--------------+---------+
| 1 | Cluster Buster | 0 | 1 |
| 2 | Star Gazer | 0 | 1 |
| 3 | Flower Child | 0 | 1 |
| 4 | Oyster-meister | 1149789371 | 0 |
| 5 | Big Cheese of the South Seas | NULL | NULL |
| 6 | Hexic Addict | NULL | NULL |
| 7 | Collapse Master | 1149800406 | 0 |
| 8 | Survivalist | 0 | 1 |
| 9 | Tick-Tock Doc | 1149794790 | 0 |
| 10 | Marathon Mogul | 1149792417 | 0 |
| 11 | Millionaire Extraordinaire | NULL | NULL |
| 12 | Grand Pearl Pooh-Bah | NULL | NULL |
+----+-------------------------------+--------------+---------+
12 rows in set (0.00 sec)
我在这里缺少什么?据我了解,基本查询对我来说是正确的,但我显然遗漏了一些关键信息。
【问题讨论】:
+1 好详细的问题..... 【参考方案1】:很多人已经回答了,但我也会尝试并希望能提供更多说明。我一直是如何解释它的(你可以查看我用 LEFT join 回复的许多其他帖子),我尝试从第一个开始列出我想要的所有内容(左侧......因此从左到右阅读)。然后在它们之间的任何条件下左连接到“其他”表(右侧)......然后,在进行左连接时,并且有针对右侧表的附加条件,这些条件将保持该连接条件.通过将它们带入“WHERE”子句将意味着 INNER JOIN(必须始终匹配)这不是您想要的...我还尝试始终显示左表 alias.field = 右表 alias.field 以保持相关性清除...然后,将 where 子句应用于您想要从第一个表中获得的基本条件.. 类似
select
a.id,
a.name,
ga.earned_epoch,
ga.offline
from
achievement a
LEFT OUTER JOIN gamer_achievement ga
ON a.id = ga.achievement_id
AND a.game_id = ga.game_id
AND ga.gamer_id = 'fba8fcaa-f57b-44c6-9431-4ab78605b024'
where
a.game_id = '1480656849'
order by
convert (a.id, unsigned)
通过公共 ID 和游戏 ID 值注意“a”和“ga”之间的直接关系,但随后会附加到特定的游戏玩家身上。 where 子句只关心基于特定游戏的外部成就。
【讨论】:
在您的答案和@Benoit 给出的答案(你们都得到了赞成)之间纠结,但您对“最佳实践”的澄清和讨论是我在此处打勾的原因。谢谢! 很好的答案。大多数人所做的是JOIN table1 t1 on t1.id = t2.id WHERE t1.game_id = t2.game_id
,这不是他们想要的。他们想要的是JOIN table1 t1 on t1.id = t2.id AND t1.game_id = t2.game_id
。【参考方案2】:
WHERE
子句从整个结果集中过滤结果。如果您只想对JOIN
应用过滤器,则可以将表达式添加到ON
子句。
在以下查询中,我已将适用于连接表 (ga.gamer_id =
) 的筛选表达式从 WHERE 子句移至 ON 子句。这可以防止表达式过滤掉 gamer_achievement 值为 NULL 的行。
SELECT a.id,
a.name,
ga.earned_epoch,
ga.offline
FROM achievement a
LEFT OUTER JOIN gamer_achievement ga
ON ga.achievement_id = a.id
AND ga.game_id = a.game_id
AND ga.gamer_id = 'fba8fcaa-f57b-44c6-9431-4ab78605b024'
WHERE
a.game_id = '1480656849'
ORDER BY CONVERT(a.id, UNSIGNED)
【讨论】:
【参考方案3】:我的猜测是 where 子句过滤掉了你想要的结果,将它移动到左连接可能会起作用。
select a.id,
a.name,
ga.earned_epoch,
ga.offline
from achievement a
LEFT OUTER JOIN gamer_achievement ga
ON (a.id = ga.achievement_id and
a.game_id = ga.game_id and
ga.gamer_id = 'fba8fcaa-f57b-44c6-9431-4ab78605b024' and
a.game_id = '1480656849')
order by convert (a.id, unsigned)
【讨论】:
实际上这会为每一个 id 不是 1480656849 的成就显示一个 NULL 值的行。【参考方案4】:因为这行:
where ga.gamer_id = 'fba8fcaa-f57b-44c6-9431-4ab78605b024'
如果gamer
没有获得achievement
,则ga.gamer_id
的值将是NULL
并且不符合WHERE
条件。
【讨论】:
【参考方案5】:在 WHERE 子句中,您丢弃了一些 LEFT JOIN 将用 NULL 值填充的行。您想将条件 ga.gamer_id = 'fba8fcaa-f57b-44c6-9431-4ab78605b024'
放在 JOIN 子句中。
另一种选择是:
LEFT OUTER JOIN (SELECT * FROM gamer_achievement
WHERE ga.gamer_id = 'fba8fcaa-f57b-44c6-9431-4ab78605b024'
) ga
记住,执行了join,此时如果条件不满足,就会出现NULL值;然后应用where
过滤器。
【讨论】:
以上是关于让 LEFT OUTER JOIN 工作的问题的主要内容,如果未能解决你的问题,请参考以下文章
使用 VIEW 和 LEFT OUTER JOIN 进行慢查询
Linq to Entities 和 LEFT OUTER JOIN 问题与 MANY:1 关系
SQL Server 中的 LEFT JOIN 与 LEFT OUTER JOIN
关于mysql中的left join和left outer join的区别