La lectura de columnas fuera de servicio devuelve valores incorrectos (controlador ODBC de SQL Server)

Esta pregunta forma parte de una serie de errores en el controlador ODBC de Microsoft:

El controlador ODBC no puede generar errores; sino que los suprimeLeer columnas fuera de servicio devuelve resultados incorrectosNo se puede ejecutar un procedimiento almacenado que sea un SINONIMO

Microsoft ha dicho que no corregirán estos errores en su controlador ODBC.

Version corta

Si leoidentificador único valores enSELECCIONAR orden, me devuelven los valores correctos:

Columna B: (leer valor válido)Columna C (leer valor válido)

Si leo valores de columna de identificador único fuera del orden de selección, las columnas anteriores no devuelven nada (y a veces basura):

Columna C (leer valor válido)Columna B (vuelve vacío)

He probado esto en:

Microsoft SQL Azure (RTM) - 12.0.2000.8Microsoft SQL Server 2012 (SP3)Microsoft SQL Server 2008 R2 (SP2)Microsoft SQL Server 2005 - 9.00.5000.00 (Intel X86)Windows 10Windows 7Windows Vista

Editar: Ejemplos de código proporcionados para:

C#(aplicación de línea de comandos)Delphi(aplicación de línea de comandos)Javascript(línea de comandocscript)HTML + Javascript(Solo Internet Explorer)Antecedentes

ConEl anuncio del desuso de los controladores OleDb, Quería probar usando los controladores ODBC para SQL Server. Cuando cambio la conexión para usar uno de los controladores ODBC de SQL Server (por ejemplo, "{SQL Server}") y ejecuto la misma instrucción SQL.

Actualización: no en desuso: Seis años después, Microsoft tieneanunció el desuso del controlador OLE DB de SQL Server. (archivo)

Anteriormente, Microsoftanunció el desuso del proveedor Microsoft OLE DB para SQL Server, parte del SQL Server Native Client (SNAC). En ese momento, se tomó esta decisión para tratar de proporcionar más simplicidad para la historia del desarrollador sobre el desarrollo de software nativo de Windows a medida que avanzamos en la era de la nube con Azure SQL Database, y para tratar de aprovechar las similitudes de JDBC y ODBC para los desarrolladores. Sin embargo, durante las revisiones posteriores se determinó que la desaprobación era un error porque los escenarios sustanciales dentro de SQL Server todavía dependen de OLE DB y cambiarlos rompería algunos escenarios de clientes existentes.

Con esto en mente, hemos decididodesaprobar OLE DB y lanzar una nueva versión porel primer trimestre del año calendario 2018 Marzo 2018.

Estoy emitiendo una consulta para tres columnas fijas:

SELECT
   CAST('Hello' AS varchar(max)) AS ColumnA,
   CAST('C6705EDE-CE58-4AB9-81BE-679AC1E75DE6' AS uniqueidentifier) AS ColumnB,
   CAST('2466C151-88EC-40C0-B091-25B6BD74070C' AS uniqueidentifier) AS ColumnC

Esto significa que hay tres columnas:

| ColumnA            | ColumnB                              | ColumnC                              |
| varchar(max)       | uniqueidentifier                     | uniqueidentifier                     |
|--------------------|--------------------------------------|--------------------------------------|
| 'Hello'            | C6705EDE-CE58-4AB9-81BE-679AC1E75DE6 | 2466C151-88EC-40C0-B091-25B6BD74070C |

Nota: Obviamente, cuando descubrí el error, estaba seleccionando datos reales de una tabla real. En mi búsqueda para crear un MRCE, la consulta agnóstica de base de datos anterior también desencadena el error.

Estoy usando ADO (COM nativo) y elservidor SQL Controlador ODBC para conectarse a SQL Server:

Provider=MSDASQL;Driver={SQL Server};Server={vader};Database=master;Trusted_Connection=Yes;
Leer la columna C primero hace que la columna B esté vacía

En este MRCE, solo estoy leyendo los valores de los dosuniqueidentifier columnas

recordset.Fields['ColumnB'].Value;
recordset.Fields['ColumnC'].Value;

y si leo las dos columnasen ese orden, los valores salen correctos:

Columna B: "C6705EDE-CE58-4AB9-81BE-679AC1E75DE6" (Tipo de varianteVT_BSTR)Columna C: "2466C151-88EC-40C0-B091-25B6BD74070C" (Tipo de varianteVT_BSTR)

Pero si leo los valores de la columna en el otro orden:

Columna C: "2466C151-88EC-40C0-B091-25B6BD74070C" (Tipo de varianteVT_BSTR)Columna B: (empty) (Tipo de varianteVT_EMPTY)Ejemplo de código mínimo (C #)
using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            TestIt();
        }

        private static void TestIt()
        {
            String serverName = "vader";
            String CRLF = "\r\n";

            String connectionString = "Provider=MSDASQL;Driver={SQL Server};Server={" + serverName + "};Database=master;Trusted_Connection=Yes;";
            WriteLn("ConnectionString: " + connectionString);
            WriteLn("");

            Int32 adOpenForwardOnly = 0;
            Int32 adLockReadOnly = 1;
            Int32 adCmdText = 1;

            dynamic rs = CreateOleObject("ADODB.Recordset");

            String sql = "SELECT " + CRLF +
                " CAST('Hello' AS varchar(max)) AS ColumnA, " + CRLF +
                " CAST('C6705EDE-CE58-4AB9-81BE-679AC1E75DE6' AS uniqueidentifier) AS ColumnB," + CRLF +
                " CAST('2466C151-88EC-40C0-B091-25B6BD74070C' AS uniqueidentifier) AS ColumnC";

            WriteLn("Command text:");
            WriteLn(sql);
           , WriteLn("");

            WriteLn("Executing query");
            rs.open(sql, connectionString, adOpenForwardOnly, adLockReadOnly, adCmdText);
            WriteLn("Query complete");

            if (rs.EOF) return; //just to shut people up

            var columnC = rs("ColumnC").Value;
            var columnB = rs("ColumnB").Value;

            WriteLn("ColumnB: " + columnB);
            WriteLn("ColumnC: " + columnC);
        }

        private static dynamic CreateOleObject(string progID)
        {
            Type comType = Type.GetTypeFromProgID(progID);
            var instance = Activator.CreateInstance(comType);

            return instance;
        }

        private static void WriteLn(string v)
        {
            Console.WriteLine(v);
        }
    }
}

con resultados:

ConnectionString: Provider=MSDASQL;Driver={SQL Server};Server={vader};Database=master;Trusted_Connection=Yes;

Command text:
SELECT
 CAST('Hello' AS varchar(max)) AS ColumnA,
 CAST('C6705EDE-CE58-4AB9-81BE-679AC1E75DE6' AS uniqueidentifier) AS ColumnB,
 CAST('2466C151-88EC-40C0-B091-25B6BD74070C' AS uniqueidentifier) AS ColumnC

Executing query
Query complete
ColumnB:
ColumnC: {2466C151-88EC-40C0-B091-25B6BD74070C}
Ejemplo de código mínimo (Delphi)
program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  ADOInt,
  ComObj,
    ActiveX;

function DataTypeEnumToStr(t: DataTypeEnum): string;
begin
    case t of
    adEmpty: Result := 'adEmpty';
    adSmallInt: Result := 'adSmallInt';
    adInteger: Result := 'adInteger';
    adTinyInt: Result := 'adTinyInt';
    adBigInt: Result := 'adBigInt';
    adUnsignedTinyInt: Result := 'adUnsignedTinyInt';
    adUnsignedSmallInt: Result := 'adUnsignedSmallInt';
    adUnsignedInt: Result := 'adUnsignedInt';
    adUnsignedBigInt: Result := 'adUnsignedBigInt';
    adSingle: Result := 'adSingle';
    adDouble: Result := 'adDouble';
    adCurrency: Result := 'adCurrency';
    adDecimal: Result := 'adDecimal';
    adNumeric: Result := 'adNumeric';
    adBoolean: Result := 'adBoolean';
    adError: Result := 'adError';
    adUserDefined: Result := 'adUserDefined';
    adVariant: Result := 'adVariant';
    adIDispatch: Result := 'adIDispatch';
    adIUnknown: Result := 'adIUnknown';
    adGUID: Result := 'adGUID';
    adDate: Result := 'adDate';
    adDBDate: Result := 'adDBDate';
    adDBTime: Result := 'adDBTime';
    adDBTimeStamp: Result := 'adDBTimeStamp';
    adBSTR: Result := 'adBSTR';
    adChar: Result := 'adChar';
    adVarChar: Result := 'adVarChar';
    adLongVarChar: Result := 'adLongVarChar';
    adWChar: Result := 'adWChar';
    adVarWChar: Result := 'adVarWChar';
    adLongVarWChar: Result := 'adLongVarWChar';
    adBinary: Result := 'adBinary';
    adVarBinary: Result := 'adVarBinary';
    adLongVarBinary: Result := 'adLongVarBinary';
    adChapter: Result := 'adChapter';
    adFileTime: Result := 'adFileTime';
    adDBFileTime: Result := 'adDBFileTime';
    adPropVariant: Result := 'adPropVariant';
    adVarNumeric: Result := 'adVarNumeric';
    adArray: Result := 'adArray';
    else
        Result := IntToStr(t);
    end;
end;

procedure TestLoadingGUID;
var
    connectionString: string;
    sql: string;
    rs: _Recordset;
    s: string;
    guid: TGUID;
    i: Integer;
    fld: Field;

    function DumpField(const FieldName: string): string;
    var
        sValue: string;
        vt: TVarType;
        value: OleVariant;
    begin
        WriteLn('Reading '+FieldName+' column');
        value := rs.Fields[FieldName].Value;

        sValue := value;
        vt := TVarData(value).VType;
        WriteLn('   VType: '+IntToStr(vt));
        WriteLn('   Value: "'+sValue+'" (as string)');
        WriteLn('');
    end;

begin
{
    Tested:
        Windows 10
        Windows 7

        Microsoft SQL Server 2012 (SP3)
        Microsoft SQL Server 2008 R2 (SP2)
        Microsoft SQL Server 2005 - 9.00.5000.00 (Intel X86)
}

    Write('Enter name of server to connect to (leave blank for VADER): ');
    ReadLn(s);

    if s = '' then
        s := 'vader';

    connectionString := 'Provider=MSDASQL;Driver={SQL Server};Server={'+s+'};Database=master;Trusted_Connection=Yes;';
    WriteLn('ConnectionString: '+connectionString);
    WriteLn;


//  sql := 'SELECT CAST(NULL AS varchar(max)) AS ColumnA, newid() AS ColumnB, newid() as ColumnC';
    sql := 'SELECT '+#13#10+
            '   CAST(''Hello'' AS varchar(max)) AS ColumnA, '+#13#10+
            '   CAST(''C6705EDE-CE58-4AB9-81BE-679AC1E75DE6'' AS uniqueidentifier) AS ColumnB,'+#13#10+
            '   CAST(''2466C151-88EC-40C0-B091-25B6BD74070C'' AS uniqueidentifier) AS ColumnC';


    rs := CoRecordset.Create;
    rs.Open(sql, connectionString, adOpenForwardOnly, adLockReadOnly, adCmdText);
    WriteLn('');

    WriteLn('Command text: ');
    WriteLn(sql);
    WriteLn;

    if rs.EOF then Exit; //just to shut people up

    WriteLn('Recordset Fields');
    for i := 0 to rs.Fields.Count-1 do
    begin
        fld := rs.Fields[i];
        if fld.DefinedSize = MaxInt then
            WriteLn(Format('   %d.  %s: %s(%s)', [i, fld.Name, DataTypeEnumToStr(fld.Type_), 'max']))
        else
            WriteLn(Format('   %d.  %s: %s(%d)', [i, fld.Name, DataTypeEnumToStr(fld.Type_), fld.DefinedSize]));
    end;
    WriteLn('');
    WriteLn('');

    WriteLn('Fields["ColumnA"]: "'+rs.Fields['ColumnA'].Value+'"  (VType: '+IntToStr(TVarData(rs.Fields['ColumnA'].Value).VType)+')');
    WriteLn('Fields["ColumnC"]: "'+rs.Fields['ColumnC'].Value+'"  (VType: '+IntToStr(TVarData(rs.Fields['ColumnC'].Value).VType)+')');
    WriteLn('Fields["ColumnB"]: "'+rs.Fields['ColumnB'].Value+'"  (VType: '+IntToStr(TVarData(rs.Fields['ColumnB'].Value).VType)+')');
    WriteLn('');

    WriteLn('Fields[0]: "'+rs.Fields[0].Value+'"  (VType: '+IntToStr(TVarData(rs.Fields[0].Value).VType)+')');
    WriteLn('Fields[2]: "'+rs.Fields[2].Value+'"  (VType: '+IntToStr(TVarData(rs.Fields[2].Value).VType)+')');
    WriteLn('Fields[1]: "'+rs.Fields[1].Value+'"  (VType: '+IntToStr(TVarData(rs.Fields[1].Value).VType)+')');
    WriteLn('');



    DumpField('ColumnA');
    DumpField('ColumnB');
    s := DumpField('ColumnC');

    if s = '' then
    begin
        WriteLn(Format('WARNING: ColumnB expected to not-empty, but was "%s"',  [s]));
        Exit;
    end;
end;


begin
  try
        CoInitialize(nil);
        TestLoadingGUID;
  except
     on E: Exception do
        Writeln(E.ClassName, ': ', E.Message);
  end;

    WriteLn('Press enter to close');
    Readln;
end.

Y la salida de la consola

Enter name of server to connect to (leave blank for VADER):
ConnectionString: Provider=MSDASQL;Driver={SQL Server};Server={vader};Database=master;Trusted_Connection=Yes;


Command text:
SELECT
        CAST('Hello' AS varchar(max)) AS ColumnA,
        CAST('C6705EDE-CE58-4AB9-81BE-679AC1E75DE6' AS uniqueidentifier) AS ColumnB,
        CAST('2466C151-88EC-40C0-B091-25B6BD74070C' AS uniqueidentifier) AS ColumnC

Recordset Fields
   0.  ColumnA: adLongVarChar(max)
   1.  ColumnB: adGUID(16)
   2.  ColumnC: adGUID(16)


Fields["ColumnA"]: "Hello"  (VType: 1)
Fields["ColumnC"]: "{2466C151-88EC-40C0-B091-25B6BD74070C}"  (VType: 8)
Fields["ColumnB"]: ""  (VType: 0)

Fields[0]: ""  (VType: 0)
Fields[2]: "{2466C151-88EC-40C0-B091-25B6BD74070C}"  (VType: 8)
Fields[1]: ""  (VType: 0)

Reading ColumnA column
   VType: 0
   Value: "" (as string)

Reading ColumnB column
   VType: 0
   Value: "" (as string)

Reading ColumnC column
   VType: 8
   Value: "{2466C151-88EC-40C0-B091-25B6BD74070C}" (as string)

WARNING: ColumnB expected to not-empty, but was ""
Press enter to close
Ejemplo de código mínimo (Javascript)

Para ampliar la audiencia, aquí está el mismo código anterior en javascript:

OdbcFails.js

main();

function main() {
  serverName = "vader";
  CRLF = "\r\n";

  var connectionString = "Provider=MSDASQL;Driver={SQL Server};Server={"+serverName+"};Database=master;Trusted_Connection=Yes;";
    WriteLn("ConnectionString: "+connectionString);
    WriteLn("");

  adOpenForwardOnly = 0;
  adLockReadOnly = 1;
  adCmdText = 1;
  var rs = new ActiveXObject("ADODB.Recordset");

  var sql = "SELECT "+CRLF+
            " CAST('Hello' AS varchar(max)) AS ColumnA, "+CRLF+
            " CAST('C6705EDE-CE58-4AB9-81BE-679AC1E75DE6' AS uniqueidentifier) AS ColumnB,"+CRLF+
            " CAST('2466C151-88EC-40C0-B091-25B6BD74070C' AS uniqueidentifier) AS ColumnC";

    WriteLn("Command text:");
    WriteLn(sql);
    WriteLn("");

  WriteLn("Executing query");
  rs.open(sql, connectionString, adOpenForwardOnly, adLockReadOnly, adCmdText);
  WriteLn("Query complete");

    if (rs.EOF) return; //just to shut people up

  var columnC = rs("ColumnC").Value;
  var columnB = rs("ColumnB").Value;

   WriteLn("ColumnB: "+columnB);
   WriteLn("ColumnC: "+columnC);

}

function WriteLn(str) {
  WScript.Echo(str);
}  

Y si corres:

C:\Users\ian>cscript OdbcFails.js

Microsoft (R) Windows Script Host Version 5.812
Copyright (C) Microsoft Corporation. All rights reserved.

ConnectionString: Provider=MSDASQL;Driver={SQL Server};Server={vader};Database=master;Trusted_Connection=Yes;

Command text:
SELECT
 CAST('Hello' AS varchar(max)) AS ColumnA,
 CAST('C6705EDE-CE58-4AB9-81BE-679AC1E75DE6' AS uniqueidentifier) AS ColumnB,
 CAST('2466C151-88EC-40C0-B091-25B6BD74070C' AS uniqueidentifier) AS ColumnC

Executing query
Query complete
ColumnB: undefined
ColumnC: {2466C151-88EC-40C0-B091-25B6BD74070C}
Ejemplo de código mínimo (html + javascript - solo Internet Explorer)
<!doctype html>
<html>

<head>
    <script>
        function WriteLn(str) {
            console.log(str);
        }

        function main() {
            serverName = "vader";
            CRLF = "\r\n";

            var connectionString = "Provider=MSDASQL;Driver={SQL Server};Server={" + serverName + "};Database=master;Trusted_Connection=Yes;";
            WriteLn("ConnectionString: " + connectionString);
            WriteLn("");

            adOpenForwardOnly = 0;
            adLockReadOnly = 1;
            adCmdText = 1;
            var rs = new ActiveXObject("ADODB.Recordset");

            var sql = "SELECT " + CRLF +
                " CAST('Hello' AS varchar(max)) AS ColumnA, " + CRLF +
                " CAST('C6705EDE-CE58-4AB9-81BE-679AC1E75DE6' AS uniqueidentifier) AS ColumnB," + CRLF +
                " CAST('2466C151-88EC-40C0-B091-25B6BD74070C' AS uniqueidentifier) AS ColumnC";

            WriteLn("Command text:");
            WriteLn(sql);
            WriteLn("");

            WriteLn("Executing query");
            rs.open(sql, connectionString, adOpenForwardOnly, adLockReadOnly, adCmdText);
            WriteLn("Query complete");

            if (rs.EOF) return; //just to shut people up

            var columnC = rs("ColumnC").Value;
            var columnB = rs("ColumnB").Value;

            WriteLn("ColumnB: " + columnB);
            WriteLn("ColumnC: " + columnC);

        }

        main();

    </script>

    <body>
    </body>
    <script>
Lectura adicionalBlogs de MSDN:Microsoft se está alineando con ODBC para el acceso a datos relacionales nativos (archivo)Blog de ADO.Net:Anuncio de desaprobación del proveedor OLEDB de Microsoft SQL Server (archivo)MSDN:Conversión de aplicaciones de SQL Server de OLE DB a ODBC (archivo)HAL2020:OLE DB y SQL Server: historial, final del juego y algo de "suciedad" de Microsoft (archivo)Lectura de índice de descriptor no válida varchar (max) (archivo)Foros de MSDN:Índice de descriptor no válido que llama a SQLGetData (archivo)IBM:El trabajo DataStage con ODBC Connector recibe un error al usar la columna LOB (archivo)

Respuestas a la pregunta(1)

Su respuesta a la pregunta