KOCHEROV.NET. Ms sql примеры join


MS SQL Server и T-SQL

OUTER JOIN

Последнее обновление: 20.07.2017

В предыдущей теме было рассмотрено внутреннее соединение таблиц. Но MS SQL Server также поддерживает внешнее соединение или outer join. В отличие от inner join внешнее соединение возвращает все строки одной или двух таблиц, которые участвуют в соединении.

Outer Join имеет следующий формальный синтаксис:

SELECT столбцы FROM таблица1 {LEFT|RIGHT|FULL} [OUTER] JOIN таблица2 ON условие1 [{LEFT|RIGHT|FULL} [OUTER] JOIN таблица3 ON условие2]...

Перед оператором JOIN указывается одно из ключевых слов LEFT, RIGHT или FULL, которые определяют тип соединения:

  • LEFT: выборка будет содержать все строки из первой или левой таблицы

  • RIGHT: выборка будет содержать все строки из второй или правой таблицы

  • FULL: выборка будет содержать все строки из обоих таблиц

Также перед оператором JOIN может указываться ключевое слово OUTER, но его применение необязательно. Далее после JOIN указывается присоединяемая таблица, а затем идет условие соединения.

Например, соединим таблицы Orders и Customers:

SELECT FirstName, CreatedAt, ProductCount, Price, ProductId FROM Orders LEFT JOIN Customers ON Orders.CustomerId = Customers.Id

Таблица Orders является первой или левой таблицей, а таблица Customers - правой таблицей. Поэтому, так как здесь используется выборка по левой таблице, то вначале будут выбираться все строки из Orders, а затем к ним по условию Orders.CustomerId = Customers.Id будут добавляться связанные строки из Customers.

По вышеприведенному результату может показаться, что левостороннее соединение аналогично INNER Join, но это не так. Inner Join объединяет строки из дух таблиц при соответствии условию. Если одна из таблиц содержит строки, которые не соответствуют этому условию, то данные строки не включаются в выходную выборку. Left Join выбирает все строки первой таблицы и затем присоединяет к ним строки правой таблицы. К примеру, возьмем таблицу Customers и добавим к покупателям информацию об их заказах:

-- INNER JOIN SELECT FirstName, CreatedAt, ProductCount, Price FROM Customers JOIN Orders ON Orders.CustomerId = Customers.Id --LEFT JOIN SELECT FirstName, CreatedAt, ProductCount, Price FROM Customers LEFT JOIN Orders ON Orders.CustomerId = Customers.Id

Изменим в примере выше тип соединения на правостороннее:

SELECT FirstName, CreatedAt, ProductCount, Price, ProductId FROM Orders RIGHT JOIN Customers ON Orders.CustomerId = Customers.Id

Теперь будут выбираться все строки из Customers, а к ним уже будет присоединяться связанные по условию строки из таблицы Orders:

Поскольку один из покупателей из таблицы Customers не имеет связанных заказов из Orders, то соответствующие столбцы, которые берутся из Orders, будут иметь значение NULL.

Используем левостороннее соединение для добавления к заказам информации о пользователях и товарах:

SELECT Customers.FirstName, Orders.CreatedAt, Products.ProductName, Products.Manufacturer FROM Orders LEFT JOIN Customers ON Orders.CustomerId = Customers.Id LEFT JOIN Products ON Orders.ProductId = Products.Id

И также можно применять более комплексные условия с фильтрацией и сортировкой. Например, выберем все заказы с информацией о клиентах и товарах по тем товарам, у которых цена меньше 45000, и отсортируем по дате заказа:

SELECT Customers.FirstName, Orders.CreatedAt, Products.ProductName, Products.Manufacturer FROM Orders LEFT JOIN Customers ON Orders.CustomerId = Customers.Id LEFT JOIN Products ON Orders.ProductId = Products.Id WHERE Products.Price < 45000 ORDER BY Orders.CreatedAt

Или выберем всех пользователей из Customers, у которых нет заказов в таблице Orders:

SELECT FirstName FROM Customers LEFT JOIN Orders ON Customers.Id = Orders.CustomerId WHERE Orders.CustomerId IS NULL

Также можно комбинировать Inner Join и Outer Join:

SELECT Customers.FirstName, Orders.CreatedAt, Products.ProductName, Products.Manufacturer FROM Orders JOIN Products ON Orders.ProductId = Products.Id AND Products.Price < 45000 LEFT JOIN Customers ON Orders.CustomerId = Customers.Id ORDER BY Orders.CreatedAt

Вначале по условию к таблице Orders через Inner Join присоединяется связанная информация из Products, затем через Outer Join добавляется информация из таблицы Customers.

Cross Join

Cross Join или перекрестное соединение создает набор строк, где каждая строка из одной таблицы соединяется с каждой строкой из второй таблицы. Например, соединим таблицу заказов Orders и таблицу покупателей Customers:

SELECT * FROM Orders CROSS JOIN Customers

Если в таблице Orders 3 строки, а в таблице Customers то же три строки, то в результате перекрестного соединения создается 3 * 3 = 9 строк вне зависимости, связаны ли данные строки или нет.

При неявном перекрестном соединении можно опустить оператор CROSS JOIN и просто перечислить все получаемые таблицы:

SELECT * FROM Orders, Customers

metanit.com

SQL Full Join

FULL JOIN - возвращает строки, когда есть хоть одно совпадение в любой из таблиц.

Синтаксис SQL FULL JOIN

1 2 3 4 SELECT column_name(s) FROM table_name1 FULL JOIN table_name2 ON table_name1.column_name=table_name2.column_name

Пример SQL FULL JOIN

Есть таблица "Persons":

P_Id LastName FirstName Address City
1 Hansen Ola Timoteivn 10 Sandnes
2 Svendson Tove Borgvn 23 Sandnes
3 Pettersen Kari Storgt 20 Stavanger

Есть таблица "Orders":

O_Id OrderNo P_Id
1 77895 3
2 44678 3
3 22456 1
4 24562 1
5 34764 15

Теперь мы хотим получить список всех людей и заказов.

Для этого используем такой запрос:

1 2 3 4 5 SELECT Persons.LastName, Persons.FirstName, Orders.OrderNo FROM Persons FULL JOIN Orders ON Persons.P_Id=Orders.P_Id ORDER BY Persons.LastName

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

LastName FirstName OrderNo
Hansen Ola 22456
Hansen Ola 24562
Pettersen Kari 77895
Pettersen Kari 44678
Svendson Tove
34764

dimonchik.com

SQL Joins

SQL JOIN - используются для запроса данных из двух или нескольких таблиц связанных между собой ключами.

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

Есть таблица "Persons":

P_IdLastNameFirstNameAddressCity
1 Hansen Ola Timoteivn 10 Sandnes
2 Svendson Tove Borgvn 23 Sandnes
3 Pettersen Kari Storgt 20 Stavanger

Заметим, что столбец "P_Id" является первичным ключом в таблицы "Persons". Это означает - что никакие две строки могут иметь одинаковый "P_Id".

Есть таблица  "Orders":

O_IdOrderNoP_Id
1 77895 3
2 44678 3
3 22456 1
4 24562 1
5 34764 15

Заметим, что столбец "O_Id" является первичным ключом в таблицы "Orders" и что колонка "P_Id" относится к колонки  "P_Id" в таблице "Persons".

Отметим, что связь между таблицами обеспечивается с помощью колонки "P_Id".

Различные SQL JOINs

Прежде чем приводить примеры, мы перечислим типы JOIN доступные к использованию и различия между ними.

  • JOIN: Возвращает строки, когда есть хотя бы одно совпадение в обеих таблицах.
  • LEFT JOIN: Возвращает строки из левой таблицы, даже если их нет правой таблице.
  • RIGHT JOIN: Возвращает строки из правой таблицы, даже если их нет левой таблице.
  • FULL JOIN: Возвращает строки, когда есть хоть одно совпадение в любой из таблиц.

dimonchik.com

Объяснение работы SQL JOIN на примере диаграмм Венна - Базы данных, знаний и хранилища данных. Big data, СУБД и SQL и noSQL

Категории: Базы данных (mysql)

Использование синтаксиса SQL JOINS при работе с Базами данных достаточно популярно, без них не обходится любой серьезный SQL запрос. Я думал, что статья про SQL joins Ligaya Turmelle' великолепный примером для программистов-новчиков. Использование диаграмм Венна для объяснения их работы, вполне естественно и наглядно. Однако, комментируя ее статью, я обнаружил, что ее диаграммы Венна не вполне соответствовали синтаксису SQL join.

 

Я решил это разъяснить на примерах ниже. Предположим, что у нас есть две следующие таблицы. СлеваТаблица A, и таблица B справа. Поместим в каждой из них по 4 записи (строки).

 

id name id name -- ---- -- ---- 1 Pirate 1 Rutabaga 2 Monkey 2 Pirate 3 Ninja 3 Darth Vader 4 Spaghetti 4 Ninja

Давайте соединим эти таблицы с помощью SQL join по столбцу "name" несколькими способами и посмотрим как это будет выглядеть на диаграммах Венна.

SELECT * FROM TableA INNER JOIN TableB ON TableA.name = TableB.name id name id name -- ---- -- ---- 1 Pirate 2 Pirate 3 Ninja 4 Ninja

Inner join (внутреннее присоединение) производит выбора только строк, которые есть и в таблице А и в таблице В.

 

SELECT * FROM TableA FULL OUTER JOIN TableB ON TableA.name = TableB.name id name id name -- ---- -- ---- 1 Pirate 2 Pirate 2 Monkey null null 3 Ninja 4 Ninja 4 Spaghetti null null null null 1 Rutabaga null null 3 Darth Vader

Full outer join (полное внешнее соединение - объединение ) производит выбор всех строк из таблица А и В, причем со всеми возможными варинтами. Если с какой-либо стороны не будет записи, то недостающая запись будет содежрать пустую строку (null значения).

 

SELECT * FROM TableA LEFT OUTER JOIN TableB ON TableA.name = TableB.name id name id name -- ---- -- ---- 1 Pirate 2 Pirate 2 Monkey null null 3 Ninja 4 Ninja 4 Spaghetti null null

Left outer join (левое внешнее соединение) производит выбор всех строк таблицы А с доступными строками таблицы В. Если строки таблицы В не найдены, то подставляется пустой результат (null).

 

SELECT * FROM TableA LEFT OUTER JOIN TableB ON TableA.name = TableB.name WHERE TableB.id IS null id name id name -- ---- -- ---- 2 Monkey null null 4 Spaghetti null null

Чтобы произвести выбор строк из Таблицы A, которых нет в Таблице Б, мы выполняем тот же самый LEFT OUTER JOIN, затем исключаем строки, которые заполнены в Таблице Б. То есть выбрать все записи таблицы А, которых нет в Таблице В, мы выполняем тоже jeft outer join, но исключаем пустые записи таблицы В.

SELECT * FROM TableA FULL OUTER JOIN TableB ON TableA.name = TableB.name WHERE TableA.id IS null OR TableB.id IS null id name id name -- ---- -- ---- 2 Monkey null null 4 Spaghetti null null null null 1 Rutabaga null null 3 Darth Vader

Чтобы выбрать уникальные записи таблиц А и В, мы выполняем FULL OUTER JOIN, и затем исключаем записи, которые принадлежат и таблице А, и таблице Б с помощью условия WHERE.

Есть также декартовский соединение или CROSS JOIN, которое, насколько я могу сказать, не может быть выражено в диаграмме Венна:

SELECT * FROM TableA CROSS JOIN TableB

Оно соединяет "все со всеми", в результате мы получаем 4*4 = 16 строк, что намного больше, чем в оригинальных таблицах. Это очень опасно для таблиц, содержащих большое количество данных. То есть получаются ВСЕ возможные комбинации, включая все Null-null строчки.

 

 

 

 

выборка данных из верхней части матрицы (mysql выбрать уникальные пары)

SELECT fg.uid AS u1, fg2.uid as u2FROM data fg, data fg2 wherefg.uid!=fg2.uid and fg.uid <= fg2.uid

 

Категория: / Mine блог / PHP (LAMP)

Разберем пример. Имеем две таблицы: пользователи и отделы.

 U) users               D) departmentsid name       d_id     id  name-- ----       ----     --  ----1  Владимир    1       1  Сейлз2  Антон       2       2  Поддержка3  Александр   6       3  Финансы4  Борис       2       4  Логистика5  Юрий        4 

 SELECT u.id, u.name, d.name AS d_nameFROM users uINNER JOIN departments d ON u.d_id = d.id 

Запрос вернет объединенные данные, которые пересекаются по условию, указанному в INNER JOIN ON <..>. В нашем случае условие <таблица_пользователей>.<идентификатор_отдела> должен совпадать с <таблица_отделов>.<идентификатор>

В результате отсутствуют:

- пользователь Александр (отдел 6 - не существует)- отдел Финансы (нет пользователей) 

 id name       d_name-- --------   ---------1  Владимир    Сейлз2  Антон       Поддержка4  Борис       Поддержка3  Юрий        Логистика 

рис. Inner join

Внутреннее объединение INNER JOIN (синоним JOIN, ключевое слово INNER можно опустить). 

Выбираются только совпадающие данные из объединяемых таблиц. Чтобы получить данные, которые не подходят по условию, необходимо использовать 

внешнее объединение - OUTER JOIN. 

Такое объединение вернет данные из обеих таблиц совпадающими по одному из условий.

рис. Left join

Существует два типа внешнего объединения OUTER JOIN - LEFT OUTER JOIN и RIGHT OUTER JOIN. 

Работают они одинаково, разница заключается в том что LEFT - указывает что "внешней" таблицей будет находящаяся слева (в нашем примере это таблица users). Ключевое слово OUTER можно опустить. Запись LEFT JOIN идентична LEFT OUTER JOIN.

 SELECT u.id, u.name, d.name AS d_nameFROM users uLEFT OUTER JOIN departments d ON u.d_id = d.id 

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

 id      name          d_name--      --------      ---------1       Владимир      Сейлз2       Антон         Поддержка3       Александр     NULL4       Борис         Поддержка5       Юрий          Логистика 

Добавив условие 

WHERE d.id IS NULL

в выборке останется только 3#Александр, так как у него не назначен департамент.

рис. Left outer join с фильтрацией по полю

RIGHT OUTER JOIN вернет полный список департаментов (правая таблица) и сопоставленных пользователей.

 SELECT u.id, u.name, d.name AS d_nameFROM users uRIGHT OUTER JOIN departments d ON u.d_id = d.id  

 id      name        d_name--      --------    ---------1       Владимир    Сейлз2       Антон       Поддержка4       Борис       ПоддержкаNULL    NULL        Финансы5       Юрий        Логистика 

Дополнительно можно отфильтровать данные, проверяя их на NULL.

 SELECT d.id, d.nameFROM users uRIGHT OUTER JOIN departments d ON u.d_id = d.id WHERE u.id IS NULL 

В нашем примере указав WHERE u.id IS null, мы выберем департаменты, в которых не числятся пользователи. (3#Финансы)

Все примеры вы можете протестировать здесь:

SQLFiddle

Self joins

Так называемая выборка с замыканием на себя (и совсем не closure). Нужна нам, если необходимо выбрать более одногозначения из таблицы для нескольких условий.

Имеем: набор фильтров для информации, значения которых сохраняются в табличке filts_data.Необходимо: фильтровать продукты по дате, артикулу и имеющимся фильтрам

 CREATE TABLE filts_data(  id serial NOT NULL,  fid integer NOT NULL,  -- product_item.id   value integer NOT NULL, -- значение фильтра filts_items.id  pid integer NOT NULL -- фильтр filts.id)  

Есть таблица условных товаров product_item

 CREATE TABLE product_item(  id serial NOT NULL,  section_id integer,  date timestamp,  art text,  title text)  

Пример: выбрать записи, добавленные после 17/01/2009 и с установленными фильтрами 3=14 и 4=15 и 6=19.Логика подскажет нам такой запрос (нерабочий):

 SELECT p1.title FROM products_item p1         INNER JOIN filts_data p2 ON p1.id = p2.fid WHERE p1.date > '17.01.2009'         AND (p2.pid = 3 AND p2.value = 14)         AND (p2.pid = 4 AND p2.value = 15)         AND (p2.pid = 6 AND p2.value = 19) 

Этот запрос не найдет элементов в таблице.Перепишем запрос, используя join на себя:

 SELECT p1.* FROM product_item p1         INNER JOIN filts_data p2 ON p1.id = p2.fid          INNER JOIN filts_data p3 ON p1.id = p3.fid          INNER JOIN filts_data p4 ON p1.id = p4.fid WHERE p1.date > '17.01.2009'         AND (p2.pid = 3 AND p2.value = 14)         AND (p3.pid = 4 AND p3.value = 15)         AND (p4.pid = 6 AND p4.value = 19)       

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

подитожим

JOIN: возвращаются строки, когда есть хотя бы одно совпадение в обеих таблицах.LEFT JOIN: возвращаются все строки из левой таблицы, даже если нет совпадений в таблице справаRIGHT JOIN: возвращаются все строки из правой таблицы, даже если нет совпадений в таблице слеваFULL JOIN: возвращаются строки, когда есть совпадение в одной из таблицЕще есть CROSS JOIN - декартово произведение двух таблиц, но он используется крайне редко.

 

intellect.ml

SQL optimization. Join против In и Exists. Что использовать?

«Раньше было проще» — Подумал я, садясь за оптимизацию очередного запроса в SQL management studio. Когда я писал под MySQL, реально все было проще — или работает, или нет. Или тормозит или нет. Explain решал все мои проблемы, больше ничего не требовалось. Сейчас у меня есть мощная среда разработки, отладки и оптимизации запросов и процедур/функций, и все это нагромождение создает по-моему только больше проблем. А все почему? Потому что встроенный оптимизатор запросов — зло. Если в MySQL и PostgreSQL я напишу

select * from a, b, c where a.id = b.id, b.id = c.id

и в каждой из табличек будет хотя бы по 5к строк — все зависнет. И слава богу! Потому что иначе в разработчике, в лучшем случае, вырабатывается ленность писать правильно, а в худшем он вообще не понимает что делает! Ведь этот же запрос в MSSQL пройдет аналогично

select * from a join b on a.id = b.id join c on b.id = c.id

Встроенный оптимизатор причешет быдлозапрос и все будет окей.

Он так же сам решит, что лучше делать — exist или join и еще много чего. И все будет работать максимально оптимально.

Только есть одно НО. В один прекрасный момент оптимизатор споткнется о сложный запрос и спасует, и тогда вы получите большущую проблему. И получите вы ее, возможно, не сразу, а когда вес таблиц достигнет критической массы.

Так вот к сути статьи. exists и in — очень тяжелые операции. Фактически это отдельный подзапрос для каждой строчки результата. А если еще и присутствует вложенность, то это вообще туши свет. Все будет окей, когда возвращается 1, 10, 50 строк. Вы не почувствуете разницы, а возможно join  будет даже медленнее. Но когда вытаскивается 500 — начнутся проблемы. 500 подзапросов в рамках одного запроса — это серьезно.

Пусть с точки зрения человеческого понимания in и exists лучше, но с точки зрения временных затрат для запросов, возвращающих 50+ строк — они не допустимы.

Нужно оговориться, что естественно, если где-то убывает — где-то должно прибывать. Да, join более ресурсоемок по памяти, ведь держать единовременно всю таблицу значений и оперировать ею — накладнее, чем дергать подзапросы для каждой строки, быстро освобождая память. Нужно смотреть конкретно по запросу и замерять — критично ли будет использование лишней памяти в угоду времени или нет.

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

select * from a where a.id in (select id from b) select * from a where exists (select top 1 1 from b where b.id = a.id) select * from a join b on a.id = b.id select * from a where a.id not in (select id from b) select * from a where not exists (select top 1 1 from b where b.id = a.id) select * from a left join b on a.id = b.id where b.id is null

Повторюсь — данные примеры MSSQL оптимизатор оптимизирует под максимальную производительность и на таких простейших запросах тупняков не будет никогда.

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

Нужно вытащить все дубликаты «продуктов» в разных аккаунтах, ориентируясь на параметры продукта, его группы, и группы-родителя, если таковая есть.

select d.PRODUCT_ID from PRODUCT s, PRODUCT_GROUP sg left join M_PG_DEPENDENCY sd on (sg.PRODUCT_GROUP_ID = sd.M_PG_DEPENDENCY_CHILD_ID), PRODUCT d, PRODUCT_GROUP dg left join M_PG_DEPENDENCY dd on (dg.PRODUCT_GROUP_ID = dd.M_PG_DEPENDENCY_CHILD_ID) where s.PRODUCT_GROUP_ID=sg.PRODUCT_GROUP_ID and d.PRODUCT_GROUP_ID=dg.PRODUCT_GROUP_ID and sg.PRODUCT_GROUP_PERSPEC=dg.PRODUCT_GROUP_PERSPEC and sg.PRODUCT_GROUP_NAME=dg.PRODUCT_GROUP_NAME and s.PRODUCT_NAME=d.PRODUCT_NAME and s.PRODUCT_TYPE=d.PRODUCT_TYPE and s.PRODUCT_IS_SECURE=d.PRODUCT_IS_SECURE and s.PRODUCT_MULTISELECT=d.PRODUCT_MULTISELECT and dg.PRODUCT_GROUP_IS_TMPL=0 and ( (    sd.M_PG_DEPENDENCY_CHILD_ID is null    and    dd.M_PG_DEPENDENCY_CHILD_ID is null  )  or exists  ( select 1 from PRODUCT_GROUP sg1, PRODUCT_GROUP dg1 where sd.M_PG_DEPENDENCY_PARENT_ID = sg1.PRODUCT_GROUP_ID and       dd.M_PG_DEPENDENCY_PARENT_ID = dg1.PRODUCT_GROUP_ID and       sg1.PRODUCT_GROUP_PERSPEC=dg1.PRODUCT_GROUP_PERSPEC and       sg1.PRODUCT_GROUP_NAME=dg1.PRODUCT_GROUP_NAME and  ) )

Так вот это тот случай, когда оптимизатор спасовал. И для каждой строчки выполнялся тяжеленный exists, что убивало базу.

select d.PRODUCT_ID from PRODUCT s join PRODUCT d on s.PRODUCT_TYPE=d.PRODUCT_TYPE and s.PRODUCT_NAME=d.PRODUCT_NAME and s.PRODUCT_IS_SECURE=d.PRODUCT_IS_SECURE and s.PRODUCT_MULTISELECT=d.PRODUCT_MULTISELECT join PRODUCT_GROUP sg on s.PRODUCT_GROUP_ID=sg.PRODUCT_GROUP_ID join PRODUCT_GROUP dg on d.PRODUCT_GROUP_ID=dg.PRODUCT_GROUP_ID and sg.PRODUCT_GROUP_NAME=dg.PRODUCT_GROUP_NAME and sg.PRODUCT_GROUP_PERSPEC=dg.PRODUCT_GROUP_PERSPEC left join M_PG_DEPENDENCY sd on sg.PRODUCT_GROUP_ID = sd.M_PG_DEPENDENCY_CHILD_ID left join M_PG_DEPENDENCY dd on dg.PRODUCT_GROUP_ID = dd.M_PG_DEPENDENCY_CHILD_ID left join PRODUCT_GROUP sgp on sgp.PRODUCT_GROUP_ID = sd.M_PG_DEPENDENCY_PARENT_ID left join PRODUCT_GROUP dgp on dgp.PRODUCT_GROUP_ID = dd.M_PG_DEPENDENCY_PARENT_ID and sgp.PRODUCT_GROUP_NAME = dgp.PRODUCT_GROUP_NAME and isnull(sgp.PRODUCT_GROUP_IS_TMPL, 0) = isnull(dgp.PRODUCT_GROUP_IS_TMPL, 0) where ( sd.M_PG_DEPENDENCY_CHILD_ID is null and dd.M_PG_DEPENDENCY_CHILD_ID is null ) or ( sgp.PRODUCT_GROUP_NAME is not null and dgp.PRODUCT_GROUP_NAME is not null ) go

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

Это наглядный пример того, как доверие MSSQL оптимизатору может сыграть злую шутку. Не доверяйте ему, не ленитесь, join’те ручками, каждый раз думайте что лучше в данной ситуации — exists, in или join.

kocherov.net