ошибочные c "задержка" оценки CTE? - PullRequest
1 голос
/ 30 марта 2020

Я наблюдаю поведение с CTE, которое я не ожидал (и кажется противоречивым). Не совсем уверен, что это правильно ...

По сути, через CTE я фильтрую строки, чтобы избежать конкретной проблемы, а затем использую результат этого CTE для выполнения вычислений, которые могут привести к ошибкам c строки, которые, как я думал, я удалил в своем CTE ...

Возьмем простую таблицу со столбцом varchar, в которой часто есть номер, но не всегда

CREATE TABLE MY_TABLE(ROW_ID      INTEGER NOT NULL
                    , GOOD_ROW    BOOLEAN NOT NULL
                    , SOME_VALUE  VARCHAR NOT NULL);

INSERT INTO MY_TABLE(ROW_ID, GOOD_ROW, SOME_VALUE)
    VALUES(1, TRUE, '1'), (2, TRUE, '2'), (3, FALSE, 'ABC');

Я также создаю маленькая таблица с просто числами для объединения на

CREATE TABLE NUMBERS(NUMBER_ID INTEGER NOT NULL);
INSERT INTO NUMBERS(NUMBER_ID) VALUES(1), (2), (3);

Соединение этих двух таблиц на SOME_VALUE приводит к ошибке, потому что 'AB C' не является цифрой c, и кажется, что JOIN оценивается ДО Предложение WHERE ( BAD влияние на производительность здесь ...)

SELECT *
  FROM MY_TABLE
  INNER JOIN NUMBERS ON NUMBERS.NUMBER_ID = TO_NUMBER(SOME_VALUE)
  WHERE ROW_ID < 3; --> ERROR

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

WITH ONLY_GOOD_ONES
AS (
  SELECT SOME_VALUE
    FROM MY_TABLE
    WHERE GOOD_ROW = TRUE
)
SELECT *
  FROM ONLY_GOOD_ONES;

Теперь я ожидал бы, что смогу использовать результат этого CTE, когда SOME_VALUE будет иметь числовое значение c.

WITH ONLY_GOOD_ONES
AS (
  SELECT SOME_VALUE 
    FROM MY_TABLE
    WHERE GOOD_ROW = TRUE
)
SELECT *
  FROM ONLY_GOOD_ONES 
  INNER JOIN NUMBERS ON NUMBERS.NUMBER_ID = TO_NUMBER(SOME_VALUE);

Чудо !!!

Это сработало! Я получаю свои 2 ожидаемые записи. Пока все хорошо ...

Однако, если бы я определил свой CTE немного по-другому (предложение WHERE, которое фильтрует те же записи)

WITH ONLY_GOOD_ONES
AS (
  SELECT SOME_VALUE 
    FROM MY_TABLE
    WHERE ROW_ID < 3
)
SELECT *
  FROM ONLY_GOOD_ONES;

Этот CTE возвращает точно то же самое, что и раньше

Но если я попытаюсь присоединиться, произойдет сбой!

WITH ONLY_GOOD_ONES
AS (
  SELECT * 
    FROM MY_TABLE
    WHERE ROW_ID < 3
)
SELECT *
  FROM ONLY_GOOD_ONES 
  INNER JOIN NUMBERS ON NUMBERS.NUMBER_ID = TO_NUMBER(SOME_VALUE);

Я получаю следующую ошибку ...

SQL Ошибка [100038] [ 22018]: числовое значение c значение 'AB C' не распознано

Есть ли конкретное объяснение тому, что вторая версия CTE ведет себя по-другому ???

Ответы [ 3 ]

1 голос
/ 30 марта 2020

Фактический ответ: снежинка не соответствует стандарту SQL и выполняет SQL в указанном порядке.

Они применяют преобразования к данным перед фильтрацией, когда оптимизатор решит, что хочет.

То же самое для вашей таблицы MY_TABLE, когда вы делаете

SELECT some_value::NUMBER FROM my_table WHERE row_id IN (1,2);

. в некоторых случаях приведение as_number происходит во всех строках и взрывается в 'ABC'. Что нарушает правила SQL, когда WHERE оценивается до выполнения преобразований SELECT, но Snowflake знает это годами, и это намеренно, так как все работает быстрее.

Решение состоит в том, чтобы понять, что у вас есть смешанные данные и, следовательно, предположим, что код может и будет работать не по порядку, и, следовательно, использовать защитные версии функций, таких как TRY_TO_NUMBER

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

0 голосов
/ 30 марта 2020

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

Чтобы решить эту проблему, вы всегда можете использовать «Функции преобразования при обработке ошибок»:

SELECT *
  FROM MY_TABLE
  INNER JOIN NUMBERS ON NUMBERS.NUMBER_ID = TRY_TO_NUMBER(SOME_VALUE)
  WHERE ROW_ID < 3;

Дополнительная информация:

https://docs.snowflake.com/en/sql-reference/functions-conversion.html#label -ти-функции преобразования

0 голосов
/ 30 марта 2020

Это потому, что вы получаете другой план выполнения с разными запросами. Вот как запрос выполняется с рабочим запросом: enter image description here

... и вот как он выполняется с запросом, генерирующим ошибку. Ошибка возникает из-за того, что фильтр объединения применяется непосредственно к сканированию таблицы до применения фильтра ROW_ID <3 по сравнению с рабочим запросом. <a href="https://i.stack.imgur.com/3Ma56.png" rel="nofollow noreferrer">enter image description here Эти планы можно просмотреть в истории, щелкнув идентификатор запроса, а затем вкладку «Профиль».

...