Предикаты поиска / Хабр
По материалам статьи Craig Freedman
Перед тем, как SQL Server приступит к поиску по индексу, он должен определить, являются ли ключи индекса подходящими для оценки предиката запроса.
Индексы по одному столбцу
С индексами по одному столбцу всё довольно просто. SQL Server может их использовать для самых простых сравнений, например, равенства и неравенства (больше чем, меньше чем, и т.д.). Более сложные выражения, такие как функции по столбцу и предикаты «LIKE» с символами подстановки, будут в таких случаях создавать трудности для использования поиска по индексу.
Например, предположим, что мы имеем индекс по одному столбцу, созданный по полю «a». Этот индекс может использоваться для поиска при следующих предикатах:
a = 3.14 a > 100 a between 0 and 99 a like 'abc%' a in (2, 3, 5, 7)
Однако, поиск по индексу не будет задействован если использовать вот такие предикаты:
ABS(a) = 1 a + 1 = 9 a like '%abc'
Индексы по нескольким столбцам
С индексами по нескольким столбцам дело обстоит сложнее. Для таких индексов важен порядок ключей. Этим определяется порядок сортировки индекса, и от порядка ключей зависит набор предикатов поиска, которые SQL Server сможет использовать для этого индекса.
Для того, чтобы проще понять важность порядка ключей, представьте себе телефонную книгу. Для телефонной книги походит индекс с ключами: «фамилия» и «имя». Содержание телефонной книги отсортировано по фамилии, что упрощает поиск кого-нибудь, если мы знаем его фамилию. Однако, если мы знаем только имя, очень трудно получить список людей с необходимым нам именем. В таком случае, нам бы лучше подошла другая телефонная книга, в которой абоненты отсортированы по имени.
Точно также обстоит дело, если мы имеем индекс по двум столбцам, т.е. мы сможем использовать индекс только для предиката по второму столбцу, если указан предикат равенства для первого столбца. Даже если мы не сможем использовать индекс для удовлетворения условия предиката второго столбца, мы сможем использовать его для первого столбца. В этом случае, вводится остаточный предикат для предиката второго столбца, который будет тем же самым предикатом, который используется для просмотра.
Например, предположим, что у нас есть индекс по двум столбцам «a» и «b». Мы можем его использовать для поиска по любому из предикатов, которые применимы для индексов по одному столбцу. Кроме того, можно использовать это индекс и для поиска со следующими дополнительными предикатами:
a = 3.14 and b = 'pi' a = 'xyzzy' and b <= 0
Для следующих ниже примеров, мы можем использовать индекс для удовлетворения условий предиката для столбца «a», но не для столбца «b». В этих случаях потребуется остаточный предикат:
a > 100 and b > 100 a like 'abc%' and b = 2
И, наконец, невозможно использовать индекс для поиска со следующим ниже набором предикатов, поскольку поиск невозможен даже по столбцу «a». В таких случаях, оптимизатор вынужден использовать другой индекс (например, такой индекс, у которого столбец «b» указан первым), или он будет использовать просмотр с остаточным предикатом.
b = 0 a + 1 = 9 and b between 1 and 9 a like '%abc' and b in (1, 3, 5)
Добавим в пример немного конкретики.
Рассмотрим следующую схему:
create table person (id int, last_name varchar(30), first_name varchar(30)) create unique clustered index person_id on person (id) create index person_name on person (last_name, first_name)
Ниже представлены три запроса с соответствующими им текстовыми планами исполнения. Первый запрос осуществляет поиск по обоим столбцам индекса person_name. Второй запрос ищет только по первому столбцу и использует остаточный предикат, для оценки first_name. Третий запрос не может использовать поиск и использует просмотр с остаточным предикатом.
select id from person where last_name = 'Doe' and first_name = 'John' |--Index Seek(OBJECT:([person].[person_name]), SEEK:([person].[last_name]='Doe' AND [person].[first_name]='John')) select id from person where last_name > 'Doe' and first_name = 'John' |--Index Seek(OBJECT:([person]. [person_name]), SEEK:([person].[last_name] > 'Doe'), WHERE:([person].[first_name]='John')) select id from person where last_name like '%oe' and first_name = 'John' |--Index Scan(OBJECT:([person].[person_name]), WHERE:([person].[first_name]='John' AND [person].[last_name] like '%oe'))
Внимание: Если Вы пробуете воспроизвести эти планы для этих и некоторых следующих примеров, учтите, что я использовал подсказки индексов (которые не указаны), позволяющие гарантировать получение необходимого плана запроса, поскольку я хотел проиллюстрировать эти примеры без необходимости вставки данных в таблицу.
Дополнение о ключах индекса
Очень часто ключи индекса являются набором столбцов, которые были указаны в инструкции по созданию этого индекса. Однако, когда создается некластеризованный уникальный индекс для таблицы с кластеризованным индексом, в ключ некластеризованного индекса добавляется ключ кластеризованного индекса, если он не является частью ключей некластеризованного индекса. Поиск по этим неявным ключам осуществляется точно так же, как если бы они были определены явно.
Например, рассмотрим такую схему:
create table T_heap (a int, b int, c int, d int, e int, f int) create index T_heap_a on T_heap (a) create index T_heap_bc on T_heap (b, c) create index T_heap_d on T_heap (d) include (e) create unique index T_heap_f on T_heap (f) create table T_clu (a int, b int, c int, d int, e int, f int) create unique clustered index T_clu_a on T_clu (a) create index T_clu_b on T_clu (b) create index T_clu_ac on T_clu (a, c) create index T_clu_d on T_clu (d) include (e) create unique index T_clu_f on T_clu (f)
Столбцы ключей и покрываемые столбцы для каждого из индексов:
Индекс | Столбцы ключа | Покрываемые столбцы |
T_heap_a | a | a |
T_heap_bc | b, c | b, c |
T_heap_d | d | d, e |
T_heap_f | f | f |
T_clu_a | a | a, b, c, d, e |
T_clu_b | b, a | a, b |
T_clu_ac | a, c | a, c |
T_clu_d | d, a | a, d, e |
T_clu_f | f | a, f |
Обратите внимание, что каждый некластеризованный индекс таблицы T_clu включает ключевой столбец кластеризованного индекса, за исключением уникального индекса T_clu_f.
Описанное в этой статье поведение оптимизатора не изменилось и в SQL Server 2005. Уникальные некластеризованные индексы не включают ключи кластеризованного индекса в свой набор ключевых столбцов, но делают их покрываемыми столбцами. То есть Вы не можете искать по кластеризованному ключу, но Вы можете выбирать значения по индексу. Это происходит потому, что ключ кластеризованного индекса хранится только на страницах листового уровня сбалансированного дерева; он не хранится на страницах не листового уровня. Ключ кластеризованного индекса необходим для поиска закладок. Нет нужды искать по ключу кластеризованного индекса, поскольку поиск по ключу уникального индекса обычно возвращает одну строку. В SQL Server 2000, фактически может использоваться поиск по ключу кластеризованного индекса, но это относится к внутренней реализации в виде остаточного предиката, а не в виде поиска.
Postgres Pro Enterprise : Документация: 9.6: 9.2. Функции и операторы сравнения : Компания Postgres Professional
Набор операторов сравнения включает обычные операторы, перечисленные в Таблице 9. 1.
Таблица 9.1. Операторы сравнения
Оператор | Описание |
---|---|
< | меньше |
> | больше |
<= | меньше или равно |
>= | больше или равно |
= | равно |
<> или != | не равно |
Примечание
Оператор !=
преобразуется в <>
на стадии разбора запроса. Как следствие, реализовать операторы !=
и <>
по-разному невозможно.
Операторы сравнения определены для всех типов данных, для которых они имеют смысл. Все операторы сравнения представляют собой бинарные операторы, возвращающие значения типа boolean
; при этом выражения вида 1 < 2 < 3
недопустимы (так как не существует оператора <
, который бы сравнивал булево значение с 3
).
Существует также несколько предикатов сравнения; они приведены в Таблице 9.2. Они работают подобно операторам, но имеют особый синтаксис, установленный стандартом SQL.
Таблица 9.2. Предикаты сравнения
Предикат | Описание |
---|---|
a BETWEEN x AND y | между |
a NOT BETWEEN x AND y | не между |
a BETWEEN SYMMETRIC x AND y | между, после сортировки сравниваемых значений |
a NOT BETWEEN SYMMETRIC x AND y | не между, после сортировки сравниваемых значений |
a IS DISTINCT FROM b | не равно, при этом NULL воспринимается как обычное значение |
a IS NOT DISTINCT FROM b | равно, при этом NULL воспринимается как обычное значение |
выражение IS NULL | эквивалентно NULL |
выражение IS NOT NULL | не эквивалентно NULL |
выражение ISNULL | эквивалентно NULL (нестандартный синтаксис) |
выражение NOTNULL | не эквивалентно NULL (нестандартный синтаксис) |
логическое_выражение IS TRUE | истина |
логическое_выражение IS NOT TRUE | ложь или неопределённость |
логическое_выражение IS FALSE | ложь |
логическое_выражение IS NOT FALSE | истина или неопределённость |
логическое_выражение IS UNKNOWN | неопределённость |
логическое_выражение IS NOT UNKNOWN | истина или ложь |
Предикат BETWEEN
упрощает проверки интервала:
a
BETWEENx
ANDy
равнозначно выражению
a
>=x
ANDa
<=y
Заметьте, что BETWEEN
считает, что границы интервала также включаются в интервал. NOT BETWEEN
выполняет противоположное сравнение:
a
NOT BETWEENx
ANDy
равнозначно выражению
a
<x
ORa
>y
Предикат BETWEEN SYMMETRIC
аналогичен BETWEEN
, за исключением того, что аргумент слева от AND
не обязательно должен быть меньше или равен аргументу справа. Если это не так, аргументы автоматически меняются местами, так что всегда подразумевается непустой интервал.
Обычные операторы сравнения выдают NULL (что означает «неопределённость»), а не true или false, когда любое из сравниваемых значений NULL. Например, 7 = NULL
выдаёт NULL, так же, как и 7 <> NULL
. Когда это поведение нежелательно, можно использовать предикаты IS [ NOT ] DISTINCT FROM
:
a
IS DISTINCT FROMb
a
IS NOT DISTINCT FROMb
Для значений не NULL условие IS DISTINCT FROM
работает так же, как оператор <>
. Однако если оба сравниваемых значения NULL, результат будет false, и только если одно из значений NULL, возвращается true. Аналогично, условие IS NOT DISTINCT FROM
равносильно =
для значений не NULL, но возвращает true, если оба сравниваемых значения NULL, и false в противном случае. Таким образом, эти предикаты по сути работают с NULL, как с обычным значением, а не с «неопределённостью».
Для проверки, содержит ли значение NULL или нет, используются предикаты:
выражение
IS NULLвыражение
IS NOT NULL
или равнозначные (но нестандартные) предикаты:
выражение
ISNULLвыражение
NOTNULL
Заметьте, что проверка
не будет работать, так как выражение
= NULLNULL
считается не «равным» NULL
. (Значение NULL представляет неопределённость, и равны ли две неопределённости, тоже не определено. )
Подсказка
Некоторые приложения могут ожидать, что
вернёт true, если результатом выражение
= NULLвыражения
является NULL. Такие приложения настоятельно рекомендуется исправить и привести в соответствие со стандартом SQL. Однако в случаях, когда это невозможно, это поведение можно изменить с помощью параметра конфигурации transform_null_equals. Когда этот параметр включён, Postgres Pro преобразует условие x = NULL
в x IS NULL
.
Если выражение
возвращает табличную строку, тогда IS NULL
будет истинным, когда само выражение — NULL или все поля строки — NULL, а IS NOT NULL
будет истинным, когда само выражение не NULL, и все поля строки так же не NULL. Вследствие такого определения, IS NULL
и IS NOT NULL
не всегда будут возвращать взаимодополняющие результаты для таких выражений; в частности такие выражения со строками, одни поля которых NULL, а другие не NULL, будут ложными одновременно. В некоторых случаях имеет смысл написать строка
IS DISTINCT FROM NULL
или строка
IS NOT DISTINCT FROM NULL
, чтобы просто проверить, равно ли NULL всё значение строки, без каких-либо дополнительных проверок полей строки.
Логические значения можно также проверить с помощью предикатов
логическое_выражение
IS TRUEлогическое_выражение
IS NOT TRUEлогическое_выражение
IS FALSEлогическое_выражение
IS NOT FALSEлогическое_выражение
IS UNKNOWNлогическое_выражение
IS NOT UNKNOWN
Они всегда возвращают true или false и никогда NULL, даже если какой-любо операнд — NULL. Они интерпретируют значение NULL как «неопределённость». Заметьте, что IS UNKNOWN
и IS NOT UNKNOWN
по сути равнозначны IS NULL
и IS NOT NULL
, соответственно, за исключением того, что выражение может быть только булевого типа.
Также имеется несколько связанных со сравнениями функций; они перечислены в Таблице 9.3.
Таблица 9.3. Функции сравнения
Функция | Описание | Пример | Результат примера |
---|---|---|---|
num_nonnulls(VARIADIC "any") | возвращает число аргументов, отличных от NULL | num_nonnulls(1, NULL, 2) | 2 |
num_nulls(VARIADIC "any") | возвращает число аргументов NULL | num_nulls(1, NULL, 2) | 1 |
mysql — Как проверить, пересекается ли вывод предиката SQL с выводом другого предиката SQL, прежде чем фактически запрашивать данные?
спросил
Изменено 1 год, 9 месяцев назад
Просмотрено 99 раз
Есть ли какой-либо известный алгоритм или библиотека, которые могут помочь определить, содержится ли предикат SQL в другом предикате SQL?
Эти предикаты будут идти после этого оператора
SELECT * FROM table1 WHERE . ..
Например, давайте посмотрим на этот предикат
имя = Смит
Я хочу проверить, содержится ли этот предикат в следующем предикате:
(компания = Walmart ИЛИ хобби = бейсбол) И (НЕ (имя! = Смит))
Я пытаюсь выяснить, может ли вывод второго предиката содержать вывод первого предиката или нет. Эта информация требуется перед запросом фактических данных.
Ограничения:
- Вложенные предикаты не допускаются.
- Разрешены только следующие операторы: И ИЛИ НЕ МЕЖДУ В IS.
- mysql
- sql
- postgresql
- partiql
В этом могут помочь таблицы истинности.
Вы можете выразить каждый термин в логическом выражении как истинный или как ложный. Или, что то же самое, различные возможные входные данные, которые приводят к тому, что каждый термин является истинным или ложным.
Составьте таблицу всех их комбинаций.
компания | хобби | имя | РЕЗУЛЬТАТ |
---|---|---|---|
Уолмарт | бейсбол | Смит | ИСТИНА |
Уолмарт | гольф | Смит | ИСТИНА |
Костко | гольф | Смит | ЛОЖЬ |
Костко | гольф | Джонс | ЛОЖЬ |
Уолмарт | бейсбол | Джонс | ЛОЖЬ |
Есть и другие комбинации, но я их опускаю.
Затем составьте таблицу истинности для предиката, который вы хотите проверить: имя = Смит
компания | хобби | имя | РЕЗУЛЬТАТ |
---|---|---|---|
Уолмарт | бейсбол | Смит | ИСТИНА |
Уолмарт | гольф | Смит | ИСТИНА |
Костко | гольф | Смит | ИСТИНА |
Костко | гольф | Джонс | ЛОЖЬ |
Уолмарт | бейсбол | Джонс | ЛОЖЬ |
Второй предикат не является подмножеством первого, потому что в первом есть по крайней мере один набор входных данных, для которых результат ЛОЖЬ, хотя имя = Смит
верно. Я выделил результат, который отличается от первого предиката.
Чего не хватает в моем ответе, так это метода анализа логического выражения и определения терминов, чтобы вы могли составить таблицу истинности. Это зависит от того, какой язык вы используете, и так далее. Вы можете выполнить поиск прошлых ответов Stack Overflow на похожие вопросы: https://stackoverflow.com/search?q=parse+sql+boolean+expression
Что касается рекомендации библиотеки или программного продукта, который может сделать это для вас, это выходит за рамки объем ответа переполнения стека. В рекомендациях по тематическим вопросам прямо говорится, что этот сайт не предназначен для рекомендаций по программным библиотекам или инструментам.
1Зарегистрируйтесь или войдите в систему
Зарегистрируйтесь с помощью Google Зарегистрироваться через Facebook Зарегистрируйтесь, используя электронную почту и парольОпубликовать как гость
Электронная почтаТребуется, но никогда не отображается
Опубликовать как гость
Электронная почтаТребуется, но не отображается
Нажимая «Опубликовать свой ответ», вы соглашаетесь с нашими условиями обслуживания и подтверждаете, что прочитали и поняли нашу политику конфиденциальности и кодекс поведения.
tsql — Использование предикатов НЕ в SQL
Задавать вопрос
спросил
Изменено 12 лет, 6 месяцев назад
Просмотрено 665 раз
Я создал несколько запросов и не могу понять, почему результаты не такие, как я ожидал.
Я не понимаю, почему запросы II и III не возвращают одинаковые результаты. Я ожидал, что запрос II вернет все строки, не выбранные запросом I.
Я ожидал, что запросы II и III дадут одинаковые результаты. На мой взгляд, результаты III являются правильными.
Я уверен, что что-то упустил, просто не знаю что.
Пример:
Таблица:
CREATE TABLE [dbo]. [TestTable]( [TestTableId] [int] НЕ NULL, [ValueA] [число] NULL, [ValueB] [число] NULL ) НА [ПЕРВИЧНОМ]
Данные:
TestTableId ValueA ValueB 1 10 5 2 20 5 3 10 НОЛЬ 4 20 НОЛЬ 5 НУЛЕВОЕ 10 6 10 10 7 НУЛЬ НУЛЬ
Запросы:
Все записи: выберите * из TestTable
I. Запрос на выбор:
выберите * из TestTable где (ValueA = 10 или ValueA = 20) AND ValueB = 5
Результат:
TestTableId ValueA ValueB 1 10 5 2 20 5
II. Тот же запрос, но как НЕ
выберите * из TestTable где НЕ ((ЗначениеA = 10 или ЗначениеA = 20) И ЗначениеB = 5)
Результат:
TestTableId ValueA ValueB 5 НУЛЕВОЕ 10 6 10 НОЛЬ
III. Тот же запрос, что и второй (думаю)
select * from TestTable где TestTable.TestTableId не в (выберите TestTableId из TestTable где (ValueA = 10 или ValueA = 20) И ValueB = 5)
Результат:
TestTableId ValueA ValueB 3 10 НОЛЬ 4 20 НОЛЬ 5 НУЛЕВОЕ 10 6 10 10 7 НУЛЬ НУЛЬ
- sql
- tsql
NULL — забавные существа. Они ответят «Я не знаю» на оба следующих вопроса:
Вам 5 лет? (... ГДЕ ValueB = 5)
и
Вам не 5? (... ГДЕ НЕ ValueB = 5)
Что приводит к тому, что значения NULL исключаются из обоих запросов , как вы обнаружили.
Вы должны задать вопрос таким образом, чтобы явно учитывались значения NULL:
... ГДЕ (ValueB IS NULL OR NOT ValueB = 5) ...3
При использовании NOT
, NULL
значения представляют собой особую ситуацию.
A NULL
является неизвестным значением . SQL не может сказать, является ли это НЕ
a 12, но он может сказать, является ли a 12.
Хороший пример:
Вы на вечеринке. Вы знаете имена 2 из 12 человек в комнате, обоих зовут Джон. Вы можете сказать мне, кто такие «Джоны». Вы не может сказать мне, кто такой «Не Джек», кроме двух «Джонов». Для SQL эти другие 10 человек в комнате имеют имя NULL
.