Проблемы с получением левого внешнего соединения на работу - PullRequest
28 голосов
/ 25 января 2012

Мне показалось, что я понял, как работают левые внешние объединения, но у меня ситуация, которая не работает, и я не уверен на 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 может быть любое количество строк, включая ни одной, если в этой игре не было достигнуто никаких достижений.Например, в приведенных выше примерах данных достижения с идентификаторами 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)

Что мне здесь не хватает?Исходя из того, что я понимаю, основной запрос выглядит правильно, но мне явно не хватает какой-то важной информации.

Ответы [ 5 ]

16 голосов
/ 25 января 2012

Многие ответили, но я тоже попробую и, надеюсь, предоставлю еще разъяснения.Как я всегда интерпретировал это (и вы можете проверить так много других постов, на которые я ответил с помощью ЛЕВЫХ объединений), я пытаюсь перечислить таблицу, в которой я хочу все сначала (слева ... отсюда читаем слева направо).Затем левое соединение с таблицей «Другое» (правая сторона) при любых критериях между ними ... Затем, при выполнении левого соединения и при наличии дополнительных критериев относительно правой таблицы эти условия остаются с этим условием соединения.,Включение их в предложение «WHERE» подразумевает ВНУТРЕННЕЕ СОЕДИНЕНИЕ (должно всегда совпадать), что не то, что вы хотите ... Я также стараюсь всегда показывать левую таблицу alias.field = правая таблица alias.field, чтобы сохранить корреляциюclear ... Затем примените условие 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" по общему идентификатору изначения идентификатора игры, но затем привязывается к конкретному игроку.Предложение where касается только внешнего уровня достижений, основанного на конкретной игре.

9 голосов
/ 25 января 2012

В предложении 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 голосов
/ 25 января 2012

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)
1 голос
/ 25 января 2012

Это из-за этой строки:

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

Если gamer не заработал achievement, значение ga.gamer_id будет равно NULL и не соответствует условиям WHERE.

0 голосов
/ 25 января 2012

Я предполагаю, что предложение 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) 
...