SQL Server: поиск или сканирование. N верхних позиций для группы. Продолжение
Модель оптимизации использует исходное допущение, известное как включение (containment) и включение (inclusion). Включение (containment) предполагает, что если вы что-то ищете, то искомое существует. В случае эквивалентного соединения (equijoin) исходное положение таково, что различные значения в столбцах соединения, существующие на одной стороне, существуют и на другой. Исходное положение о включении (inclusion) аналогично, но для предиката фильтра равенства с константой, то есть допущение таково, что фильтруемое значение действительно существует. Посмотрим на остаточный предикат оператора Clustered Index Scan: O.empid = E.empid. Учитывая исходные допущения модели, если ID каждого сотрудника компании, являющийся объектом сканирования, действительно существует, то по законам статистики из каждой сотни строк одна строка будет искомым совпадением (плотность Orders.empid — 0.01). Таким образом, опять же по законам статистики, для выхода на искомую строку понадобится чтение всего пары страниц. Если исходные допущения модели корректны, то эта стратегия лучше, чем двойной поиск. Однако в нашем случае исходные допущения модели не выполняются, поскольку у нас в таблице Employees 200 сотрудников компании, из которых 100 не имеют искомых заказов. Поэтому в 100 случаях цикл сканирования будет выполняться от начала до конца. Следовательно, производительность такого решения очень низкая.

N верхних позиций для группы
Исправить ситуацию можно несколькими способами. Если есть возможность расширить индекс включением отсутствующих столбцов (в нашем случае filler), то это лучше всего. В результате получаем план, аналогичный второму варианту на скриншоте выше. Если такой возможности нет, то можно указать подсказку индекса к таблице Orders: WITH (INDEX (idx_eid_oid_i_od_cid)). В результате получаем стратегию двойного поиска. Если вы предпочитаете не пользоваться подсказками, то можно применить метод, который я называю промежуточным CROSS APPLY. Этот метод предполагает использование двух операторов CROSS APPLY. Промежуточный оператор вычисляет соответствующий ID заказа для данного сотрудника компании с помощью запроса ТОР (1), но не возвращает никаких элементов внешнему запросу. Оператор активирует внутренний оператор CROSS APPLY и передает внутреннему запросу соответствующий ID заказа, после чего внутренний запрос возвращает соответствующую строку из таблицы Orders. Промежуточный запрос CROSS APPLY возвращает строку, возвращаемую внутренним запросом CROSS APPLY, внешнему запросу, который, в свою очередь, возвращает искомые элементы заказа. Если смысл не вполне ясен, взгляните на запрос, предлагаемый в качестве решения, и вновь прочтите объяснение (см. код выше). План запроса показан на скриншоте ниже.
Как мы видим, на этот раз план предусматривает применение стратегии двойного поиска вместо сканирования. Запрос задействует всего несколько сотен операций чтения и выполняется за 90 миллисекунд.
В заключение для очистки выполните код, представленный выше.
Поиск или сканирование — вопрос непростой. В этой статье показано, что в некоторых случаях оптимизатор самостоятельно делает выбор, например при использовании предиката EXISTS для обработки полусоединений или антиполусоединений. Однако в некоторых случаях, например если требуется вывести значение min или шах или п верхних позиций для группы, приходится вручную разрабатывать решение, которое дает оптимальный план запроса. В следующей статье на примере случая с возрастающим ключом будет показано, как из-за неточной оценки количества элементов оптимизатор может выбрать неудачную стратегию и что можно в этом случае сделать, чтобы исправить положение.
Давно мечтаете приобрести лицензию SQL Server, но стеснены в материальных средствах? Тогда вам определенно точно следует заглянуть на 1хбет сайт (http://www.1xbet.site/). Это топовый букмекер, который позволит вам сделать целое состояние на простых и выгодных ставках. И благодаря ему вы определенно точно сможете стать счастливым обладателем SQL Server!