Могу ли я указать порядок, в котором происходят объединения?

#mysql #join #left-join #inner-join

#mysql #Присоединиться #левое соединение #внутреннее соединение

Вопрос:

Допустим, у меня есть три таблицы, A, B и C. Концептуально A (необязательно) имеет один B, а B (всегда) имеет один C.

 Table A:
a_id
... other stuff

Table B:
a_fk_id (foreign key to A.a_id, unique, primary, not null)
c_fk_id (foreign key to C.c_id, not null)
... other stuff

Table C:
c_id
... other stuff
  

Я хочу выбрать все записи из A, а также связанные с ними записи из B и C, если они присутствуют. Однако данные B и C должны встречаться в результате только в том случае, если присутствуют оба B и C.

Я чувствую, что хочу сделать:

 SELECT *
FROM
    A
    LEFT JOIN B on A.a_id=B.a_fk_id
    INNER JOIN C on B.c_fk_id=C.c_id
  

Но объединения, похоже, остаются ассоциативными (первое объединение происходит перед вторым объединением), поэтому это не даст записей из A, у которых нет записи в C.

AFAICT я должен использовать подзапросы, что-то вроде:

 SELECT *
FROM
    A
    LEFT JOIN (
        SELECT * FROM B INNER JOIN C ON B.c_fk_id=C.c_id
    ) as tmp ON A.id = tmp.a_fk_id
  

но как только у меня появляется пара таких отношений в запросе (на самом деле у меня может быть два или три вложенных), я беспокоюсь как о сложности кода, так и об оптимизаторе запросов.

Есть ли у меня способ указать порядок соединения, отличный от этого метода подзапроса?

Спасибо.

Ответ №1:

В SQL Server вы можете сделать

 SELECT *
FROM   a
       LEFT JOIN b
                 INNER JOIN c
                   ON b.c_fk_id = c.c_id
         ON a.id = b.a_fk_id  
  

Позиция ON предложения означает, что LEFT JOIN b логически on происходит последним. Насколько я знаю, это стандартно (заявлено, что здесь прописан ANSI), но я уверен, что отрицательные голоса уведомят меня, если это не сработает в MySQL!

Комментарии:

1. Как ни странно, это работает. Я протестировал его на таблицах, которые я создал для своего ответа, и он дает тот же результат, что и моя версия. С таким синтаксисом я никогда раньше не сталкивался, поэтому мое первое впечатление таково, что кому-либо еще будет трудно его понять, учитывая, что я неплохо разбираюсь в MySQL, но как только вы узнаете, что происходит, возможно, не все так плохо. пожимаю плечами, не уверен, но я проверю, что это работает на MySQL 5.1.19.

2. MySQL поддерживает вложенные объединения с 5.0.1 ( документы ).

3. @bfavaretto — Спасибо за ссылку. Похоже, в этом ответе на самом деле могут отсутствовать некоторые круглые скобки?

4. @MartinSmith, я не уверен, но я думаю, что круглые скобки необязательны (или, по крайней мере, не являются необходимыми в некоторых случаях).

5. Это действительно чертовски хорошо, но я не понял ваш запрос

Ответ №2:

Редактировать: И это то, что я получаю за то, что говорю быстрее, чем я думаю. Мое предыдущее решение не работает, потому что ‘c’ еще не был присоединен. Давайте попробуем это снова.

Мы можем использовать предложение WHERE, чтобы ограничить результаты только теми, которые соответствуют критериям, которые вы ищете, где C имеет допустимое значение (НЕ РАВНО NULL) или B не имеет значения (РАВНО NULL). Вот так:

 SELECT *
FROM a
    LEFT JOIN b ON (b.a = a.a)
    LEFT JOIN c ON (c.b = b.b)
WHERE (c.c IS NOT NULL OR b.b IS NULL);
  

Без результатов WHERE:

 mysql> SELECT * FROM a LEFT JOIN b ON (b.a = a.a) LEFT JOIN c ON (c.b = b.b);
 ------ ------ ------ ------ ------ 
| a    | a    | b    | c    | b    |
 ------ ------ ------ ------ ------ 
|    1 |    1 |    1 |    1 |    1 |
|    1 |    1 |    2 | NULL | NULL |
|    2 |    2 |    3 |    2 |    3 |
|    3 | NULL | NULL | NULL | NULL |
|    4 | NULL | NULL | NULL | NULL |
 ------ ------ ------ ------ ------ 
  

С результатами WHERE:

 mysql> SELECT * FROM a LEFT JOIN b ON (b.a = a.a) LEFT JOIN c ON (c.b = b.b) WHERE (c.c IS NOT NULL OR b.b IS NULL);
 ------ ------ ------ ------ ------ 
| a    | a    | b    | c    | b    |
 ------ ------ ------ ------ ------ 
|    1 |    1 |    1 |    1 |    1 |
|    2 |    2 |    3 |    2 |    3 |
|    3 | NULL | NULL | NULL | NULL |
|    4 | NULL | NULL | NULL | NULL |
 ------ ------ ------ ------ ------ 
  

Комментарии:

1. Спасибо. Пошел с Мартином, поскольку он избегает WHERE . IIUC WHERE используется после объединения, поэтому другое решение может удалять строки раньше и, следовательно, более эффективно. Может быть, я говорю о своем дне здесь. И оптимизатор может позаботиться об этом.

2. Если вы хотите знать, какой запрос более эффективен, просто добавьте к ним ОБЪЯСНЕНИЕ . Он покажет вам, какие индексы он будет использовать, и примерно сколько строк из каждой таблицы ему придется учитывать, чтобы получить свой ответ. Это хорошо для гипотез, но всегда старайтесь подтвердить. Я несколько раз сталкивался с MySQL с этой странной оптимизацией для больших наборов данных.

Ответ №3:

Да, вы используете STRAIGHT_JOIN для этого.
При использовании этого ключевого слова объединение будет происходить в точном порядке, который вы укажете.

Смотрите: http://dev.mysql.com/doc/refman/5.5/en/join.html

Комментарии:

1. Это не позволит показывать записи из ‘A’, если в ‘C’ нет записей. STRAIGHT_JOIN — это всего лишь инструмент оптимизации запросов; он не изменяет возвращаемые результаты.

Ответ №4:

Ну, я придумал и другое решение, и я публикую его для полноты картины (хотя на самом деле я использую ответ Мартина).

Используйте ПРАВИЛЬНОЕ СОЕДИНЕНИЕ:

 SELECT 
    *
FROM
    b
    INNER JOIN c ON b.c_fk_id = c.c_id
    RIGHT JOIN a ON a.id = b.a_fk_id
  

Я почти уверен, что в каждой части, которую я читал о СОЕДИНЕНИЯХ, говорилось, что ПРАВИЛЬНЫЕ соединения бессмысленны, но вот вы где.