此问题是Microsoft ODBC中一系列错误的一部分 驱动程序:
- ODBC driver fails to raise errors; but instead suppresses them
- Reading columns out of order returns incorrect results
- Cannot execute a stored procedure that is a SYNONYM
微软表示他们不会在他们的ODBC中修复这些错误 驱动程序。
如果我在 SELECT 顺序中读取 uniqueidentifier 值,我会返回正确的值:
如果我读取选择顺序之外的uniqueidentifier列值,则较早的列不返回任何内容(有时是垃圾):
我已经测试了这个:
修改:提供的代码示例:
cscript
) 使用the announcement of the deprecation of OleDb drivers,我想测试使用SQL Server的ODBC驱动程序。当我更改连接以使用其中一个SQL Server ODBC驱动程序(例如“{SQL Server}”)并执行相同的SQL语句时。
更新 - 未完成:六年后,Microsoft已announced the un-deprecation the SQL Server OLE DB driver。 (archive)
以前,Microsoft announced deprecation of the Microsoft OLE DB Provider for SQL Server是SQL Server Native Client(SNAC)的一部分。当时,当我们使用Azure SQL数据库进入云时代时,我们决定尝试为Windows本机软件开发提供更简单的开发人员故事,并尝试利用JDBC和ODBC的相似性为开发人员。但是,在随后的审核过程中,确定弃用是一个错误,因为SQL Server中的实际场景仍然依赖于OLE DB,而更改这些场景会破坏一些现有的客户场景。
考虑到这一点,我们决定取消激活OLE DB 并在2018年3月的第一季度发布新版本。
我正在发出三个固定列的查询:
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)和 SQL Server ODBC驱动程序连接到SQL Server:
Provider=MSDASQL;Driver={SQL Server};Server={vader};Database=master;Trusted_Connection=Yes;
在此MRCE中,我只读取两个uniqueidentifier
列的值。
recordset.Fields['ColumnB'].Value;
recordset.Fields['ColumnC'].Value;
如果我按顺序读取两列 ,则值正确无误:
"C6705EDE-CE58-4AB9-81BE-679AC1E75DE6"
(变体类型VT_BSTR
)"2466C151-88EC-40C0-B091-25B6BD74070C"
(变体类型VT_BSTR
)但是,如果我按其他顺序读取列值:
"2466C151-88EC-40C0-B091-25B6BD74070C"
(变体类型VT_BSTR
)(empty)
(变体类型VT_EMPTY
)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}
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中的相同代码:
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}
<!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>
答案 0 :(得分:0)
答案是这个行为不会在ODBC驱动程序中修复。
在20世纪80年代后期,强制客户端只按行顺序读取行缓冲区中的列,这有一个性能优势。您可以询问驱动程序是否允许您通过SqlGetInfo function:
以任何顺序读取列值SqlGetInfo(..., SQL_GD_ANY_ORDER, ...) //returns true or false
SQL_GD_ANY_COLUMN
= SQLGetData ,包括最后一个绑定列之前的列。请注意,必须按列号递增的顺序调用列,除非还返回SQL_GD_ANY_ORDER
。SQL_GD_ANY_ORDER
= SQLGetData 。请注意,除非还返回SQL_GD_ANY_COLUMN
,否则只能为最后一个绑定列之后的列调用 SQLGetData 。即使计算机目前有超过4MB的RAM,Windows 3.0时代的现代SQL Server ODBC驱动程序continues to opt-in to this limitation:
SQL Server Native Client ODBC驱动程序不支持使用SQLGetData以随机列顺序检索数据。
他们非常可以支持这样的事情,如17岁的OLEDB驱动程序,以及ADO.NET SqlClient驱动程序。但他们没有;所以ODBC驱动程序是脑死亡的憎恶,不适合现实世界的使用。
您需要继续使用:
奖金阅读
Client Driver Support Policies
- OLE DB支持策略:应用程序应使用Windows操作系统附带的SQL Server OLE DB提供程序。
- ADO支持策略:如果不需要SQL Server 2005或更高版本的任何功能,ADO应用程序可以使用Windows附带的SQLOLEDB OLE DB提供程序。