无法正常使用LEFT OUTER JOIN的问题

28

我以为我理解了左连接的工作原理,但是我遇到了一种情况,它没有起作用,而且我不确定我的查询结构是否有误,或者它是一个数据问题。

背景:我有以下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)

我在这里错过了什么?从我的理解来看,基本查询语句对我来说是正确的,但显然我缺少一些关键信息。


8
+1 很好的详细问题..... - Manse
5个回答

17

虽然已经有许多人回答了,但我也会尝试并希望能提供更多的解释。 我一直以来的理解是,(你可以查看我回复的许多其他帖子中的LEFT JOIN),我首先列出要从中获取所有信息的表(左侧...因此从左到右阅读)。 然后在它们之间的任何条件上左连接到“Other”表格(右侧)... 然后,在进行左连接时,如果对右侧表格有其他条件,则这些条件将保留该连接条件。通过将它们带入“WHERE”子句会意味着INNER JOIN(必须始终匹配),这不是你想要的...我还尝试始终显示左表别名.field = 右表别名.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)

注意到“a”和“ga”之间的直接关系,它们共享相同的ID和游戏ID值,然后附加了特定的玩家。where子句仅关心基于特定游戏的外部成就。


1
我在你和@Benoit的回答之间犹豫不决(你们两个都得到了赞),但是你对“最佳实践”的澄清和讨论是我选择在这里打勾的原因。谢谢! - TheIcemanCometh
很好的答案。大多数人做的是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 - kev

9
在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 

请记住,连接是在此时执行的,如果条件不满足,则会出现NULL值;然后应用where过滤器。


3

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)

2

这是因为这一行代码:

where  ga.gamer_id = 'fba8fcaa-f57b-44c6-9431-4ab78605b024'

如果游戏玩家没有获得成就,那么ga.gamer_id的值将为NULL,不符合WHERE条件。

0
我的猜测是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。 - Benoit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接