NEWID () в объединенной виртуальной таблице вызывает непреднамеренное перекрестное применение


9

Мой фактический рабочий запрос был внутренним соединением, но этот простой пример с перекрестным соединением почти всегда воспроизводит проблему.

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT NEWID() TEST_ID
) BB ( B )

С моим внутренним объединением у меня было много строк, для которых я добавил к каждому GUID с помощью функции NEWID (), и примерно для 9 из 10 таких строк умножение с двухстрочной виртуальной таблицей дало ожидаемые результаты, всего 2 копии тот же GUID, в то время как 1 из 10 даст разные результаты. Это было неожиданно, мягко говоря, и мне очень трудно было найти эту ошибку в моем скрипте генерации тестовых данных.

Если вы посмотрите на следующие запросы, используя также недетерминированные функции getdate и sysdatetime, вы не увидите этого, в любом случае, я не вижу - я всегда вижу одно и то же значение datetime в обеих конечных строках результата.

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT GETDATE() TEST_ID
) BB ( B )

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT SYSDATETIME() TEST_ID
) BB ( B )

В настоящее время я использую SQL Server 2008, и сейчас я работаю над тем, чтобы загрузить строки с GUID в переменную таблицы перед завершением сценария генерации случайных данных. Как только я получу их в виде значений в таблице, а не виртуальной таблицы, проблема исчезнет.

У меня есть обходной путь, но я ищу способы обхода без фактических таблиц или табличных переменных.

При написании этого я безуспешно пытался использовать следующие возможности: 1) поместить newid () во вложенную виртуальную таблицу:

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT TEST_ID
    FROM (
        SELECT NEWID() TEST_ID
    ) TT
) BB ( B )

2) обертывание newid () в приведенном выражении, таком как:

SELECT CAST(NEWID() AS VARCHAR(100)) TEST_ID

3) изменение порядка появления виртуальных таблиц в выражении соединения

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS JOIN (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )

4) с использованием некоррелированного креста

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )

Непосредственно перед тем, как наконец опубликовать этот вопрос, теперь я попробовал это с успехом, кажется, применимо коррелированное пересечение:

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
    SELECT A
    FROM (
        SELECT 1 UNION ALL
        SELECT 2
    ) TT ( A )
    WHERE BB.B IS NOT NULL
) AA ( A )

У кого-нибудь есть другой, более элегантный, более простой обходной путь? Я действительно не хочу использовать перекрестное применение или корреляцию для простого умножения строк, если мне не нужно.

Ответы:


20

Такое поведение предусмотрено специально, как подробно описано в отчете об ошибках Connect . Наиболее подходящий ответ Microsoft воспроизводится ниже для удобства (и в случае, если ссылка умирает в какой-то момент):

Опубликовано Microsoft 7/7/2008 в 9:27

Закрытие петли. , , Я обсуждал этот вопрос с командой разработчиков. И в итоге мы решили не менять текущее поведение по следующим причинам:

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

  2. Такое «поведение на ряд» не является новой проблемой, хотя и не обсуждается широко. Мы начали настраивать его поведение еще в выпуске Yukon. Но довольно сложно точно определить, во всех случаях, что именно это означает! Например, применимо ли это к промежуточным строкам, рассчитанным «по пути» к конечному результату? - в этом случае это явно зависит от выбранного плана. Или это относится только к строкам, которые в конечном итоге появятся в готовом результате? - здесь идет отвратительная рекурсия, я уверен, вы согласитесь!

  3. Как я упоминал ранее, мы по умолчанию «оптимизируем производительность», что хорошо для 99% случаев. 1% случаев, когда это может изменить результаты, довольно легко обнаружить - «побочные» «функции», такие как NEWID, - и их легко «исправить» (следствие торговли). Это значение по умолчанию для «оптимизации производительности» снова, давно установлено и принято. (Да, это не позиция, выбранная компиляторами для обычных языков программирования, но так и будет).

Итак, наши рекомендации:

  1. Избегайте использования негарантированной синхронизации и семантики количества выполнений.
  2. Избегайте использования NEWID () глубоко в табличных выражениях.
  3. Используйте OPTION, чтобы вызвать определенное поведение (торговля перф)

Надеюсь, что это объяснение поможет прояснить причины, по которым мы можем закрыть эту ошибку, поскольку "не исправлю".

Функции GETDATEи SYSDATETIMEдействительно недетерминированы, но они обрабатываются как константы времени выполнения для конкретного запроса. В широком смысле это означает, что значение функции кэшируется при запуске запроса, и результат повторно используется для всех ссылок в запросе.

Ни один из «обходных путей» в вопросе не является безопасным; нет никакой гарантии, что поведение не изменится при следующей компиляции плана, при следующем применении пакета обновления или накопительного обновления ... или по другим причинам.

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


«Ни один из« обходных путей »в вопросе не является безопасным;» То же самое. Когда я попытался применить один из них к моему фактическому рабочему запросу, это не помогло вообще.
JM Hicks
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.