Переключение на Главную Страницу Страницы: 1 ОтправитьПечать
Горячая тема (более 10 ответов) WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL (число прочтений - 5992 )
kos
Full Member
***
Отсутствует


1C++ rocks!

Сообщений: 127
Местоположение: Киев
Зарегистрирован: 03. Марта 2013
WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL
30. Августа 2013 :: 07:43
Печать  
Где-то на форуме был вопрос типа
'как правильно и/или что быстрее:  WHERE, JOIN, и т.д.'
(помнится даже отвечал на него, но теперь оказывается - не совсем правильно)

Недавно передо мной возник этот же вопрос......

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

Критика принимается  Смех

ЗАДАЧА:

     хотим получить результат из табл.А, наложив доп.условие на реквизит А,
     который 'живет' в табл.Б
     и при этом на таблицу Б накладываем еще одно некоторое доп.условие ч/з WHERE

Код
Выбрать все
		SELECT А.ID
		FROM A
		{ INNER | LEFT,RIGHT OUTER | FULL } JOIN А.РевизитСсылка = Б.ID
		WHERE (.... дополн.фильтр по Б)
 


ВАРИАНТЫ ЗАПРОСОВ (как результат курения планов + чтения статей из интернет):

                       http://stackoverflow.com/questions/2389204/left-inner-join-vs-left-outer-join-wh...
                       http://www.skillz.ru/dev/php/article-Obyasnenie_SQL_obedinenii_JOIN_INNER_OUTER....
                       http://bytes.com/topic/sql-server/answers/82859-sql-syntax-inner-outer-join-vs-w...
                       http://ru.wikipedia.org/wiki/Join_(SQL)

     получил такое:

     INNER:            
           1. сперва сканируется Б (по его фильтру из WHERE) и формируется <Список Б.ID>
           2. из А выбираются только те записи, для которых {А.РевизитСсылка = УжеОтобранныеЗаписиБ.ID}
           т.е. во втором запросе (при сканировании А)
           собственно JOIN-условие <А.РеквА = Б.РеквБ>
           как бы превращается в WHERE-условие <А.РеквА IN (уже отобранные Б.ID)>

     LEFT OUTER:      
           (запрос INNER) в полном объеме, см.выше
           (запрос OUTER А) сканируется А и выбираются те записи, для которых {А.РевизитСсылка = NULL}
           (UNION) и его результат присоединяется к первому сканированию

     RIGHT OUTER:      
           (запрос INNER) в полном объеме, см.выше
           (запрос OUTER Б) сканируется Б и выбираются те записи, что не имеют отношения к А (связь = NULL)
           (UNION) результаты 2х сканирований объединяются (UNION)

     FULL:      
           (запрос INNER) в полном объеме, см.выше
           (запрос OUTER А) сканируется А и выбираются те записи, что не имеют отношения к Б (связь = NULL)
           (запрос OUTER Б) сканируется Б и выбираются те записи, что не имеют отношения к А (связь = NULL)
           (UNION) все три результата объединяются через UNION

РЕЗУЛЬТАТ:

     СКОРОСТЬ (при равных условиях WHERE и условиях связи 'А.РевизитСсылка = Б.ID')

     INNER      - самый быстрый (2 простых последовательных запроса ч/з WHERE: фильтр-2 накладываем на результат фильтра-1)
     OUTER      - 2е место (все запросы INNER + 1 запрос на NULL + всё объединяем ч/з UNION)
     FULL      - 3е место (все запросы INNER + 2 запроса на NULL + всё объединяем ч/з UNION)

     Причем чем меньше величина отношения количества записей

           У = (кол-во отобранных ч/з INNER) / (Всего записей А + Всего записей Б)

     тем существенней разница между скоростью INNER (быстрее) vs OUTER|FULL (медленней)

ВЫВОДЫ:
     
1) INNER - самый быстрый из всех JOIN-ов, без надобности OUTER|FULL - не использовать !
2) естественно WHERE быстрее JOIN, т.к. WHERE это запрос по 1й таблице, а JOIN по 2вум (и более)

P.S. иногда внутреннее соединение пишут так
        (и надо понимать что это запрос к 2м таблицам, а не простой WHERE)

Код
Выбрать все
		SELECT А.ID
		FROM A
		WHERE (А.РевизитСсылка = Б.ID)
		AND (.... дополн.фильтр по Б)

	вместо (правильно, хотя первый вариант - не запрещается 8=))

		SELECT А.ID
		FROM A
		[INNER] JOIN А.РевизитСсылка = Б.ID
		WHERE (.... дополн.фильтр по Б)
 

  
Наверх
 
IP записан
 
Eprst
God Member
*****
Отсутствует



Сообщений: 3397
Зарегистрирован: 08. Октября 2007
Re: WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL
Ответ #1 - 30. Августа 2013 :: 09:28
Печать  
Как бэ..цитата Ицика Бен-Гана:
Основные типы соединений - перекрестное (cross), внутреннее (inner) и внешнее (left/right).
Все три соединения отличаются стадиями логической обработки запроса, у каждого типа соединений свой набор стадий.
Перекрестное соединение включает только одну стадию логической  обработки -  декартово произведение.
Внутреннее, две стадии - декартово произведение и фильтрацию.
Внешнее, три стадии - декартово произведение, фильтрацию и добавление внешних строк.

Отсюда вывод , всех быстрее - кросс, далее иннер и далее лефт/райт.

а так, быстрее юнион
Улыбка

ЗЫ: Хотя, это только "логическая" стадия, а там, как ужо решит оптимизатор самого скуля.
  
Наверх
 
IP записан
 
kos
Full Member
***
Отсутствует


1C++ rocks!

Сообщений: 127
Местоположение: Киев
Зарегистрирован: 03. Марта 2013
Re: WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL
Ответ #2 - 30. Августа 2013 :: 10:19
Печать  
Eprst писал(а) 30. Августа 2013 :: 09:28:
......
Перекрестное соединение включает только одну стадию логической  обработки -  декартово произведение.
Внутреннее, две стадии - декартово произведение и фильтрацию.
......
ЗЫ: Хотя, это только "логическая" стадия, а там, как ужо решит оптимизатор самого скуля.


Т.е. правильно ли я понял, что:

INNER - это вот это:

Код
Выбрать все
// выбрать все товары, если 'ОсновнойСклад' товра обслуживает менеджер 'ВыбМенеджер'

SELECT А.ID [Товар $Справочник.Товары]
, Б.ID [Склад $Справочник.Клиенты]
FROM  $Справочник.Товары AS А
INNER JOIN $Справочник.Клиенты AS Б ON Б.ID = А.ОсновнойСклад
WHERE ($Б.ОсновнойМенеджер = :ВыбМенеджер)

 


CROSS - тот же INNER, но без WHERE (теоретический термин, без конструкции в T-SQL):
ДЛЯ МЕНЯ ОТКРЫТИЕ  Ужас ЧТО CROSS - это из T-SQL .....
(здесь сравнение CROSS & INNER : http://msdn.microsoft.com/en-us/library/ms190690(SQL.100).aspx)

Код
Выбрать все
// выбрать все товары, у которых 'ОсновнойСклад' назначен (не пустой)

SELECT А.ID [Товар $Справочник.Товары]
, Б.ID [Склад $Справочник.Клиенты]
FROM  $Справочник.Товары AS А
CROSS JOIN $Справочник.Клиенты AS Б ON Б.ID = А.ОсновнойСклад
// хотя результат с INNER будет тот же,
// но оптимизатор наверное отработает качественней 8))...
 



Если так, то предположу что стадии оптимизатора такие:

CROSS - только декартово произведение (А*Б)
INNER - две стадии: тот же CROSS + фильтрация WHERE,
       причем последовательность оптимизатора (наверное) такая:

      1) если кол-во записей А больше чем Б то
         сперва WHERE по Б (по меньше таблице)
         а потом CROSS: произведение <результат_Б * А>

      2) если кол-во записей А меньше чем Б
         сначала CROSS: произведение <Б * А> + дополнительное поле для Б
         а потом WHERE <.... дополн.фильтр из РезультатКросс.ПолеДляWhere>      

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

UNION:

Eprst писал(а) 30. Августа 2013 :: 09:28:
а так, быстрее юнион
Улыбка


Не совсем понял...

Можно пример - как заменить CROSS|INNER на UNION.
Собственно - не понял вот что:
как заменить "произведение" на "сложение"

А скорость UNION vs X мы померяем, но  - потом.

« Последняя редакция: 30. Августа 2013 :: 16:05 - kos »  
Наверх
 
IP записан
 
Eprst
God Member
*****
Отсутствует



Сообщений: 3397
Зарегистрирован: 08. Октября 2007
Re: WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL
Ответ #3 - 30. Августа 2013 :: 10:34
Печать  
Цитата:
на работу оптимизатора мы повлиять не можем (текстом запроса).

почему ?
можем немного, писать лефт джоин и превращать его в иннер через where
  
Наверх
 
IP записан
 
kos
Full Member
***
Отсутствует


1C++ rocks!

Сообщений: 127
Местоположение: Киев
Зарегистрирован: 03. Марта 2013
Re: WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL
Ответ #4 - 30. Августа 2013 :: 10:59
Печать  
Eprst писал(а) 30. Августа 2013 :: 10:34:
можем немного, писать лефт джоин и превращать его в иннер через where


т.е. так?

INNER(cross)

Код
Выбрать все
SELECT А.ID [Товар $Справочник.Товары]
, Б.ID [Склад $Справочник.Клиенты]
FROM  $Справочник.Товары AS А
INNER JOIN $Справочник.Клиенты AS Б ON Б.ID = А.ОсновнойСклад
 


что то же самое что и
Код
Выбрать все
SELECT А.ID [Товар $Справочник.Товары]
, Б.ID [Склад $Справочник.Клиенты]
FROM  $Справочник.Товары AS А,$Справочник.Клиенты AS Б
WHERE Б.ID = А.ОсновнойСклад
 



LEFT + WHERE

Код
Выбрать все
SELECT А.ID [Товар $Справочник.Товары]
, Б.ID [Склад $Справочник.Клиенты]
FROM  $Справочник.Товары AS А
LEFT JOIN $Справочник.Клиенты AS Б ON Б.ID = А.ОсновнойСклад
WHERE (А.ОсновнойСклад <> NULL)
// или $ПустойИД9 ?  



Похоже что скорость выполнения запроса измениться,
но (думаю) в случае LEFT+WHERE: в худшую сторону (исходя из логики стадий, что постами выше)......
Если товаров больше чем клиентов.....
  
Наверх
 
IP записан
 
Eprst
God Member
*****
Отсутствует



Сообщений: 3397
Зарегистрирован: 08. Октября 2007
Re: WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL
Ответ #5 - 30. Августа 2013 :: 11:20
Печать  
последний запрос не верный, условие надо на табличку Б накладывать, а не на А.
  
Наверх
 
IP записан
 
kos
Full Member
***
Отсутствует


1C++ rocks!

Сообщений: 127
Местоположение: Киев
Зарегистрирован: 03. Марта 2013
Re: WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL
Ответ #6 - 30. Августа 2013 :: 11:36
Печать  
Eprst писал(а) 30. Августа 2013 :: 11:20:
последний запрос не верный, условие надо на табличку Б накладывать, а не на А.


А, ну да. Запросы дадут одинаковый результат:

Код
Выбрать все
SELECT А.ID [Товар $Справочник.Товары]
, Б.ID [Склад $Справочник.Клиенты]
FROM  $Справочник.Товары AS А
LEFT JOIN $Справочник.Клиенты AS Б ON Б.ID = А.ОсновнойСклад
WHERE (А.ОсновнойСклад <> NULL)
// или $ПустойИД9 ?
 


Код
Выбрать все
SELECT А.ID [Товар $Справочник.Товары]
, Б.ID [Склад $Справочник.Клиенты]
FROM  $Справочник.Клиенты AS Б
LEFT JOIN $Справочник.Товары AS А ON Б.ID = А.ОсновнойСклад
WHERE (Б.ID <> NULL)
// или $ПустойИД9 ? 



Тогда нужно (еще запрос?) сперва посчитать кого больше: Клиентов или Товаров
и выполнить
Запрос WHERE (Б.ID <> NULL) - если товаров больше (=== таблица Б меньше)
Запрос WHERE (А.ОсновнойСклад <> NULL) - если клиентов больше (=== таблица А меньше)

НО: ту же самую работу (подсчет "кого больше")
выполняет оптимизатор на сервере - а это быстрее чем
1) клиент-сервер: запрос "кого больше"
2) клиент-сервер: запрос "выборка результатов"

Поэтому: INNER|CROSS - будет быстрее чем LEFT+WHERE

Хотя не замерял, но логически, думаю - я прав.
« Последняя редакция: 30. Августа 2013 :: 12:59 - kos »  
Наверх
 
IP записан
 
kos
Full Member
***
Отсутствует


1C++ rocks!

Сообщений: 127
Местоположение: Киев
Зарегистрирован: 03. Марта 2013
Re: WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL
Ответ #7 - 30. Августа 2013 :: 11:37
Печать  
И все таки - как правильно:

Код
Выбрать все
WHERE (Б.ID <> NULL) 



Код
Выбрать все
WHERE (Б.ID <> $ПустойИД9) 



?

Ой. Ну да, отвечу сам:
в контексте LEFT+WHERE
правильно NULL
  
Наверх
 
IP записан
 
kos
Full Member
***
Отсутствует


1C++ rocks!

Сообщений: 127
Местоположение: Киев
Зарегистрирован: 03. Марта 2013
Re: WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL
Ответ #8 - 30. Августа 2013 :: 12:01
Печать  
kos писал(а) 30. Августа 2013 :: 10:19:
UNION:

Eprst писал(а) 30. Августа 2013 :: 09:28:
а так, быстрее юнион
Улыбка


Не совсем понял...

Можно пример - как заменить CROSS|INNER на UNION.
Собственно - не понял вот что:
как заменить "произведение" на "сложение"

А скорость UNION vs X мы померяем, но  - потом.



Сорри, туплю: здесь тоже понятно, ведь арифметика простая:

8 = {2} * 4 = {2} + {2} + {2} + {2} Смех

т.е.

Код
Выбрать все
SELECT A.ID
FROM A
JOIN B ON B.ID = A.Рекв1
JOIN C ON C.ID = A.Рекв2
JOIN D ON D.ID = A.Рекв3
WHERE (B.Рекв6 = ВыбРекв6)
AND (C.Рекв7 = ВыбРекв7)
AND (D.Рекв8 = ВыбРекв8)
 



это все равно что НЕВЕРНО! см.следующий пост

Код
Выбрать все
SELECT A.ID
FROM A
WHERE A.Рекв1 IN (SELECT B.ID FROM B WHERE (B.Рекв6 = ВыбРекв6))

UNION

SELECT A.ID
FROM A
WHERE A.Рекв2 IN (SELECT C.ID FROM C WHERE (C.Рекв7 = ВыбРекв7))

UNION

SELECT A.ID
FROM A
WHERE A.Рекв3 IN (SELECT D.ID FROM D WHERE (D.Рекв8 = ВыбРекв8))
 



Можем замерять скорость. Кто хочет?  Круглые глаза
НЕВЕРНО! см.следующий пост



  
Наверх
 
IP записан
 
kos
Full Member
***
Отсутствует


1C++ rocks!

Сообщений: 127
Местоположение: Киев
Зарегистрирован: 03. Марта 2013
Re: WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL
Ответ #9 - 30. Августа 2013 :: 12:16
Печать  
В предыдущем посте запрос с UNION не верный.

Такой UNION выдаст не пересечение (И,И,И....)
а сумму (ИЛИ) отдельных последовательных результатов

    {A1} ИЛИ(union) {A2} ИЛИ(union) {A3}

и возможен случай когда в выборку попадет элемент (например для 1й части запроса с UNION):
A.Рекв1 = ВыбРекв6
A.Рекв2<>ВыбРекв7
A.Рекв3<>ВыбРекв8


Правильно "развернуть" вот так:
Код
Выбрать все
SELECT A.ID
FROM A
WHERE (A.Рекв1 IN (SELECT B.ID FROM B WHERE (B.Рекв6 = ВыбРекв6)))
AND (A.Рекв2 IN (SELECT C.ID FROM C WHERE (C.Рекв7 = ВыбРекв7)))
AND (A.Рекв3 IN (SELECT D.ID FROM D WHERE (D.Рекв8 = ВыбРекв8))) 



или вот так
Код
Выбрать все
SELECT AAA.ID
FROM (
	SELECT AA.ID,AA.Рекв3
	FROM (
			SELECT A.ID,A.Рекв2,A.Рекв3
			FROM A
			WHERE (A.Рекв1 IN (SELECT B.ID FROM B WHERE (B.Рекв6 = ВыбРекв6))
	) AS AA
	WHERE (AA.Рекв2 IN (SELECT C.ID FROM C WHERE (C.Рекв7 = ВыбРекв7))
) AS AAA
WHERE (AAA.Рекв3 IN (SELECT D.ID FROM D WHERE (D.Рекв8 = ВыбРекв8)) 



Это чтобы "управлять" последовательностью оптимизатора

Но это без UNION.

Eprst писал(а) 30. Августа 2013 :: 09:28:
а так, быстрее юнион
Улыбка


Уважаемый Eprst,
может всё-таки пример замены INNER|CROOS на UNION ?

А то арифметика работает (8 = 2*4 = 2+2+2+2)
а с запросом не пойму как это сделать.....
« Последняя редакция: 30. Августа 2013 :: 16:11 - kos »  
Наверх
 
IP записан
 
Satans Claws
God Member
*****
Отсутствует


1C++ rocks!

Сообщений: 721
Зарегистрирован: 29. Ноября 2010
Re: WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL
Ответ #10 - 02. Сентября 2013 :: 04:54
Печать  
Все это, конечно, замечательно, но
а) о каком скуле идет речь?
б) в любом случае, оптимизатор скуля - вещь настолько загадочная, что далеко не всегда можно предугадать, что будет эффективнее.

Например, на 2005 скуле в простых случаях план запроса с явным Иннер-ом и неявным в виде "Лефт-джойн + условия  по левой короче, по присоединяемой таблице" будет просто одинаковый.
А на 2000 скуле,помниццо, в каких-то случаях оптимизатору на Иннер-ах вообще сносило башню, в результате чего у меня появилась привычка практически всюду использовать неявные Иннеры через лефт+условие.
« Последняя редакция: 03. Сентября 2013 :: 03:19 - Satans Claws »  
Наверх
 
IP записан
 
Salimbek
God Member
*****
Отсутствует



Сообщений: 862
Зарегистрирован: 06. Июня 2006
Пол: Мужской
Re: WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL
Ответ #11 - 02. Сентября 2013 :: 05:24
Печать  
А если попробовать:
INNER JOIN $Справочник.Клиенты AS Б ON ((Б.ID = А.ОсновнойСклад) AND (тут доп. условие по таблице Б))
З.Ы. А так - оптимизатор СКЛ он умный, и зачастую сам всякие WHERE преобразует в JOIN-ы
  
Наверх
ICQ  
IP записан
 
Satans Claws
God Member
*****
Отсутствует


1C++ rocks!

Сообщений: 721
Зарегистрирован: 29. Ноября 2010
Re: WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL
Ответ #12 - 03. Сентября 2013 :: 06:16
Печать  
Цитата:
Уважаемый Eprst,
может всё-таки пример замены INNER|CROOS на UNION ?


Я знаю только методу замены фулл-джойна на Юнион для такой стандартной ситуации:

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

Решение на фулл джойне:
Код
Выбрать все
Select
   IsNull(регОст.Материал, рег.Материал) Материал,
   IsNull(регОст.Количество, 0) + IsNull(рег.Количество) Количество
From
   регОст
   Full Join рег on регОст.Материал = рег.Материал 



Решение на Юнионе:
Код
Выбрать все
Select
   регОст.Материал
   регОст.Количество,
   0 Колво
From
   регОст

Union All

Select
   рег.Материал,
   0,
   рег.Количество
From
   рег 


И Групбай сверху
  
Наверх
 
IP записан
 
Satans Claws
God Member
*****
Отсутствует


1C++ rocks!

Сообщений: 721
Зарегистрирован: 29. Ноября 2010
Re: WHERE vs JOIN: INNER vs (L|R)OUTER vs FULL
Ответ #13 - 03. Сентября 2013 :: 06:19
Печать  
В принципе, для Иннера тоже можно извратиться


Код
Выбрать все
Select
...
From
   (
	Select
	   регОст.Материал
	   регОст.Количество,
	   1 КаунтерОст,
	   0 Колво,
	   0 Каунтер
	From
	   регОст

	Union All

	Select
	   рег.Материал,
	   0,
	   0,
	   рег.Количество,
	   1
	From
	   рег
   ) Данные

Group By
   Материал
Having sum(КаунтерОст) > 0 And sum(Каунтер) > 0
  

  
Наверх
 
IP записан
 
Переключение на Главную Страницу Страницы: 1
ОтправитьПечать