Чому цей чіткий склад викликає проблеми лише з пов'язаним сервером?


21

Я запитую дані з пов'язаного сервера через представлення на початковому сервері. Подання повинно включати пару стандартизованих стовпців, таких як Created, Modifiedі Deleted, але в цьому випадку таблиця на вихідному сервері не має відповідної інформації. Отже, стовпці явно наводяться на відповідні типи. Я оновив перегляд, змінивши стовпець із

NULL AS Modified

до

CAST(NULL as DateTime) as Modified

Однак після цього оновлення подання викликає таке повідомлення про помилку:

Msg 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 (Build 7601: Pack 1) (Hypervisor)

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

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 (Build 7601: Service Pack 1) (Hypervisor )

Редагувати
Щойно я зрозумів, що помилився, не опублікувавши всі стовпці, про які йдеться, і прошу вибачення за те, що залишив важливу деталь. Я не знаю, як я цього раніше не помітив. Але питання все ще залишається.

Помилкова передача відбувається не з передачею на DateTime, а зі стовпцем, який передається UniqueIdentifier.

Це винуватець:

CAST(NULL AS UniqueIdentifier) AS [GUID]

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


Чи є у вас різні налаштування ANSI NULL на кожному сервері? Різне співставлення?
Рандольф Вест

На обох серверах ANSI NULL = 0. У початкового сервера є зіставлення, Danish_Norwegian_CI_ASа на зв’язаному сервері - зіставлення SQL_Danish_Pref_CP1_CI_AS, але COLLATEпункт не можна застосувати до DateTimeстовпців, тому я не отримав набагато більше!
krystah

Чи не вдасться це зробити, якщо у вас є select Null from ...вкладений або вкладений запит і 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 або драйвері OLEDB для Native Client SQL Server. Однак помилка перетворення виникає в драйвері OLEDB, і тому не обов'язково виникає проблема перетворення з INTна UNIQUEIDENTIFIER(конверсія, яка не дозволена в SQL Server), оскільки драйвер не використовує SQL Server для здійснення конверсій (SQL Server також не робить дозволяють перетворювати INTв DATE, але ручки водія OLEDB , який успішно, як показано в одному з тестів).

Я провів три випробування. Для двох, що досягли успіху, я переглянув плани виконання XML, які показують запит, який виконується віддалено. Для всіх трьох я зафіксував будь-які винятки або події OLEDB через SQL Profiler:

Події:

  • Помилки та попередження
    • Увага
    • Виняток
    • Попередження щодо виконання
    • Повідомлення про помилку користувача
  • OLEDB
    • всі
  • TSQL
    • все, крім :
      • SQL: StmtRecompile
      • Статичний тип XQuery

Фільтри стовпців:

  • ApplicationName
    • НЕ ПОДОБАЙТЕ % Intellisense%
  • SPID
    • Більше або рівне 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роботи запиту.

Файли Trace XML для трьох тестів розташовані на PasteBin. Якщо ви хочете побачити деталі того, чим кожен тест відрізняється від інших, можете зберегти їх локально, а потім зробити "розріз" на них. Файли:

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

ERGO?

Що з цим робити? Мабуть, якраз обхід, який я зазначив у верхньому розділі, враховуючи, що SQL Native Client - SQLNCLI11- застаріло, як у SQL Server 2012. Більшість сторінок MSDN на тему Stive Server Native Client мають таке повідомлення в верх:

Увага

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

Для отримання додаткової інформації дивіться:


ODBC ??

Я встановив ODBC-пов'язаний сервер за допомогою:

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" повернув повідомлення "Запрошена конверсія не підтримується."
Msg 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.
Bob Klimes

@BobKlimes Цікаво. Це використання зв'язаного сервера для екземпляра 2008 року R2 або зворотного циклу?
Соломон Руцький

віддалений зв'язаний сервер - 2008 R2 sp2 + MS15-058 (10.50.4339.0).
Боб Клімес

1
не здається пов'язаними з версією. Випробували декілька комбо 2008r2,2012,2014,2016, і всі до цих пір призвели до помилок, навіть 2008r2-2008r2.
Bob Klimes

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 працює, але мені потрібно зрозуміти, чому це відбувається. Варто також зазначити, що я нічого не імпортую, тому немає можливості змінити вихідні дані.
krystah

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;

Msg 529, рівень 16, стан 2, рядок 3

Явне перетворення з типу даних int в унікальний ідентифікатор заборонено.

Хоча це конкретне значення (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;

Ця проблема насправді не пов’язана з перетвореннями типу даних, принаймні, не в межах SQL Server. Деталі є в моїй відповіді , але перетворення проводиться в драйвері OLEDB, а не в SQL Server, і правила перетворення не однакові.
Соломон Руцький

1

Зрештою, ОП може вирішити, чи це відповідна відповідь.

У мене немає «абсолютного» доказу, але я «підозрюю», що проблема випливає з того, що UniqueIdentifer залежить від сервера і, можливо, у постачальника виникають труднощі з'ясувати, на якому сервері (локальному чи віддаленому) отримати цей унікальний ідентифікатор, навіть якщо це нуль. Ось чому ви, ймовірно, можете успішно передавати будь-який інший тип даних у цьому сценарії, але не унікальний ідентифікатор. Типи даних, які залежать від "сервера", як 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 ) "Тип даних унікального ідентифікатора зберігає 16-байтові бінарні значення, які діють як глобально унікальні ідентифікатори (GUID) . GUID - це унікальний двійковий номер; жоден інший комп'ютер у світі не генерує дублікат цього значення GUID. Основне використання GUID - це призначення ідентифікатора, який повинен бути унікальним у мережі, що має багато комп'ютерів на багатьох сайтах. "
Скотт Ходгін

Для DateTimeOffSet ( msdn.microsoft.com/en-us/library/bb630289.aspx ) Визначає дату, яка поєднується з часом доби, яка має обізнаність про часовий пояс і заснована на цілодобовому годиннику
Скотт Ходгін,

Я вважаю, що оскільки ці два типи даних "здаються" є єдиними, які дають помилку у вашому сценарії, а ці типи є "залежними від сервера", це є причиною вашої проблеми. Якщо ви використовуєте OPENQUERY для отримання своїх результатів із перегляду та додавання додаткових нульових ролей, це має працювати, тому що постачальник знає "куди", щоб отримати цю інформацію
Скотт Ходгін,

@krystah та Scott: ці два типи даних не залежать від сервера, принаймні, не в тому, як ви описуєте та маєте на увазі. 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]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.