Стандарт SQL относительно левого внешнего соединения и условий где - PullRequest
7 голосов
/ 21 мая 2010

Я получаю разные результаты в зависимости от условия фильтра в запросе, в зависимости от того, где я размещаю условие фильтра. Мои вопросы:

  • Есть ли техническая разница между этими запросами?
  • Есть ли что-нибудь в стандарте SQL это объясняет разные наборы записей из запросов?

Учитывая упрощенный сценарий:

--Table: Parent  Columns: ID, Name, Description
--Table: Child   Columns: ID, ParentID, Name, Description

--Query 1
SELECT p.ID, p.Name, p.Description, c.ID, c.Name, c.Description
FROM   Parent p
   LEFT OUTER JOIN Child c ON (p.ID = c.ParentID)
WHERE  c.ID IS NULL OR c.Description = 'FilterCondition'

--Query 2
SELECT p.ID, p.Name, p.Description, c.ID, c.Name, c.Description
FROM   Parent p
   LEFT OUTER JOIN Child c
   ON (p.ID = c.ParentID AND c.Description = 'FilterCondition')

Я предполагал, что запросы будут возвращать одинаковые наборы результатов, и я был удивлен, когда они этого не сделали. Я использую MS SQL2005, и в реальных запросах запрос 1 возвратил ~ 700 строк, а запрос 2 вернул ~ 1100 строк, и я не смог определить шаблон, по которому были возвращены строки, а какие были исключены. В запросе 1 было еще много строк с дочерними строками с данными и данными NULL. Я предпочитаю стиль запроса 2 (и я думаю, что он более оптимален), но я думал, что запросы будут возвращать те же результаты.

Редактировать / Резюме:

Здесь были даны отличные ответы. Мне было трудно выбрать, кому присудить ответ. Я решил пойти с MDMA, так как это был первый ответ и один из самых ясных. Основываясь на предоставленных ответах, вот мое резюме:

Возможные результаты:

  • A: Родитель без детей
  • B: родители с детьми
  • | -> B1: родители с детьми, у которых ни один ребенок не соответствует фильтр
  • \ -> B2: родители с детьми, у которых 1 или более соответствуют фильтру

Результаты запроса:

  • Запрос 1 возвращает (A, B2)
  • Запрос 2 возвращает (A, B1, B2)

Запрос 2 всегда возвращает родителя из-за левого соединения. В запросе 1 предложение WHERE выполняется после левого соединения, поэтому исключаются родители с детьми, у которых ни один из детей не соответствует фильтру (случай B1).

Примечание: в случае B1 возвращается только родительская информация, а в случае B2 возвращается только информация о родителе / ​​потомке, соответствующая фильтру.

HLGEM предоставил хорошую ссылку:

http://wiki.lessthandot.com/index.php/WHERE_conditions_on_a_LEFT_JOIN

Ответы [ 7 ]

10 голосов
/ 21 мая 2010

Да, есть огромная разница. Когда вы помещаете фильтры в предложение ON в LEFT JOIN, фильтр применяется до , результаты объединяются во внешнюю таблицу. Когда вы применяете фильтр в предложении WHERE, это происходит после применения LEFT JOIN.

Короче говоря, первый запрос исключит строки, в которых есть дочерние строки, но дочернее описание не равно условию фильтра, тогда как второй запрос всегда будет возвращать строку для родителя.

9 голосов
/ 21 мая 2010

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

Второй запрос вернет строку для всех родителей. Если в условии фильтра нет совпадения, для всех столбцов c будет возвращено значение NULL. Вот почему вы получаете больше строк в запросе 2 - родители с детьми, которые не соответствуют условию фильтра, выводятся с пустыми дочерними значениями, где в первом запросе они отфильтровываются.

3 голосов
/ 21 мая 2010

Помещение условия в предложение where преобразует его во внутреннее объединение (если только вы не используете что-то, где id равно нулю, что дает вам записи, не входящие в таблицу). См. Это для более полного объяснения:

http://wiki.lessthandot.com/index.php/WHERE_conditions_on_a_LEFT_JOIN

2 голосов
/ 21 мая 2010

Для этого набора записей:

parent

id
1

child

id    parent filter
1     1      OtherCondition
2     1      OtherCondition

, первый запрос будет возвращать 0 записей, в то время как второй будет возвращать 1 запись:

WITH    parent (id) AS
        (
        SELECT  1
        ),
        child (id, parent, condition) AS
        (
        SELECT  1, 1, 'OtherCondition'
        UNION ALL
        SELECT  2, 1, 'OtherCondition'
        )
SELECT  *
FROM    parent
LEFT JOIN
        child
ON      child.parent = parent.id   

/* The children are found, so no fake NULL records returned */

1   1   1   OtherCondition
1   2   1   OtherCondition

Теперь добавляем WHERE предложение:

WITH    parent (id) AS
        (
        SELECT  1
        ),
        child (id, parent, condition) AS
        (
        SELECT  1, 1, 'OtherCondition'
        UNION ALL
        SELECT  2, 1, 'OtherCondition'
        )
SELECT  *
FROM    parent
LEFT JOIN
        child
ON      child.parent = parent.id       
WHERE   child.id IS NULL OR child.condition = 'FilterCondition'

WHERE предложение фильтрует записи, возвращенные на предыдущем шаге, и ни одна запись не соответствует условию.

Пока этот:

WITH    parent (id) AS
        (
        SELECT  1
        ),
        child (id, parent, condition) AS
        (
        SELECT  1, 1, 'OtherCondition'
        UNION ALL
        SELECT  2, 1, 'OtherCondition'
        )
SELECT  *
FROM    parent
LEFT JOIN
        child
ON      child.parent = parent.id       
        AND child.condition = 'FilterCondition'

1   NULL    NULL    NULL

возвращает одну поддельную запись.

1 голос
/ 21 мая 2010

Первый запрос возвращает меньше строк, потому что он возвращает только те строки, которые либо не имеют дочерних элементов, либо имеют дочерние элементы, соответствующие условию фильтра.

Предложение WHERE исключает остальные (те, которые имеют детей, но не соответствуют условию фильтра.)

2-й запрос показывает все три условия выше.

1 голос
/ 21 мая 2010

родители, у которых есть только дети с description != 'FilterCondition', не появятся в запросе 1, потому что предложение WHERE оценивается после объединения строк.

0 голосов
/ 21 мая 2010

Я заметил пару различий, которые могут изменить результаты. В первом запросе у вас есть LEFT OUTER JOIN Child c ON (p.ID = c.ParentID) , а затем во втором запросе у вас есть LEFT OUTER JOIN Child c ON (p.ID = c.ParentID AND c.Description = 'FilterCondition'), и во втором запросе возвращаются все родители с детьми, удовлетворяющие вашему условиюгде в качестве первого условия также вернутся родители без детей.Также обратите внимание на приоритет условий соединения и условия.

...