Entonces, pude reproducir el error después de darme cuenta de que CAST
se estaba haciendo localmente, no en la instancia remota. Anteriormente había recomendado pasar al SP3 con la esperanza de solucionarlo (en parte debido a que no podía reproducir el error en SP3 y en parte debido a que era una buena idea, independientemente). Sin embargo, ahora que puedo reproducir el error, está claro que pasar al SP3, aunque probablemente sea una buena idea, no va a solucionarlo. Y también reproduje el error en SQL Server 2008 R2 RTM y 2014 SP1 (utilizando un servidor vinculado local "en bucle" en los tres casos).
Parece que este problema tiene que ver con dónde se está ejecutando la consulta, o al menos dónde se están ejecutando parte (s) de la misma. Digo esto porque pude hacer que la CAST
operación funcione, pero solo al incluir una referencia a un objeto DB local:
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);
Eso realmente funciona. Pero lo siguiente obtiene el error original:
SELECT rmt.*, CAST(NULL AS UNIQUEIDENTIFIER) AS [GUID]
FROM [Local].[database_name].[dbo].[table_name] rmt
CROSS JOIN (VALUES (1)) tmp(dummy);
Supongo que cuando no hay referencias locales, toda la consulta se envía al sistema remoto para que se ejecute, y por alguna razón NULL
no se puede convertir UNIQUEIDENTIFIER
, o tal vez el NULL
controlador OLE DB lo está traduciendo incorrectamente.
Según las pruebas que he realizado, esto parecería ser un error, pero no estoy seguro de si el error se encuentra en SQL Server o en el controlador SQL Server Native Client / OLEDB. Sin embargo, el error de conversión ocurre dentro del controlador OLEDB, por lo que no es necesariamente un problema de conversión de INT
a UNIQUEIDENTIFIER
(una conversión que no está permitida en SQL Server) ya que el controlador no está usando SQL Server para realizar conversiones (SQL Server tampoco permite la conversión INT
a DATE
, sin embargo, el controlador OLEDB lo maneja con éxito, como se muestra en una de las pruebas).
Hice tres pruebas. Para los dos que tuvieron éxito, miré los planes de ejecución XML que muestran la consulta que se ejecuta de forma remota. Para los tres, capturé cualquier excepción o evento OLEDB a través de SQL Profiler:
Eventos:
- Errores y advertencias
- Atención
- Excepción
- Advertencias de Ejecución
- Mensaje de error del usuario
- OLEDB
- TSQL
- todos excepto :
- SQL: StmtRecompile
- Tipo estático XQuery
Filtros de columna:
- Nombre de la aplicación
- NO ME GUSTA % Intellisense%
- SPID
LOS EXÁMENES
Prueba 1
CAST(NULL AS UNIQUEIDENTIFIER)
eso funciona
SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
, (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
Porción relevante del plan de ejecución XML:
<DefinedValue>
<ColumnReference Column="Expr1002" />
<ScalarOperator ScalarString="NULL">
<Const ConstValue="NULL" />
</ScalarOperator>
</DefinedValue>
...
<RemoteQuery RemoteSource="Local" RemoteQuery=
"SELECT 1 FROM "TEMPTEST"."sys"."objects" "Tbl1001""
/>
Prueba 2
CAST(NULL AS UNIQUEIDENTIFIER)
eso falla
SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
-- , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
(nota: mantuve la subconsulta allí, comenté, de modo que sería una diferencia menos cuando comparé los archivos de rastreo XML)
Prueba 3
CAST(NULL AS DATE)
eso funciona
SELECT TOP (2) CAST(NULL AS DATE) AS [Something]
-- , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
(nota: mantuve la subconsulta allí, comenté, de modo que sería una diferencia menos cuando comparé los archivos de rastreo XML)
Porción relevante del plan de ejecución XML:
<DefinedValue>
<ColumnReference Column="Expr1002" />
<ScalarOperator ScalarString="[Expr1002]">
<Identifier>
<ColumnReference Column="Expr1002" />
</Identifier>
</ScalarOperator>
</DefinedValue>
...
<RemoteQuery RemoteSource="Local" RemoteQuery=
"SELECT TOP (2) NULL "Expr1002" FROM "TEMPTEST"."sys"."objects" "Tbl1001""
/>
Si nos fijamos en la Prueba # 3, está haciendo un SELECT TOP (2) NULL
en el sistema "remoto". La traza del Analizador de SQL muestra que el tipo de datos de este campo remoto es de hecho INT
. El seguimiento también muestra que el campo en el lado del cliente (es decir, desde donde estoy ejecutando la consulta) es DATE
, como se esperaba. La conversión de INT
a DATE
, algo que obtendrá un error en SQL Server, funciona bien dentro del controlador OLEDB. El valor remoto es NULL
, por lo que se devuelve directamente, de ahí el <ColumnReference Column="Expr1002" />
.
Si nos fijamos en la Prueba # 1, está haciendo un SELECT 1
en el sistema "remoto". La traza del Analizador de SQL muestra que el tipo de datos de este campo remoto es de hecho INT
. El seguimiento también muestra que el campo en el lado del cliente (es decir, desde donde estoy ejecutando la consulta) es GUID
, como se esperaba. La conversión de INT
a GUID
(recuerde, esto se realiza dentro del controlador, y OLEDB lo llama "GUID"), algo que obtendrá un error en SQL Server, funciona bien dentro del controlador OLEDB. El valor remoto no es NULL
, por lo que se reemplaza con un literal NULL
, de ahí el <Const ConstValue="NULL" />
.
La prueba # 2 falla, por lo que no hay un plan de ejecución. Sin embargo, consulta el sistema "remoto" con éxito, pero simplemente no puede devolver el conjunto de resultados. La consulta que capturó SQL Profiler es:
SELECT TOP (2) NULL "Expr1002" FROM "TEMPTEST"."sys"."objects" "Tbl1001"
Esa es exactamente la misma consulta que se está haciendo en la Prueba # 1, pero aquí está fallando. Hay otras diferencias menores, pero no puedo interpretar completamente la comunicación OLEDB. Sin embargo, el campo remoto todavía se muestra como INT
(wType = 3 = adInteger / entero de cuatro bytes con signo / DBTYPE_I4) mientras que el campo "cliente" todavía se muestra como GUID
(wType = 72 = adGUID / identificador único global / DBTYPE_GUID). La documentación de OLE DB no ayuda mucho, ya que las conversiones de tipo de datos GUID , las conversiones de tipo de datos DBDATE y las conversiones de tipo de datos I4 muestran que la conversión de I4 a GUID o DBDATE no es compatible, pero la DATE
consulta funciona.
Los archivos XML Trace para las tres pruebas se encuentran en PasteBin. Si desea ver los detalles de dónde difiere cada prueba de las demás, puede guardarlas localmente y luego hacer una "diferencia" en ellas. Los archivos son:
- NullGuidSuccess.xml
- NullGuidError.xml
- NullDateSuccess.xml
¿ES DECIR?
¿Qué hacer al respecto? Probablemente solo la solución que anoté en la sección superior, dado que el SQL Native Client - SQLNCLI11
está en desuso a partir de SQL Server 2012. La mayoría de las páginas de MSDN sobre el tema del SQL Server Native Client tienen el siguiente aviso en el parte superior:
Advertencia
SQL Server Native Client (SNAC) no es compatible más allá de SQL Server 2012. Evite usar SNAC en nuevos trabajos de desarrollo y planee modificar las aplicaciones que actualmente lo usan. El controlador ODBC de Microsoft para SQL Server proporciona conectividad nativa de Windows a Microsoft SQL Server y Microsoft Azure SQL Database.
Para más información, consulte:
ODBC ??
Configuré un servidor vinculado ODBC a través de:
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;
Y luego probé:
SELECT CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
FROM [LocalODBC].[tempdb].[sys].[objects] rmt;
y recibió el siguiente error:
El proveedor OLE DB "MSDASQL" para el servidor vinculado "LocalODBC" devolvió el mensaje "La conversión solicitada no es compatible".
Mensaje 7341, Nivel 16, Estado 2, Línea 53
No se puede obtener el valor de fila actual de la columna "(expresión generada por el usuario) .Expr1002" del proveedor OLE DB "MSDASQL" para el servidor vinculado "LocalODBC".
PD
En lo que se refiere al transporte de GUID entre servidores remotos y locales, los valores no NULL se manejan mediante una sintaxis especial. Noté la siguiente información del evento OLE DB en el seguimiento del Analizador de SQL cuando ejecuté CAST(0x00 AS UNIQUEIDENTIFIER)
:
<RemoteQuery RemoteSource="Local" RemoteQuery=
"SELECT {guid'00000000-0000-0000-0000-000000000000'} "Expr1002" FROM "TEMPTEST"."sys"."objects" "Tbl1001""
/>
PPS
También probé a través OPENQUERY
de la siguiente consulta:
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;
y tuvo éxito, incluso sin la referencia de objeto local. El archivo XML de seguimiento de SQL Profiler se ha publicado en PasteBin en:
NullGuidSuccessOPENQUERY.xml
El plan de ejecución XML lo muestra usando una NULL
constante, igual que en la Prueba # 1.