Почему это явное приведение вызывает проблемы только со связанным сервером?

21

Я запрашиваю данные со связанного сервера через просмотр на исходном сервере. Представление должно включать пару стандартизированных столбцов, таких как Created, Modifiedи Deleted, но в этом случае таблица на исходном сервере не имеет подходящей информации. Поэтому столбцы явно приводятся к своим соответствующим типам. Я обновил вид, изменив столбец с

NULL AS Modified

в

CAST(NULL as DateTime) as Modified

Однако после выполнения этого обновления представление вызывает следующее сообщение об ошибке:

Сообщение 7341, уровень 16, состояние 2, строка 3 Не удается получить текущее значение строки столбца "(сгенерированное пользователем выражение) .Expr1002" от поставщика OLE DB "SQLNCLI11" для связанного сервера "".

Мы сделали это «явное приведение» -обмена, как правило, на исходном сервере, не беспокоясь, и я подозреваю, что проблема может быть связана с версией задействованных серверов. Нам не нужно применять этот актерский состав, но он кажется чище. Прямо сейчас мне просто интересно, почему это происходит.

Версия сервера (источник):

Microsoft SQL Server 2012 - 11.0.5058.0 (X64) 14 мая 2014 г. 18:34:29 Авторское право (c) Microsoft Corporation Enterprise Edition (64-разрядная версия) в Windows NT 6.1 (сборка 7601: пакет обновления 1) (гипервизор)

Версия сервера (связана):

Microsoft SQL Server 2008 R2 (SP1) - 10.50.2500.0 (X64) 17 июня 2011 г. 00:54:03 Авторское право (c) Microsoft Corporation Enterprise Edition (64-разрядная версия) в Windows NT 6.1 (сборка 7601: пакет обновления 1) (гипервизор) )

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

Ошибочное приведение происходит не с приведением к DateTime, а с приведением столбца к UniqueIdentifier.

Это виновник:

CAST(NULL AS UniqueIdentifier) AS [GUID]

UniqueIdentifiers поддерживаются в SQL Server 2008 R2, и, как упоминалось в комментариях, запрос, выполняемый представлением, прекрасно работает на связанном сервере.

krystah
источник
У вас есть разные настройки ANSI NULL на каждом сервере? Различное сопоставление?
Рэндольф Вест
У обоих серверов ANSI NULL = 0. Исходный сервер имеет параметры сортировки, Danish_Norwegian_CI_ASа связанный сервер имеет параметры сортировки SQL_Danish_Pref_CP1_CI_AS, но COLLATEпредложение не может быть применено к DateTimeстолбцам, поэтому я не получил много дальше!
krystah
Не получится ли это, если select Null from ...в WITH или во вложенном запросе и CASTв другом?
Stoleg
Без явного приведения он будет обрабатываться INTтак, как вы изменили тип данных. Я не знаю, почему это даст вам это сообщение об ошибке, хотя.
Мартин Смит
Ранее я пытался обернуть выбранные значения фактическими значениями в CTE, а затем выбрать их и прикрепить пропущенные значения NULL в операторе, следующем за CTE, безуспешно. Я попробовал ваше предложение, сохранив NULL в CTE и приведя их к выражению, запрашивающему CTE, но оно также выдает ту же ошибку.
krystah

Ответы:

13

Итак, я смог воспроизвести ошибку, поняв, что CAST делается локально, а не на удаленном экземпляре. Ранее я рекомендовал перейти на SP3 в надежде исправить это (частично из-за невозможности воспроизвести ошибку на SP3, а частично из-за того, что это хорошая идея, независимо от того). Тем не менее, теперь, когда я могу воспроизвести ошибку, становится ясно, что переход на SP3, хотя все еще, вероятно, хорошая идея, не собирается это исправить. И я также воспроизвел ошибку в SQL Server 2008 R2 RTM и 2014 SP1 (с использованием «закольцованного» локального связанного сервера во всех трех случаях).

Кажется , что эта проблема связана с , где запрос выполняется, или , по крайней мере там , где часть (части) из него выполняются. Я говорю это потому, что смог заставить CASTработать операцию, но только включив ссылку на локальный объект БД:

SELECT rmt.*, CAST(NULL AS UNIQUEIDENTIFIER) AS [GUID]
FROM [Local].[database_name].[dbo].[table_name] rmt
CROSS JOIN (SELECT TOP (1) 1 FROM [sys].[data_spaces]) tmp(dummy);

Это на самом деле работает. Но следующее получает оригинальную ошибку:

SELECT rmt.*, CAST(NULL AS UNIQUEIDENTIFIER) AS [GUID]
FROM [Local].[database_name].[dbo].[table_name] rmt
CROSS JOIN (VALUES (1)) tmp(dummy);

Я предполагаю, что когда нет локальных ссылок, весь запрос отправляется в удаленную систему для выполнения, и по какой-то причине NULLs не может быть преобразовано UNIQUEIDENTIFIERили, возможно, NULLнеправильно переводится драйвером OLE DB.


Исходя из проведенного мною тестирования, это может показаться ошибкой, но я не уверен, что ошибка в SQL Server или в драйвере собственного клиента SQL Server / OLEDB. Однако ошибка преобразования происходит в драйвере OLEDB, и поэтому не обязательно является проблемой преобразования из INTв UNIQUEIDENTIFIER(преобразование, которое не разрешено в SQL Server), поскольку драйвер не использует SQL Server для выполнения преобразований (SQL Server также не делает этого). позволяют преобразовывать INTв DATE, но ручки водителя OLEDB , который успешно, как показано в одном из тестов).

Я провел три теста. Для двух успешных я посмотрел на планы выполнения XML, которые показывают запрос, который выполняется удаленно. Для всех трех я захватил любые исключения или события OLEDB через SQL Profiler:

Мероприятия:

  • Ошибки и предупреждения
    • Внимание
    • исключение
    • Предупреждения Исполнения
    • Сообщение об ошибке пользователя
  • OLEDB
    • все
  • TSQL
    • все кроме :
      • SQL: StmtRecompile
      • Статический тип XQuery

Фильтры колонок:

  • Имя приложения
    • НЕ НРАВИТСЯ % Intellisense%
  • ИСП
    • Больше или равно 50

ТЕСТЫ

  • Тест 1

    • CAST(NULL AS UNIQUEIDENTIFIER) это работает

    SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
                 , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
    FROM [Local].[TEMPTEST].[sys].[objects] rmt;

    Соответствующая часть плана выполнения XML:

              <DefinedValue>
                <ColumnReference Column="Expr1002" />
                <ScalarOperator ScalarString="NULL">
                  <Const ConstValue="NULL" />
                </ScalarOperator>
              </DefinedValue>
      ...
    <RemoteQuery RemoteSource="Local" RemoteQuery=
     "SELECT 1 FROM &quot;TEMPTEST&quot;.&quot;sys&quot;.&quot;objects&quot; &quot;Tbl1001&quot;"
     />
  • Тест 2

    • CAST(NULL AS UNIQUEIDENTIFIER) что терпит неудачу

    SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
             --  , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
    FROM [Local].[TEMPTEST].[sys].[objects] rmt;

    (примечание: я сохранил подзапрос, закомментированный, чтобы при сравнении файлов трассировки XML разница была на один раз меньше)

  • Тест 3

    • CAST(NULL AS DATE) это работает

    SELECT TOP (2) CAST(NULL AS DATE) AS [Something]
             --  , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
    FROM [Local].[TEMPTEST].[sys].[objects] rmt;

    (примечание: я сохранил подзапрос, закомментированный, чтобы при сравнении файлов трассировки XML разница была на один раз меньше)

    Соответствующая часть плана выполнения XML:

              <DefinedValue>
                <ColumnReference Column="Expr1002" />
                <ScalarOperator ScalarString="[Expr1002]">
                  <Identifier>
                    <ColumnReference Column="Expr1002" />
                  </Identifier>
                </ScalarOperator>
              </DefinedValue>
     ...
    <RemoteQuery RemoteSource="Local" RemoteQuery=
     "SELECT TOP (2) NULL &quot;Expr1002&quot; FROM &quot;TEMPTEST&quot;.&quot;sys&quot;.&quot;objects&quot; &quot;Tbl1001&quot;" 
     />

Если вы посмотрите на тест № 3, он выполняется SELECT TOP (2) NULLна «удаленной» системе. Трассировка SQL Profiler показывает, что тип данных этого удаленного поля на самом деле INT. Трассировка также показывает, что поле на стороне клиента (то есть, откуда я запускаю запрос) DATE, как и ожидалось. Преобразование из INTв DATE, что приведет к ошибке в SQL Server, прекрасно работает в драйвере OLEDB. Удаленное значение есть NULL, поэтому оно возвращается напрямую, отсюда и <ColumnReference Column="Expr1002" />.

Если вы посмотрите на Тест № 1, он выполняется SELECT 1на «удаленной» системе. Трассировка SQL Profiler показывает, что тип данных этого удаленного поля на самом деле INT. Трассировка также показывает, что поле на стороне клиента (то есть, откуда я запускаю запрос) GUID, как и ожидалось. Преобразование из INTв GUID(помните, что это делается в драйвере, и OLEDB называет его «GUID»), что приведет к ошибке в SQL Server, прекрасно работает в драйвере OLEDB. Удаленного значения нет NULL , поэтому оно заменяется литералом NULL, отсюда и <Const ConstValue="NULL" />.

Тест № 2 не пройден, поэтому нет плана выполнения. Однако он успешно запрашивает «удаленную» систему, но просто не может вернуть набор результатов. Запрос, который захватил SQL Profiler:

SELECT TOP (2) NULL "Expr1002" FROM "TEMPTEST"."sys"."objects" "Tbl1001"

Это тот же самый запрос, который выполняется в Тесте № 1, но здесь он терпит неудачу. Существуют и другие незначительные различия, но я не могу полностью интерпретировать сообщение OLEDB. Однако удаленное поле по-прежнему отображается как INT(wType = 3 = adInteger / четырехбайтовое целое число со знаком / DBTYPE_I4), а поле «клиент» по-прежнему отображается как GUID(wType = 72 = adGUID / глобально уникальный идентификатор / DBTYPE_GUID). Документации OLE DB не очень помогает , как GUID типа данных преобразований , DBDATE Тип данных преобразований и I4 Тип данных преобразований показывают , что преобразование из I4 либо GUID или DBDATE не поддерживается, все же DATEработы запроса.

XML-файлы трассировки для трех тестов находятся в PasteBin. Если вы хотите увидеть детали того, где каждый тест отличается от других, вы можете сохранить их локально, а затем выполнить «сравнение» с ними. Файлы:

  1. NullGuidSuccess.xml
  2. NullGuidError.xml
  3. NullDateSuccess.xml

ЭРГО?

Что с этим делать? Вероятно, это просто обходной путь, который я отметил в верхнем разделе, учитывая, что собственный клиент SQL - SQLNCLI11- не рекомендуется на SQL Server 2012. Большинство страниц MSDN по теме собственного клиента SQL Server имеют следующее уведомление на верх:

Предупреждение

Собственный клиент SQL Server (SNAC) не поддерживается после SQL Server 2012. Избегайте использования SNAC в новых разработках и планируйте модифицировать приложения, которые в настоящее время его используют. Драйвер Microsoft ODBC для SQL Server обеспечивает встроенную возможность соединения с Windows , в Microsoft SQL Server и Microsoft Azure SQL Database.

Для получения дополнительной информации, пожалуйста, смотрите:


ODBC ??

Я установил ODBC Linked Server через:

EXEC master.dbo.sp_addlinkedserver
  @server = N'LocalODBC',
  @srvproduct=N'{my_server_name}',
  @provider=N'MSDASQL',
  @provstr=N'Driver={SQL Server};Server=(local);Trusted_Connection=Yes;';

EXEC master.dbo.sp_addlinkedsrvlogin
  @rmtsrvname=N'LocalODBC',
  @useself=N'True',
  @locallogin=NULL,
  @rmtuser=NULL,
  @rmtpassword=NULL;

А потом попробовал:

SELECT CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
FROM [LocalODBC].[tempdb].[sys].[objects] rmt;

и получил следующую ошибку:

Поставщик OLE DB «MSDASQL» для связанного сервера «LocalODBC» вернул сообщение «Запрошенное преобразование не поддерживается».
Сообщение 7341, уровень 16, состояние 2, строка 53
Не удается получить текущее значение строки столбца "(сгенерированное пользователем выражение) .Expr1002" от поставщика OLE DB "MSDASQL" для связанного сервера "LocalODBC".


PS

Поскольку это относится к транспортировке GUID между удаленными и локальными серверами, значения, отличные от NULL, обрабатываются с помощью специального синтаксиса. Я заметил следующую информацию о событии OLE DB в трассировке SQL Profiler при запуске CAST(0x00 AS UNIQUEIDENTIFIER):

<RemoteQuery RemoteSource="Local" RemoteQuery=
 "SELECT {guid'00000000-0000-0000-0000-000000000000'} &quot;Expr1002&quot; FROM &quot;TEMPTEST&quot;.&quot;sys&quot;.&quot;objects&quot; &quot;Tbl1001&quot;" 
 />

PPS

Я также проверил через OPENQUERYследующий запрос:

SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
     --, (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM   OPENQUERY([Local], N'SELECT 705 AS [dummy] FROM [TEMPTEST].[sys].[objects];') rmt;

и это удалось, даже без ссылки на локальный объект. Файл XML трассировки SQL Profiler был опубликован в PasteBin по адресу:

NullGuidSuccessOPENQUERY.xml

План выполнения XML показывает его с использованием NULLконстанты, как в Тесте № 1.

Соломон Руцкий
источник
Я воссоздал этот выпуск 2012 года sp3-cu1.
Боб Климс
@BobKlimes Интересно. Это использование связанного сервера с экземпляром 2008 R2 или обратной связью?
Соломон Руцкий
удаленный связанный сервер 2008 R2 sp2 + MS15-058 (10.50.4339.0).
Боб Климс
1
не похоже на версию Протестировал несколько комбинаций 2008r2,2012,2014,2016, и все они до сих пор выдавали ошибку, даже 2008r2-2008r2.
Боб Климс
2
Что за крайне непонятный вопрос. Большое спасибо за понимание и исследования, @srutzky, это очень ценится. Я буду помнить о дряхлости SNAC для будущей работы и отступления от вышеупомянутого обходного пути. Отличная работа!
krystah
4

Есть только уродливый обходной путь - используйте некоторую константу даты как '1900-01-01'вместо null.

CAST('1900-01-01' as DateTime) as Modified

После импорта вы можете обновить столбцы 1900-01-01обратно на Null.

Это своего рода особенность / ошибка SQL 2012, как здесь .

Изменить: заменено 1900-00-00действительной датой 1900-01-01согласно комментарию @a_horse_with_no_name ниже.

Антон Кроуглов
источник
Этот обходной путь, вероятно, заслуживает упоминания, но теперь он может перестать быть актуальным, поскольку ФП пояснил, что виновником проблемы является uniqueidentifierстолбец. Или, может быть, это может быть адаптировано - что-то вроде CAST('00000000-0000-0000-0000-000000000000' AS UniqueIdentifier) AS [GUID], возможно?
Андрей М
Спасибо, парни. Приведение к пустому GUID или DateTime работает, но мне нужно понять, почему это происходит. Стоит также упомянуть, что я ничего не импортирую, поэтому нет возможности изменить исходные данные.
Крыста
1
1900-00-00является недействительной датой и не будет принята.
a_horse_with_no_name
@a_horse_with_no_name: исправлена ​​глупая ошибка.
Антон Кроуглов
2

Проблема связана с преобразованием типов данных (как отмечено в комментариях).

Учтите следующее:

SELECT NULL as NullColumn INTO SomeTable;
EXEC sp_help SomeTable;
DROP TABLE SomeTable;

Обратите внимание, что NullColumnэто типа int. SQL Server не любит преобразовывать intзначения в uniqueidentifier. Этот SELECTоператор потерпит неудачу при преобразовании типов данных:

--Just a SELECT from nothing
SELECT CAST(CAST(NULL as int) as uniqueidentifier);
--
--or to see it from a physical table:
SELECT NULL as NullColumn INTO SomeTable;
SELECT CAST(NullColumn as uniqueidentifier) FROM SomeTable;
DROP TABLE SomeTable;

Сообщение 529, уровень 16, состояние 2, строка 3

Явное преобразование из типа данных int в uniqueidentifier не допускается.

Хотя это конкретное значение (NULL) можно преобразовать в GUID, SQL Server выдает ошибку, основанную на преобразовании типов данных, прежде чем даже смотреть на конкретные значения. Вместо этого вам нужно будет выполнить многошаговую CASTоперацию, чтобы изменить неявный intтип данных, который может быть чисто преобразован в uniqueidentifer-, что означает приведение сначала к varcharпотом uniqueidentifier:

--Just a SELECT from nothing
SELECT CAST(CAST(CAST(NULL as int) as varchar) as uniqueidentifier);
--
--or to see it from a physical table:
SELECT NULL as NullColumn INTO SomeTable;
SELECT CAST(CAST(NullColumn as varchar(32)) as uniqueidentifier) FROM SomeTable;
DROP TABLE SomeTable;
AMtwo
источник
Эта проблема на самом деле не из-за преобразования типов данных, по крайней мере, не в SQL Server. Подробности в моем ответе , но преобразование выполняется в драйвере OLEDB, а не в SQL Server, и правила преобразования не совпадают.
Соломон Руцкий,
1

ФП в конечном итоге может решить, является ли это правильным ответом.

У меня нет «абсолютных» доказательств, но я «подозреваю», что проблема связана с тем фактом, что UniqueIdentifer зависит от сервера, и, возможно, провайдеру сложно определить, с какого сервера (локального или удаленного) получить этот uniqueidentifier, даже если он значение NULL. Вот почему в этом сценарии вы, вероятно, можете успешно преобразовать любой другой тип данных, но не uniqueidentifier. Типы данных, которые зависят от сервера, такие как UNIQUEIDENTIFIERS и DATETIMEOFFSET, сообщат вам об ошибке, с которой вы столкнулись.

Использование OPENQUERY вместо названия из 4-х частей работает.

set nocount on  
DECLARE @cmd nVARCHAR(max)
DECLARE @datatype SYSNAME

DECLARE _CURSOR CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY
FOR
SELECT NAME
FROM sys.types 

OPEN _CURSOR

FETCH NEXT
FROM _CURSOR
INTO @datatype

WHILE @@FETCH_STATUS = 0
BEGIN
    BEGIN TRY
        SET @cmd = 'select top 1 cast(null as ' + @Datatype + ') as CastedData from remoteserver.remotedatabase.remoteschema.remotetable'
        PRINT @cmd
        EXECUTE sp_executesql @cmd
    END TRY

    BEGIN CATCH
        PRINT Error_message()
    END CATCH

FETCH NEXT
FROM _CURSOR
INTO @datatype
END --End While

CLOSE _CURSOR

DEALLOCATE _CURSOR
Скотт Ходжин
источник
Что именно вы имеете в виду Uniqueidentifierи DateTimeOffsetбыть сервер-зависимым? Есть ли у вас источники для этого?
krystah
@krystah - по этой ссылке ( technet.microsoft.com/en-us/library/ms190215(v=sql.105).aspx ) «Тип данных uniqueidentifier хранит 16-байтовые двоичные значения, которые работают как глобально уникальные идентификаторы (GUID) GUID - это уникальное двоичное число, никакой другой компьютер в мире не сгенерирует дубликат этого значения GUID. Основное использование GUID - это назначение идентификатора, который должен быть уникальным в сети, в которой на многих сайтах имеется много компьютеров. "
Скотт Ходжин
Для DateTimeOffSet ( msdn.microsoft.com/en-us/library/bb630289.aspx ) Определяет дату, которая объединяется со временем дня, которое имеет информацию о часовом поясе и основано на 24-часовых часах
Скотт Ходгин
Я «делаю вывод», что, поскольку эти два типа данных «кажутся» единственными, которые дают ошибку в вашем сценарии, а эти типы данных «зависят от сервера», это является причиной вашей проблемы. Если вы используете OPENQUERY, чтобы получить результаты из своего представления и добавить дополнительные нулевые приведения, это должно сработать, потому что провайдер знает, «где» получить эту информацию
Скотт Ходгин
@krystah и Скотт: эти два типа данных не зависят от сервера, по крайней мере, в том, как вы описываете и подразумеваете. Идентификаторы GUID зависят от «архитектуры» с точки зрения их базового двоичного представления ( подробности см. Здесь ), но если бы это было проблемой здесь, то идентификаторы GUID не передавались бы должным образом. Для DATETIMEOFFSET, который знает только смещение, а не фактический часовой пояс / TimeZoneID. Хотя оба используют системную информацию для генерации новых значений, новые значения здесь не генерируются, и если бы они были, они не были бы сгенерированы в поставщике.
Соломон Руцкий,
0

Обходной путь: принятый ответ кажется, указывает, что преобразование должно происходить локально, потому что драйвер OLEDB не поддерживает его.

Поэтому я думаю, что простой обходной путь (по крайней мере, в случае моего запроса, который выбирает нуль uniqueidentifierв базовом случае рекурсивного CTE) - это объявить нулевую переменную:

declare @nullGuid as uniqueidentifier = null;

--Instead of...
CAST(NULL AS UniqueIdentifier) AS [GUID]

--use
@nullGuid AS [GUID]
xr280xr
источник