A leitura de colunas fora de ordem retorna valores incorretos (driver ODBC do SQL Server)
Esta pergunta faz parte de uma série de erros no driver ODBC da Microsoft:
O driver ODBC falha ao gerar erros; mas suprime-osLer colunas fora de ordem retorna resultados incorretosNão é possível executar um procedimento armazenado que é um SYNONYMA Microsoft disse que não corrigirá esses erros no driver ODBC.
Versão curtaSe eu leridentificador único valores emSELECIONAR ordem, são retornados os valores corretos:
Coluna B: (leia valor válido)Coluna C (leia valor válido)Se eu ler valores da coluna uniqueidentifier fora da ordem de seleção, as colunas anteriores não retornarão nada (e às vezes lixo):
Coluna C (leia valor válido)Coluna B (retorna vazio)Eu testei isso em:
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 VistaEditar: Exemplos de código fornecidos para:
C #(aplicativo de linha de comando)Delphi(aplicativo de linha de comando)Javascript(linha de comandocscript
)Html + Javascript(Apenas Internet Explorer)fundoComo anúncio da descontinuação de drivers OleDb, Eu queria testar usando os drivers ODBC para SQL Server. Quando altero a conexão para usar um dos drivers ODBC do SQL Server (por exemplo, "{SQL Server}") e executo a mesma instrução SQL.
Atualização - Não Reprovado: Seis anos depois, a Microsoftanunciou a desaprovação do driver OLE DB do SQL Server. (arquivo)
Anteriormente, a Microsoftdescontinuação anunciada do Microsoft OLE DB Provider para SQL Server, parte do SQL Server Native Client (SNAC). Na época, essa decisão foi tomada para tentar fornecer mais simplicidade à história do desenvolvedor em torno do desenvolvimento de software nativo do Windows à medida que avançamos para a era da nuvem com o Banco de Dados SQL do Azure e para tentar aproveitar as semelhanças de JDBC e ODBC para desenvolvedores. No entanto, durante as análises subseqüentes, foi determinado que a depreciação era um erro, porque os cenários substanciais no SQL Server ainda dependem do OLE DB e a alteração deles quebraria alguns cenários existentes do cliente.
Com isso em mente, decidimosundeprecate OLE DB e libere uma nova versãoo primeiro trimestre do ano civil de 2018 Março de 2018.
Estou emitindo uma consulta para três colunas fixas:
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
Isso significa que existem três colunas:
| ColumnA | ColumnB | ColumnC |
| varchar(max) | uniqueidentifier | uniqueidentifier |
|--------------------|--------------------------------------|--------------------------------------|
| 'Hello' | C6705EDE-CE58-4AB9-81BE-679AC1E75DE6 | 2466C151-88EC-40C0-B091-25B6BD74070C |
Nota: Obviamente, quando descobri o bug, estava selecionando dados reais de uma tabela real. Na minha busca para criar um MRCE, a consulta independente de banco de dados acima também aciona a falha.
Estou usando o ADO (COM nativo) e oservidor SQL Driver ODBC para conectar-se ao SQL Server:
Provider=MSDASQL;Driver={SQL Server};Server={vader};Database=master;Trusted_Connection=Yes;
A leitura da coluna C faz com que a coluna B fique vaziaNeste MRCE, estou lendo apenas os valores dos doisuniqueidentifier
colunas.
recordset.Fields['ColumnB'].Value;
recordset.Fields['ColumnC'].Value;
e se eu ler as duas colunasnaquela ordem, os valores saem corretos:
Coluna B:"C6705EDE-CE58-4AB9-81BE-679AC1E75DE6"
(Tipo de varianteVT_BSTR
)Coluna C: "2466C151-88EC-40C0-B091-25B6BD74070C"
(Tipo de varianteVT_BSTR
)Mas se eu ler os valores da coluna na outra ordem:
Coluna C:"2466C151-88EC-40C0-B091-25B6BD74070C"
(Tipo de varianteVT_BSTR
)Coluna B: (empty)
(Tipo de varianteVT_EMPTY
)Exemplo 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);
}
}
}
com 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}
Exemplo 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.
E a saída do console
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
Exemplo de código mínimo (Javascript)Para aumentar o público, aqui está o mesmo código acima em 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);
}
E se você executar:
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}
Exemplo de código mínimo (html + javascript - apenas 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>
Leitura de bônusBlogs do MSDN:A Microsoft está se alinhando ao ODBC para acesso a dados relacionais nativos (arquivo)Blog do ADO.Net:Anúncio de descontinuação do provedor OLEDB do Microsoft SQL Server (arquivo)MSDN:Convertendo aplicativos SQL Server do OLE DB para ODBC (arquivo)HAL2020:OLE DB e SQL Server: histórico, final do jogo e algumas "sujeira" da Microsoft (arquivo)Índice inválido do descritor que lê varchar (max) (arquivo)Fóruns do MSDN:Índice de descritor inválido que chama SQLGetData (arquivo)IBM:O trabalho do DataStage com o ODBC Connector recebe um erro ao usar a coluna LOB (arquivo)