Почему мое предложение WHERE повлияло на мое левое соединение? - PullRequest
3 голосов
/ 02 июля 2019

Я писал SQL, который возвращал описание продукта на основе заданного кода. Я подготовил свой запрос с предположением, что коды с разной капитализацией могут сосуществовать. Однако, фильтруя результаты моей основной таблицы, я бы хотел, чтобы мой результат учитывал регистр. То есть поиск некоторого строчного кода вернул бы только строчные коды, а не прописные эквиваленты.

Однако я пришел к выводу, что в зависимости от использования условия предложения WHERE в заглавной букве результат может измениться. Я посмотрел на каждую таблицу, и у каждого есть свое сопоставление. Вместо этого я протестировал RIGHT JOIN, и он правильно соединил таблицы в обоих случаях. Кроме того, никогда не было необходимости проверять различные случаи: все коды должны быть прописными в соответствии со стандартами и валидациями нашей системы. Таким образом, хотя исправить это было так же просто, как убедиться, что мое предложение WHERE было в верхнем регистре, я все еще оставался в удивлении почему запрос дал другие результаты. Меня учили, что во время обработки запроса SQL предложение JOIN будет запускаться до предложения WHERE, гарантируя, что последнее будет просматривать результат присоединенный .

Для воспроизведения этой ошибки, во-первых, я создал базу данных с DEFAULT CHARACTER SET UTF8 COLLATION UNICODE_CI_AI.

Затем я создал каждую таблицу как таковую:

CREATE TABLE MAIN_TABLE (
  val VARCHAR(40) NOT NULL PRIMARY KEY,
  code VARCHAR(40) NOT NULL COLLATE UNICODE_CI
);

CREATE TABLE PRODUCTS  (
  name VARCHAR(40) NOT NULL PRIMARY KEY,
  code VARCHAR(40) NOT NULL COLLATE UNICODE
);

Затем я вставил следующие тестовые записи:

INSERT INTO MAIN_TABLE (val, code) VALUES ('This value is returned', 'ABC');
INSERT INTO PRODUCTS (name, code) VALUES ('My product', 'ABC');

И, наконец, я выполнил следующий запрос:

SELECT * FROM MAIN_TABLE
LEFT JOIN PRODUCTS 
ON MAIN_TABLE.code = PRODUCTS.code
WHERE MAIN_TABLE.code LIKE '%abc%'

Что привело к:

MAIN_TABLE.code | MAIN_TABLE.val         | PRODUCTS.code | PRODUCTS.name
----------------+------------------------+---------------+---------------
 ABC            | This value is returned | null          | null

Обратите внимание, что, хотя мой запрос нашел результат в MAIN_TABLE, результат LEFT JOIN был равен нулю.

Однако точно такой же запрос, измените предложение WHERE, возвращает другой результат. Итак, запрос:

SELECT * FROM MAIN_TABLE
LEFT JOIN PRODUCTS 
ON MAIN_TABLE.code = PRODUCTS.code
WHERE MAIN_TABLE.code LIKE '%ABC%'

Закончилось возвращением:

MAIN_TABLE.code | MAIN_TABLE.val         | PRODUCTS.code | PRODUCTS.name
----------------+------------------------+---------------+---------------
 ABC            | This value is returned | ABC           | My product

Я задавался вопросом - было ли мое понимание порядка операций неправильным? Читает ли сервер базы данных запрос, идентифицирует ли он, что столбец предложения WHERE (MAIN_TABLE.code) такой же, как в JOIN, и , а затем изменяет внутреннюю обработку JOIN (для оптимизации или иным образом)? Или это просто ошибка в том, как Firebird интерпретирует запросы? Я ожидал некоторого странного поведения, учитывая различные параметры сортировки, но я не был уверен, что это какая-то особенность.

Почему мое предложение WHERE повлияло на мое левое соединение?

Я не ищу способов исправить это , поскольку я нашел много - изменение параметров сортировки, прописные буквы моего запроса, предварительная проверка кода и т. Д.

Моя база данных работает на Firebird 3.0. Я проверил опции для отображения всех сообщений, проверил журналы и проверил варианты запросов, которые работали. Я не видел там ничего, что могло бы дать мне представление о том, почему это произошло.

Ответы [ 2 ]

4 голосов
/ 03 июля 2019

Меня учили, что во время обработки запроса SQL предложение JOIN будет выполняться перед предложением WHERE, гарантируя, что последнее будет просматривать объединенный результат.

Это правильное описание семантики SQL , так что то, что вы видите, скорее всего, ошибка.

Фактическая реализация СУБД является более сложной. На высоком уровне SQL-запрос анализируется в логическом плане запроса , который представляет собой дерево, которое близко следует структуре входного SQL. Затем оптимизатор отвечает за преобразование логического плана в фактические шаги (физические операторы), которые будут выполняться для получения результата.

Логический план вашего запроса будет выглядеть примерно так:

read MAIN_TABLE        read PRODUCTS
       \                  /
      join them on MAIN_TABLE.code = PRODUCTS.code
              |
       apply filter MAIN_TABLE.code LIKE '%ABC%'

Работа оптимизатора состоит в том, чтобы найти эффективный способ выполнить это. Он может выполнять преобразования, такие как предикатное нажатие, когда фильтр (MAIN_TABLE.code LIKE '%ABC%') перемещается на стадию «чтения», так что читаются только соответствующие строки. Затем оптимизатор может выбрать физическую операцию, которую он будет использовать для чтения входной таблицы (например, полное сканирование или чтение по индексу).

(Это предположение с моей стороны.) Оптимизатор также может заметить, что, поскольку вы присоединяетесь к code, можно сопоставлять только ПРОДУКТЫ, которые удовлетворяют PRODUCTS.code LIKE '%ABC%', поэтому он может передать предикат ПРОДУКТАМ Оператор сканирования также. В зависимости от параметров сортировки входных таблиц, если оптимизатор не очень осторожен, семантика предиката LIKE '%ABC%' может измениться, что приведет к поведению, которое вы видите.

3 голосов
/ 07 июля 2019

Стандарт SQL говорит, что при сравнении двух выражений символьного типа они должны иметь одинаковое сопоставление. Это потому, что равные два значения имеют смысл только в данном сопоставлении. Ваши звонки на = & LIKE нарушают это.

Не совсем понятно, о чем вы думаете, когда пишете этот код.

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

Из (черновика) стандарта SQL:

Часть 2: Фонд 2011-12-21

9.11 Операции равенства
Синтаксические правила
4) Пусть VS - множество объявленных типов операндов операции равенства. Если VS содержит типы символьных строк, то правила синтаксиса, указанные в подразделе 9.15, «Определение сопоставления», применяются с VS в качестве TYPESET; пусть сопоставление, которое будет использоваться в операции равенства, будет COLL, возвращенным из применения этих правил синтаксиса.

9.15 Определение сопоставления
Синтаксические правила
4) Дело:
e) В противном случае каждый операнд, для которого сопоставление неявно, должно иметь тот же объявленный тип сопоставления IDTC, и для сопоставления используется IDTC.

Вы можете получить сравнение без учета регистра путем соответствующего использования UPPER или COLLATE .

Поиск без учета регистра

Для поиска без учета регистра можно использовать функцию UPPER, чтобы преобразовать как аргумент поиска, так и искомые строки в верхний регистр перед попыткой сопоставления

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

См. Также: СОДЕРЖАНИЕ

Аналогично LIKE документация гласит:

Примечание
Если вам нужно выполнить поиск без учета регистра для чего-то, заключенного в строку (LIKE '% Abc%'), рекомендуется использовать предикат CONTAINING, вместо предиката LIKE.

@ Arioch'The прокомментировал ссылку на отчет об ошибке (к которому они добавили этот пример). Но ошибка в том, что об ошибке не сообщается.

...