Чтение столбцов вне порядка возвращает неверные значения (драйвер ODBC SQL Server)

Укороченная версия

Если я прочитал значения uniqueidentifier в порядке SELECT , я вернул правильные значения:

  • Столбец B : (читать действительное значение)
  • ColumnC (чтение действительного значения)

Если я прочитал значения столбца uniqueidentifier вне порядка выбора, более ранние столбцы ничего не возвращают (а иногда и мусор):

  • ColumnC (чтение действительного значения)
  • Столбец B (возвращается пустым)

Я тестировал это на:

  • Microsoft SQL Azure (окончательная первоначальная версия) – 12.0.2000.8
  • Microsoft SQL Server 2012 (SP3)
  • Microsoft SQL Server 2008 R2 (SP2)
  • Microsoft SQL Server 2005 – 9.00.5000.00 (Intel X86)
  • Windows 10
  • Windows 7
  • Виндоус виста

Изменить : примеры кода приведены для:

  • C # (приложение командной строки)
  • Delphi (приложение командной строки)
  • Javascript (командная строка cscript )
  • Html + Javascript (только для Internet Explorer)

Задний план

С объявлением об отказе от драйверов OleDb я хотел протестировать, используя драйверы ODBC для SQL Server. Когда я меняю соединение на использование одного из драйверов ODBC SQL Server (например, «{SQL Server}») и выполняю ту же инструкцию SQL

Я выдаю запрос для трех фиксированных столбцов:

 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 

Это означает, что имеется три столбца:

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

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

Я использую ADO (родной COM) и драйвер ODBC SQL Server для подключения к SQL Server:

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

Чтение столбца C сначала приводит к тому, что ColumnB будет пустым

В этом MRCE я читаю только значения двух столбцов uniqueidentifier .

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

и, если я прочитаю два столбца в этом порядке , значения выдаются правильно:

  • Столбец B : "C6705EDE-CE58-4AB9-81BE-679AC1E75DE6" (вариант Тип VT_BSTR )
  • ColumnC : "2466C151-88EC-40C0-B091-25B6BD74070C" (вариант Тип VT_BSTR )

Но если я прочитал значения столбца в другом порядке:

  • ColumnC : "2466C151-88EC-40C0-B091-25B6BD74070C" (вариант Тип VT_BSTR )
  • Столбец B : (empty) (Тип варианта VT_EMPTY )

Пример минимального кода (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); } } } 

с результатами:

 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} 

Пример минимального кода (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. 

И консольный выход

 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 

Пример минимального кода (Javascript)

Чтобы расширить аудиторию, вот такой же код выше в 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); } 

И если вы запустите:

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} 

Пример минимального кода (html + javascript – только 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> 

Другие непредвиденные функции ODBC (работающие в OLEDB)

  • Stackoverflow: чтение столбцов из строя возвращает неверные результаты
  • Stackoverflow: невозможно выполнить хранимую процедуру, которая является SYNONYM
  • Stackoverflow: драйвер ODBC подавляет ошибки

Чтение бонусов

  • Блоги MSDN: Microsoft выравнивает с ODBC для собственного реляционного доступа к данным ( архив )
  • Блог ADO.Net: Объявление об отставке поставщика OLEDB Microsoft SQL Server ( архив )
  • MSDN: преобразование приложений SQL Server из OLE DB в ODBC ( архив )
  • HAL2020: OLE DB и SQL Server: история, конечная игра и некоторая «грязь» Microsoft ( архив )
    • Недопустимое чтение индекса индекса varchar (max) ( архив )
  • Форумы MSDN: недопустимый индекс дескриптора, вызывающий SQLGetData ( архив )
  • IBM: задание DataStage с соединителем ODBC получает сообщение об ошибке при использовании столбца LOB ( архив )

Ответ заключается в том, что это поведение не будет исправлено в драйвере ODBC.

В конце 1980-х годов было достигнуто преимущество в производительности, что вынуждало клиента только считывать столбцы из буфера строк по порядку. Вы спросите у водителя, разрешено ли вам читать значения столбцов в любом порядке через функцию SqlGetInfo :

 SqlGetInfo(..., SQL_GD_ANY_ORDER, ...) //returns true or false 
  • SQL_GD_ANY_COLUMN = SQLGetData может быть вызван для любого несвязанного столбца, включая те, которые были до последнего связанного столбца. Обратите внимание, что столбцы должны вызываться в порядке возрастания номера столбца, если не SQL_GD_ANY_ORDER .
  • SQL_GD_ANY_ORDER = SQLGetData можно вызывать для несвязанных столбцов в любом порядке. Обратите внимание, что SQLGetData можно вызывать только для столбцов после последнего связанного столбца, если также не возвращается SQL_GD_ANY_COLUMN .

Несмотря на то, что компьютеры в настоящее время имеют более 4 МБ оперативной памяти, современный драйвер ODBC SQL Server продолжает отказываться от этого ограничения с эпохи Windows 3.0:

Внутренний клиент ODBC-сервера SQL Server не поддерживает использование SQLGetData для извлечения данных в порядке случайных столбцов.

Они очень хорошо могли поддержать такую ​​вещь, как 17-летние OLEDB-драйверы, а также драйверы ADO.NET SqlClient. Но они этого не делают; поэтому драйвер ODBC – это отвратительная мозг, непригодная для использования в реальном мире.

Вам необходимо продолжать использовать:

  • SQLOLEDB ( поддерживается )
  • SQLNCLI (устаревший)
  • ADO.net SqlClient (поддерживается)

Чтение бонусов

  • Драйвер ODBC 11 для ограничений SQL Server и SQLGetData

  • Политики поддержки драйверов клиентов

    • Политики поддержки OLE DB : приложения должны использовать поставщик OLE DB SQL Server, входящий в состав операционной системы Windows.
    • Политики поддержки ADO : приложения ADO могут использовать поставщика SQLOLEDB OLE DB, который включен в Windows, если они не требуют каких-либо функций SQL Server 2005 или более поздних версий.
  • Удалить приглашение для входа со связанными с SQL таблицами в Access
  • Не удается подключиться к SQL Server 2014 с использованием ODBC
  • Как написать столбец даты с DBI
  • Sql Server ODBC Date Field - дополнительная функция не реализована
  • Нужна помощь в подключении MSSQL через Soci
  • Какую структуру можно использовать для подключения к экземпляру SQL Server с iPhone?
  • Как создать odbcparameter для XML-типа sql-сервера для объекта odbccommand?
  • Распределенные транзакции между MySQL и MSSQL
  • DSN проверяет нормально, но ошибка входа в систему пытается использовать DSN для SQL Server 2005
  • Возвращает типы данных NULL в собственном клиенте C ++ из SQL Server
  • Драйверы ODBC для Mac OS X 10.6
  • Давайте будем гением компьютера.