Первый важный шаг в логической обработке запросов — обработка оператора FROM. Второй важный шаг — обработка необязательного оператора WHERE.

Логическая обработка запросов описывает концептуальную интерпретацию запросов SQL. Перед вами шестая статья серии. В первой части был дан общий обзор концепции и приведена тестовая база данных с именем TSQLV4. Кроме того, были показаны два тестовых запроса, обозначенные как простой тестовый вопрос и сложный тестовый вопрос. В остальных статьях серии были рассмотрены вопросы логической обработки запросов с использованием табличных операторов JOIN, APPLY, PIVOT и UNPIVOT. Все они обрабатываются в рамках первого важнейшего оператора — FROM. В данной статье будут описаны аспекты логической обработки запросов второго важнейшего оператора — WHERE.

Блок-схема логической обработки запросов с оператором WHERE

Напомню, что первый основной шаг логической обработки запросов связан с оператором FROM. Второй основной шаг — обработка необязательного оператора WHERE. На рисунке 1 показано графическое представление первых двух шагов логической обработки запросов.

 

Блок-схема логической обработки запросов — FROM и WHERE
Рисунок 1. Блок-схема логической обработки запросов — FROM и WHERE

В качестве входных данных на втором шаге используется виртуальная таблица, сформированная на первом шаге. К таблице применяется фильтр на основе предиката, указанного в операторе WHERE. Для каждой входной строки результат применения предиката может быть истинным, ложным или неизвестным (в случае с NULL), и второй шаг возвращает только строки, для которых результат вычислений был истинным. Строки с ложными и неизвестными результатами отбрасываются.

В программном коде листинга 1 содержатся элементы FROM и WHERE нашего простого тестового запроса.

Результат первого шага перед применением оператора WHERE — виртуальная таблица с 832 строками. После применения фильтра на основе предиката в операторе WHERE в виртуальной таблице остается 24 строки (см. экран 1).

 

Результат применения фильтра
Экран 1. Результат применения фильтра

В листинге 2 содержатся элементы FROM и WHERE нашего сложного тестового запроса.

В результате первого шага перед применением оператора WHERE мы получим виртуальную таблицу с 701 строкой. После применения предиката фильтрации в операторе WHERE результатом будет виртуальная таблица с 27 строками (см. экран 2).

 

Результат применения предиката фильтрации в WHERE
Экран 2. Результат применения предиката фильтрации в WHERE

WHERE для фильтрации, ON для сопоставления

Типичный источник недоразумений в SQL — вопрос, принадлежит ли предикат, который нужно использовать в запросе, оператору WHERE или оператору ON соединения. Замешательство может возникнуть даже у специалистов с многолетним опытом подготовки запросов SQL. Мы начнем с того обстоятельства, что в случае с внутренним соединением, по крайней мере для стандартного SQL, как ON, так и WHERE служат для одинаковой цели фильтрации, то есть в обоих случаях, если предикат для данной строки дает значение true, строка возвращается, иначе (если значение предиката false или unknown) строка отбрасывается. Рассмотрим запрос из листинга 3.

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

 

Экран 3. Результаты запроса о дате заказа
Экран 3. Результаты запроса о дате заказа

С точки зрения логической обработки запросов оператор WHERE вычисляется после оператора FROM со всеми его табличными операторами, как соединение в нашем примере. Но с точки зрения физической обработки запросов SQL Server может выполнить определенную реорганизацию, сохраняя смысл запроса (окончательный набор результатов). Например, SQL Server часто выполняет так называемую передачу предикатов (predicate pushdown), когда вместо обработки предикатов из оператора WHERE, которые применяются к единственной таблице после соединения, они обрабатываются перед соединением. Если существуют вспомогательные индексы, их можно использовать для работы с фильтрами. Даже если этого не сделать, сокращение входных наборов перед соединением позволяет сократить количество действий, выполняемых соединением. На рисунке 2 показан план выполнения для нашего запроса.

 

Рисунок 2. Передача предиката
Рисунок 2. Передача предиката

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

В отличие от внутренних соединений, роль операторов ON и WHERE во внешних соединениях совершенно иная. Оператор WHERE по-прежнему используется в обычной роли фильтрации строк из виртуальной таблицы, возвращенной оператором FROM. Оператор ON играет более сложную роль сопоставления. Во внешнем соединении таблица отмечается как сохраненная. Например, в Sales.Customers AS C LEFT OUTER JOIN Sales.Orders AS O ON C.custid = O.custid таблица Customers отмечена как сохраненная, а предикат C.custid = O.custid считается предикатом сопоставления. Это означает, что будут сохранены все строки из таблицы Customers, независимо от исхода предиката сопоставления. Оператор ON определяет, какие строки с несохраненной стороны (Orders) сопоставляются со строками на сохраненной стороне (Customers). Клиент (customer) без единого сопоставленного заказа (order) все же возвращается со значениями NULL в качестве местозаполнителей для атрибутов из Orders.

Типичная ошибка, которая происходит из-за путаницы между сопоставлением и фильтрацией, заключается в применении предполагаемого предиката сопоставления в операторе ON как предиката фильтрации в операторе WHERE. Например, предположим, в запросе нужно фильтровать только клиентов из Испании, но сопоставлять заказы клиентам, только если идентификатор клиента на обеих сторонах одинаков, а заказ размещен в 2016 году или позднее. Интуитивно пользователи задают предикаты, которые сравнивают элементы с обеих сторон в операторе ON и предикаты, применяемые к элементам с одной стороны в операторе WHERE. Такой подход в случае с нашим запросом приводит к ошибке, как показано в листинге 4.

Предполагается, что предикат C.country = N’Spain’ является предикатом фильтрации и используется как таковой в нашем запросе. Нужно сохранить только клиентов из Испании и отбросить остальных. Однако предполагается, что предикат O.orderdate >= ‘20160101’ является предикатом сопоставления, так как необходимо сохранить клиентов, которые не размещали заказы в течение этого периода, но сейчас вы применяете его как предикат фильтрации. Поэтому клиенты, которые не делали заказов в течение этого периода, отбрасываются. На самом деле соединение в этом запросе становится внутренним соединением. Любые внешние строки, выданные внешним соединением для клиентов без заказов, имеют значения NULL в столбце orderdate, и фильтр O.orderdate >= ‘20160101’ отбрасывает их. Оптимизатор SQL Server применяет обнаружение несоответствий и преобразует внешнее соединение во внутреннее, предоставляя тот же план, который показан для запроса с внутренним соединением на рисунке 2. Чтобы исправить ошибку, необходимо применить предикат O.orderdate >= ‘20160101’ как предикат сопоставления в операторе ON, например как в листинге 5.

Этот запрос формирует вывод, представленный на экране 4.

 

Экран 4. Результаты правильного запроса с предикатом
Экран 4. Результаты правильного запроса с предикатом

Обратите внимание, что клиент 22 является клиентом из Испании, который не размещал заказов с начала 2016 года. План этого запроса показан на рисунке 3.

 

Рисунок 3. План с внешним соединением
Рисунок 3. План с внешним соединением

На этот раз оптимизатор обрабатывает соединение как внешнее.

Аргументы поиска и равенство против отличия

Хотя главная тема этой статьи — логическая обработка запросов, я хочу уделить внимание описанию некоторых сторон физической обработки запросов и областям, в которых она отличается от логической обработки. Когда речь идет о настройке запросов, важно усвоить одну из базовых концепций — аргумента поиска (или, сокращенно, SARG). SARG — предикат фильтрации, позволяющий использовать индекс способом, основанным на его упорядочении, например при поиске. Типовая форма SARG выглядит следующим образом:

WHERE <имя_столбца> <оператор>
   <выражение>

Этот оператор должен представлять последовательный диапазон выбранных строк в индексе по фильтрованному столбцу. Он может быть =, >, >=, <, <=, BETWEEN, > AND <=, >= AND < и т. д. Но он не может быть <>, например. Фильтрованный столбец не должен затрагиваться при различных манипуляциях. Выражением на другой стороне оперировать можно. Например, ниже приведен SARG:

WHERE col1 > @p

Если имеется индекс для столбца col1, SQL Server может применить поиск в индексе для работы с фильтром. Показанное ниже не SARG, так как вы выполняете действия по отношению к фильтрованному столбцу:

WHERE col1 + 1 > @p

Поэтому SQL Server придется сканировать данные вместо использования поиска в индексе. В этом случае вы можете легко преобразовать фильтр в SARG, вычитая 1 из @p вместо добавления к col1, например, это SARG:

WHERE col1 > @p — 1

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

Другой пример: следующий запрос (назовем его Query 1) представляет собой запрос с аргументом поиска, поскольку предикат фильтрации не выполняет манипуляций с фильтрованным столбцом:

SELECT custid, country, region
FROM Sales.Customers
WHERE region = N’WA’;

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

CREATE INDEX idx_rgn_i_cid_ctry
   ON Sales.Customers (region)
INCLUDE (custid, country);

План выполнения для запроса 1 показан на рисунке 4.

 

Рисунок 4. План для запроса Query 1
Рисунок 4. План для запроса Query 1

Обратите внимание, что фильтр предиката применяется как предикат поиска, поскольку рассматривается как SARG.

Следующий запрос (именуется Query 2) фильтрует только клиентов из регионов, начинающихся с буквы W:

SELECT custid, country, region
FROM Sales.Customers
WHERE LEFT (region, 1) = N’W’;

Это запрос без аргумента поиска, так как он манипулирует фильтрованным столбцом. На рисунке 5 показан план этого запроса.

 

План для запроса Query 2
Рисунок 5. План для запроса Query 2

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

SELECT custid, country, region
FROM Sales.Customers
WHERE region LIKE N'W%';

На этот раз мы имеем запрос с аргументом поиска, как можно видеть в плане запроса, показанном на рисунке 6.

 

План для запроса Query 3
Рисунок 6. План для запроса Query 3

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

Это запрос с аргументом поиска, он возвращает корректные результаты, пока входные данные отличаются от NULL, как N’WA’ в данном примере. Для некоторых клиентов регион неприменим, и потому имеет значение NULL, которое представляет собой маркер SQL для отсутствующего значения, будь оно применимым или неприменимым (как в нашем случае). SQL использует логику предиката с тремя значениями. Это означает, что при любом сравнении, в котором задействовано значение NULL, в одном или обоих операндах, результатом будет не логическое значение true или false, а логическое значение unknown. Это происходит в сравнениях на основе как равенства, так и неравенства. Посмотрите, каким будет результат, если от приведенного выше запроса требуется принимать входные данные со значением NULL, чтобы возвратить всех клиентов с регионом NULL. Вы получите пустой набор, так как сравнение двух значений NULL с использованием оператора равенства приносит неизвестное значение, а фильтр запроса возвращает только строки, для которых предикат фильтра приносит значение true. Строки, для которых предикат дает значение false или unknown, отбрасываются. В том виде, в котором он существует, запрос содержит ошибку. Обычный способ устранения такой ошибки — использовать функцию ISNULL или COALESCE в обоих операндах сравнения, чтобы заменить NULL значением, обычно не встречающимся в данных, например, как в листинге 7 (назовем этот запрос Query 4).

К сожалению, поскольку мы применяем манипуляции к фильтрованному столбцу, запрос не имеет аргумента поиска, как видно в плане на рисунке 7.

 

План для запроса Query 4
Рисунок 7. План для запроса Query 4

Один из способов наделить запрос аргументом поиска — проверить особый случай, когда оба операнда сравнения имеют значение NULL, с использованием оператора IS NULL вместо оператора равенства, например, как в листинге 8 (назовем этот запрос Query 5).

SQL Server обрабатывает эту форму как SARG, что видно на плане для данного запроса (см. рисунок 8).

 

План для запроса Query 5
Рисунок 8. План для запроса Query 5

Любопытно, что стандартный SQL поддерживает предикат DISTINCT (https://connect.microsoft.com/SQLServer/feedback/details/286422/add-language-and-optimizer-support-for-iso-distinct-predicate) в качестве альтернативы для сравнения на равенство и неравенство. Форма предиката — <операнд1> IS [NOT] DISTINCT FROM <операнд2>. Концепция различимости отличается от равенства при обработке значений NULL. Предикат IS NOT DISTINCT FROM выдает значение true, когда обе стороны представляют собой NULL (или значение, отличное от NULL и такое же) или false в противном случае. Предикат IS DISTINCT FROM приносит значение false, когда обе стороны представляют собой NULL (или значение, отличное от NULL и такое же) или true в противном случае. Если бы этот предикат поддерживался в T-SQL, можно было бы использовать следующий предикат фильтрации в нашем запросе:

WHERE region IS NOT DISTINCT FROM
   @region

К сожалению, T-SQL не поддерживает предикат DISTINCT, но, как показано в статье Пола Уайта «Undocumented Query Plans: Equality Comparisons» (http://sqlblog.com/blogs/paul_white/archive/2011/06/22/undocumented-query-plans-equality-comparisons.aspx), выход из ситуации есть. В листинге 9 приводится элегантная альтернатива, поддерживаемая в T-SQL (назовем этот запрос Query 6).

В основу данного метода положено то обстоятельство, что в операторах UNION, EXCPET и INTERSECT для сравнения строк используется отличие, а не равенство. Если регион не отличается от @region, оператор INTERSECT возвращает одну строку, EXISTS возвращает значение true и возвращается строка во внешнем запросе. Если регион отличается от @region, оператор INTERSECT возвращает пустой набор, EXISTS возвращает значение false и возвращается строка во внешнем запросе. Именно это нам и нужно. Следует отметить, что оптимизатор рассматривает данную форму как SARG и план для этого запроса (Query 6) — точно такой же, как показанный для Query 5 на рисунке 8. Поскольку предикат — SARG, включается поиск в индексе.

Аналогичная конструкция может быть использована в соединении, когда требуется сравнение на основе отличий, например, как в листинге 10.

Как уже отмечалось, типовая альтернатива для «не равно», в случае когда вы получаете значение true при одном операнде NULL и другом, отличном от NULL, — IS DISTINCT FROM. Для наших столбца region и параметра @region используется следующий предикат фильтрации:

WHERE region IS DISTINCT FROM @region

Эта форма также не поддерживается в T-SQL, но существует другая изящная форма:

WHERE NOT EXISTS (SELECT region
   INTERSECT SELECT @region)

Иначе вместо проверки пересечения пустого набора можно вычислить разность непустого набора, например:

WHERE EXISTS (SELECT region
   EXCEPT SELECT @region)

Укороченная операция

Один из сложных для понимания аспектов логической обработки запросов связан с тем обстоятельством, что все выражения в одном логическом шаге вычисляются как набор, а поскольку набор неупорядоченный, отсутствует гарантия, что SQL Server обрабатывает выражения в порядке их появления. Рассмотрим это на примере.

С помощью программного кода в листинге 11 создадим и заполним таблицу с именем Properties.

Таблица содержит различные свойства; столбцы предназначены для имени свойства, имени типа данных и значения. Последнее хранится в виде строки символов. Предположим, требуется отфильтровать только целочисленные свойства, превышающие 10. Для этого предпринимается следующая попытка (см. листинг 12).

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

 

Экран 5. Сообщение об ошибке при выполнении листинга 12
Экран 5. Сообщение об ошибке при выполнении листинга 12

Взглянув на свойство Predicate оператора Clustered Index Scan в плане выполнения запроса, можно выяснить, что предикаты фильтрации реорганизованы таким образом, как показано в листинге 13.

Хотя SQL Server поддерживает метод закорачивания, не гарантируется, что ваши предикаты фильтрации будут вычислены в том порядке, в котором они записаны. Это соответствует принципу логической обработки запросов и не должно вызывать удивления. Можно попытаться обойти проблему, инкапсулировав запрос, который фильтрует только целочисленные свойства в табличном выражении (CTE, производная таблица, представление), и применив запрос с преобразованием для табличного выражения, например, как в листинге 14.

С позиций логической обработки запросов такой программный код не должен оказаться неудачным. Но из соображений обеспечения достаточной производительности средство синтаксического анализа SQL Server устраняет вложенность или встраивает код внутреннего запроса во внешний запрос. Это приводит к программному коду, эквивалентному первоначальному запросу без табличного выражения. Следовательно, выполнение кода приводит к той же ошибке.

Одно из решений этой проблемы — использовать функцию TRY_CAST вместо функции CAST для обработки преобразования. Первый пытается выполнить преобразование и в случае успеха возвращает преобразованное значение; но в случае неудачи вместо ошибки возвращается значение NULL. Таким образом, даже если SQL Server оценивает преобразование перед другим предикатом, выполнение кода не завершится ошибкой. Похожая функция TRY_CONVERT дана как альтернатива функции CONVERT. В листинге 15 приводится программный код для этого решения.

На сегодня SQL Server поддерживает функции TRY_% только для целей преобразования. Было бы хорошо иметь похожую функцию, такую как TRY_EXPRESSION (https://connect.microsoft.com/SQLServer/feedback/details/778583), для любого типа скалярных выражений, так как аналогичные проблемы могут возникать вследствие других ошибок (деление на ноль, переполнение, ввод извне домена и т. д.).

Другое решение — использовать выражение CASE с предшествующим преобразованием, только если тип данных целочисленный, как в листинге 16.

В документации по выражению CASE (https://msdn.microsoft.com/en-us/library/ms181765.aspx) для SQL Server говорится: «Вам следует полагаться только на порядок вычислений условий WHEN для скалярных выражений (включая некоррелированные вложенные запросы, которые возвращают скалярные величины), но не для статистических выражений».

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

WHERE и псевдонимы столбцов

Пользователи часто стремятся использовать псевдонимы столбцов, созданных в списке SELECT для столбцов, ставших результатом вычислений в операторе WHERE, как в листинге 17.

Однако помните, что при логической обработке запросов оператор WHERE (шаг 2) вычисляется перед оператором SELECT (шаг 5). Следовательно, псевдонимы, созданные в операторе SELECT невидимы для выражений в операторе WHERE. Этот программный код приводит к ошибкам, как на экране 6.

 

Экран 6. Ошибка при использовании псевдонимов в операторе SELECT
Экран 6. Ошибка при использовании псевдонимов в операторе SELECT

Причина, по которой выдаются три ошибки, состоит в том, что custlocation предиката IN (N’Spain.Madrid’, N’France.Paris’, N’USA.WA. Seattle’) внутренне преобразуется в соединение трех предикатов: custlocation = N’Spain.Madrid’ OR custlocation = N’France.Paris’ OR custlocation = N’USA.WA. Seattle’.

Очевидный обходной прием — использовать табличное выражение, такое как CTE или производная таблица. Вы создаете псевдоним во внутреннем запросе и используете его везде, где нужно, во внешнем запросе. Более изящное решение — объединить оператор APPLY с оператором VALUES (конструктор значений таблицы) и таким образом создать псевдонимы, необходимые на очень ранних этапах логической обработки, как часть обработки оператора FROM. В результате псевдонимы будут доступны операторам, вычисляемым на последующих этапах, таким как оператор WHERE. Программный код для нашего примера выглядит так, как показано в листинге 18.

Дополнительные сведения об этом подходе и другие примеры можно найти в статье «Логическая обработка запросов: предложение FROM и оператор APPLY» (опубликованной в Windows IT Pro/RE № 5 за 2016 год).

Обманчивая простота WHERE

На первый взгляд оператор WHERE — всего лишь простой фильтр, и не может быть предметом пристального внимания. Но, как выясняется, это гораздо более сложная тема. Логическая обработка запросов объясняет, почему нельзя ссылаться на псевдонимы, которые были определены в операторе SELECT, в операторе WHERE и почему не гарантирован порядок вычисления выражений в операторе WHERE. Также необходимо учитывать сложности, связанные с обработкой значений NULL, в частности различия между сравнениями на основе равенства и отличия. Глубокое понимание этой темы поможет составить корректный и надежный программный код. Кроме того, важно понять особенности физической обработки запросов, например какие формы предикатов составляют аргумент поиска, а какие нет, чтобы иметь возможность строить оптимальные запросы.

Листинг 1. Пример простого запроса
SELECT C.custid, O.orderid
FROM Sales.Customers AS C
  LEFT OUTER JOIN Sales.Orders AS O
    ON C.custid = O.custid
WHERE C.country = N'Spain';
Листинг 2. Пример сложного запроса
SELECT C.custid, C. custlocation, O.orderid, O.productid. O.val
FROM Sales.Customers AS C
  LEFT OUTER JOIN Sales.Orders AS O
    ON C.custid = O.custid
WHERE C.custlocation IN (N’Spain.Madrid’, N’France.Paris’, N’USA.WA.Seattle’);
Листинг 3. Запрос о дате заказа
SELECT C.custid, O.orderid, O.orderdate
FROM Sales.Customers AS C
  INNER JOIN Sales.Orders AS O
    ON C.custid = O.custid
WHERE C.country = N'Spain'
  AND O.orderdate >= '20160101';
Листинг 4. Запрос с ошибкой
SELECT C.custid, O.orderid, O.orderdate
FROM Sales.Customers AS C
  LEFT OUTER JOIN Sales.Orders AS O
    ON C.custid = O.custid
WHERE C.country = N'Spain'
  AND O.orderdate >= ‘20160101’;
Листинг 5. Предикат O.orderdate >= ‘20160101’ как предикат сопоставления в операторе ON
SELECT C.custid, O.orderid, O.orderdate
FROM Sales.Customers AS C
  LEFT OUTER JOIN Sales.Orders AS O
    ON C.custid = O.custid
    AND O.orderdate >= '20160101'
WHERE C.country = N'Spain';
Листинг 6. Использование локальной переменной
DECLARE @region AS NVARCHAR(40) =
   N’WA’; -- попробуйте также
   со значением NULL
SELECT custid, country, region
FROM Sales.Customers
WHERE region = @region;
Листинг 7. Использование функции ISNULL
DECLARE @region AS NVARCHAR(40) =
   N’WA’; -- также проверьте
   со значением NULL
SELECT custid, country, region
FROM Sales.Customers
WHERE ISNULL(region, N'') =
   ISNULL(@region, N'');
Листинг 8. Использование оператора IS NULL вместо оператора равенства
DECLARE @region AS NVARCHAR(40) = NULL;
SELECT custid, country, region
FROM Sales.Customers
WHERE region = @region
   OR (region IS NULL AND @region IS NULL);
Листинг 9. Альтернатива предикату DISTINCT
DECLARE @region AS NVARCHAR(40) = NULL;
SELECT custid, country, region
FROM Sales.Customers
WHERE EXISTS (SELECT region INTERSECT
   SELECT @region);
Листинг 10. Сравнение на основе отличия
SELECT ...
FROM dbo.T1
  INNER JOIN dbo.T2
    ON EXISTS (SELECT T1.col1 INTERSECT
    SELECT T2.col1);
Листинг 11. Создание и заполнение таблицы с именем Properties
ET NOCOUNT ON;
USE tempdb;

IF OBJECT_ID(N'dbo.Properties', N'U') IS NOT NULL DROP TABLE dbo.Properties;
GO

CREATE TABLE dbo.Properties
(
  name     VARCHAR(128) NOT NULL
    CONSTRAINT PK_Properties PRIMARY KEY,
  datatype VARCHAR(128) NOT NULL,
  val      VARCHAR(500) NOT NULL
);

INSERT INTO dbo.Properties(name, datatype, val) VALUES
  ('property1', 'SMALLINT', '1759'    ),
  ('property2', 'VARCHAR',  'abc'     ),
  ('property3', 'INT',      '43112609'),
  ('property4', 'DATE',     '20110212');
Листинг 12. Фильтрование по целочисленному свойству
SELECT name, datatype, val
FROM dbo.Properties
WHERE datatype IN ('TINYINT', 'SMALLINT', 'INT', 'BIGINT')
  AND CAST(val AS BIGINT) > 10;
Листинг 13. Реорганизация предикатов фильтрации
    CONVERT(bigint,[tempdb].[dbo].[Properties].[val],0)>(10)
AND (   [tempdb].[dbo].[Properties].[datatype]='BIGINT'
     OR [tempdb].[dbo].[Properties].[datatype]='INT'
     OR [tempdb].[dbo].[Properties].[datatype]='SMALLINT'
     OR [tempdb].[dbo].[Properties].[datatype]='TINYINT')
Листинг 14. Запрос с преобразованием для табличного выражения
WITH C AS
(
  SELECT name, datatype, val
  FROM dbo.Properties
  WHERE datatype IN ('TINYINT', 'SMALLINT', 'INT', 'BIGINT')
)
SELECT *
FROM C
WHERE CAST(val AS BIGINT) > 10;
Листинг 15. Пример использования TRY_CAST
SELECT name, datatype, val
FROM dbo.Properties
WHERE datatype IN ('TINYINT', 'SMALLINT', 'INT', 'BIGINT')
  AND TRY_CAST(val AS BIGINT) > 10;
Листинг 16. Использование выражения CASE с преобразованием
SELECT name, datatype, val
FROM dbo.Properties
WHERE
  CASE
    WHEN datatype IN ('TINYINT', 'SMALLINT', 'INT', 'BIGINT')
      THEN CAST(val AS BIGINT)
  END > 10;
Листинг 17. Использование псевдонимов столбцов
SELECT custid, CONCAT(country, N’.’ + region, N'.' + city) AS custlocation
FROM Sales.Customers
WHERE custlocation IN (N'Spain.Madrid', N'France.Paris', N'USA.WA.Seattle');
Листинг 18. Использование оператора APPLY с оператором VALUES
SELECT C.custid, A.custlocation
FROM Sales.Customers AS C
  CROSS APPLY ( VALUES( CONCAT(C.country, N'.' + C.region, N'.' + C.city) ) )
    AS A(custlocation)
WHERE A.custlocation IN (N'Spain.Madrid', N'France.Paris', N'USA.WA.Seattle');