web-dev-qa-db-ja.com

DataTypeCompatilityがオンのときに12/30/1899をSQL Serverネイティブクライアントにパラメーター化する方法は?

短縮版

datetime12/30/1899をSQL Serverに渡そうとすると、Invalidで失敗します日付形式-ただし、ネイティブクライアントドライバーのみ、およびDataTypeCompatiblityモードのみ。

ロングバージョン

SQL Serverに対して、ADOでパラメーター化されたクエリを使用しようとすると:

_SELECT ?
_

datetime値をadDBTimeStampとしてパラメーター化します。

_//Language agnostic, vaguely C#-like pseudo-code
void TestIt()
{
   DateTime dt = new DateTime("3/15/2020");
   VARIANT v = DateTimeToVariant(dt);

   Command cmd = new Command();
   cmd.CommandText = "SELECT ? AS SomeDate";
   cmd.Parameters.Append(cmd.CreateParameter("", adDBTimeStamp, adParamInput, 0, v);

   Connection cn = GetConnection();
   cmd.Set_ActiveConnection(cn);
   cmd.Execute(out recordsAffected, EmptyParam, adExecuteNoRecords);
}
_

そして、日付が_3/15/2020_の場合、これは正常に機能します。

VARIANT を作成します VType of 7( _VT_DATE_ )、および8バイトの浮動小数点値である値:

_VARIANT
   Int32  vt = 7; //VT_DATE
   Double date = 0;
_

しかし、1899年12月30日には失敗します。

特定の1つの日時で同じテストコードを実行すると、失敗します。

_void TestIt()
{
   DateTime dt = new DateTime("12/30/1899");
   VARIANT v = DateTimeToVariant(dt);

   Command cmd = new Command();
   cmd.CommandText = "SELECT ? AS SomeDate";
   cmd.Parameters.Append(cmd.CreateParameter("", adDBTimeStamp, adParamInput, 0, v);

   Connection cn = GetConnection();
   cmd.Set_ActiveConnection(cn);
   cmd.Execute(out recordsAffected, EmptyParam, adExecuteNoRecords);
}
_

ADO OLEDBプロバイダーは例外をスローします(つまり、SQL Serverに到達する前に):

_Invalid date format
_

ただし、すべてのSQL Server OLEDBプロバイダーで発生するわけではありません

この問題をデバッグしたところ、すべてのSQL Server OLEDBプロバイダーで発生するわけではないことに気付きました。 Microsoftは通常4 OLE SQL Server用のDBプロバイダーを持っています:

  • SQLOLEDB:Microsoft OLE SQL Server用DBプロバイダー(Windows 2000以降Windowsに同梱されています)
  • SQLNCLI:SQL Server Native Client(SQL Server 2005に付属)
  • _SQLNCLI10_:SQL Server Native Client 11.0(SQL Server 2008に付属)
  • _SQLNCLI11_:SQL Server Native Client 12.0(SQL Server 2012に同梱)
  • MSOLEDBSQL:Microsoft OLE SQL Server用のDBドライバー(SQL Server 2016に付属)

いくつかの異なるプロバイダーで試してみると、それはdoesのためにうまく機能します:

  • SQLOLEDB:機能
  • _SQLNCLI11_(DataTypeCompatibilityなし):機能
  • _SQLNCLI11_(DataTypeCompatiilityがオン):失敗

DataTypeCompatibility?

はい。 ActiveXデータオブジェクト(ADO)、非友好的なCOM OLEDB APIの友好的なCOMラッパーは、新しいdatetimexml、_datetime2_を理解しません、datetimeoffsetデータ型。これらの新しい型を表すために、新しいOLEDBデータ型定数が作成されました。そのため、既存のOLEDBアプリケーションは新しい定数を理解できません。

そのため、新しい キーワードは"native"でサポートされています OLE DBドライバ:

  • _DataTypeCompatibility=80_

これを接続文字列に追加できます。

"プロバイダー= SQLNCLI11;データソース=スクリュードライバー;ユーザーID = hatguy;パスワード= hunter2;DataTypeCompatibility = 80;"

これは、OLEDBが最初に発明されたときに存在していたOLEDBデータタイプのみを返すようにOLEDBドライバに指示します。

_| SQL Server data type | SQLOLEDB        | SQLNCLI                        | SQLNCLI                           |
|                      |                 |                                | w/DataTypeCompatibility=80        |
|----------------------|-----------------|--------------------------------|-----------------------------------|
| Xml                  | adLongVarWChar  | 141 (DBTYPE_XML)               | adLongVarChar                     |
| datetime             | adDBTimeStamp   | adDBTimeStamp                  | adDBTimeStamp                     |
| datetime2            | adVarWChar      | adDBTimeStamp                  | adVarWChar                        |
| datetimeoffset       | adVarWChar      | 146 (DBTYPE_DBTIMESTAMPOFFSET) | adVarWChar                                  |
| date                 | adVarWChar      | adDBDate                       | adVarWChar                        |
| time                 | adVarWChar      | 145 (DBTYPE_DBTIME2)           | adVarWChar                        |
| UDT                  |                 | 132 (DBTYPE_UDT)               | adVarBinary (documented,untested) |
| varchar(max)         | adLongVarChar   | adLongVarChar                  | adLongVarChar                     |
| nvarchar(max)        | adLongVarWChar  | adLongVarWChar                 | adLongVarWChar                    |
| varbinary(max)       | adLongVarBinary | adLongVarBinary                | adLongVarBinary                   |
| timestamp            | adBinary        | adBinary                       | adBinary                          |
_

そして失敗があります

いつ:

  • datetime値をパラメーター化しようとしています
  • _12/30/1899_の値
  • "native client"ドライバを使用する場合
  • DataTypeCompatilibtyはオンです
  • ドライバー自体が値を窒息させる
  • その値が実際には完全に問題ない場合。

「1899年12月30日」の日付を使用しても、本質的に問題はありません。

  • SELECT CAST('18991230' AS datetime)は正常に動作します
  • 元のOLE DBドライバで正常に動作します
  • "native" OLE DBドライバで正常に動作します
  • DataTypeCompatibilityがオンのネイティブドライバで失敗するだけです

明らかにこれはMicrosoftのバグですOLE DBドライバです。しかし、Microsoftが絶対に絶対に真実ではないeverever[〜#〜] ever [〜#〜]、バグを修正します。

それを回避する方法は?

この特別な日時を検出でき、データアクセスレイヤーでこのバグを回避することができます。

  • しかし、VARIANT構造に配置できる値が必要です。
  • それは_12/30/1899 12:00:00 AM_を表します
  • SQOLEDBの下で動作します
  • SQLNCLIxxドライバーの下
  • MSOLEDBSQLドライバーの下
  • DataTypeCompatibilityMode
  • (そして、モードがオフの場合でも、地獄は何ですか-ADOをオンにしないと無効です)

ドライバーによって生成されたT-SQL

OLE DB driver doesが実際に私が言うことを実行するのに煩わしい場合、生成されたRPCのプロファイルを作成できます。

[〜#〜] sqoledb [〜#〜]

_exec sp_executesql N'SELECT @P1 AS SomeDate',N'@P1 datetime','1899-12-30 00:00:00'_

SQLNCLI11

exec sp_executesql N'SELECT @P1 AS SomeDate',N'@P1 datetime2(0)','1899-12-30 00:00:00'

CMRE(Delphi)

_program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  ComObj,
  ActiveX,
  ADOdb,
  ADOint,
  Variants;

function GetConnection(Provider: string; DataTypeCompatibility: Boolean): _Connection;
var
    connectionString: string;
begin
{
    SQLOLEDB - Default provider with Windows
    SQLNCLI11 - SQL Server 2008 native client
}
    connectionString := 'Provider='+Provider+'; Data Source=screwdriver;User ID=hydrogen;Password=hunter2;';
    if DataTypeCompatibility then
        connectionString := connectionString+'DataTypeCompatibility=80';

    Result := CoConnection.Create;
    Result.Open(connectionString, '', '', adConnectUnspecified);
end;

procedure Test(ProviderName: string; DataTypeCompatibility: Boolean);
var
    dt: TDateTime;
    v: OleVariant;
    cmd: _Command;
    cn: _Connection;
    recordsAffected: OleVariant;
    s: string;
begin
    dt := EncodeDate(1899, 12, 30);// 12/30/1899 12:00:00 AM (also known in Delphi as zero)
    v := dt; //the variant is of type VT_DATE (7)

    cmd := CoCommand.Create;
    cmd.CommandText := 'SELECT ? AS SomeDate';
    cmd.Parameters.Append(cmd.CreateParameter('', adDBTimeStamp, adParamInput, 0, v));

    try
        cn := GetConnection(ProviderName, DataTypeCompatibility);
    except
        on E: Exception do
            begin
                WriteLn('Provider '+ProviderName+' not installed: '+E.message);
                Exit;
            end;
    end;

    if SameText(ProviderName, 'SQLOLEDB') then
        s := ''
    else if DataTypeCompatibility then
        s := ' (with DataTypeCompatibility)'
    else
        s := ' (without DataTypeCompatibility)';

    cmd.Set_ActiveConnection(cn);
    try
        cmd.Execute({out}recordsAffected, EmptyParam, adExecuteNoRecords);
        WriteLn('Provider '+ProviderName+s+': success.');
    except
        on E:Exception do
            begin
                WriteLn('Provider '+ProviderName+s+' failed: '+E.Message);
            end;
    end;

end;

procedure Main;
begin
    CoInitialize(nil);

    Test('SQLOLEDB', False);        //SQL Server client that ships with Windows since 2000

    Test('SQLNCLI', False);     //SQL Server 2005 native client
    Test('SQLNCLI', True);      //SQL Server 2005 native client, w/ DataTypeCompatibilty

    Test('SQLNCLI10', False);   //SQL Server 2008 native client
    Test('SQLNCLI10', True);    //SQL Server 2008 native client, w/ DataTypeCompatibilty

    Test('SQLNCLI11', False);   //SQL Server 2012 native client
    Test('SQLNCLI11', True);    //SQL Server 2012 native client, w/ DataTypeCompatibilty

    Test('MSOLEDBSQL', False);  //SQL Server 2016 native client
    Test('MSOLEDBSQL', True);   //SQL Server 2016 native client, w/ DataTypeCompatibilty
end;


begin
  try
    Main;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
    WriteLn('Press enter to close');
    ReadLn;
end.
_

そして、これはDelphi固有の質問ではありませんが、 Delphiを使用しています。そのため、Delphiのタグが付けられています。あなたが文句を言うなら 私はあなたの舌を詰まらせます。

:これはADO.netではなく、ADOです。これはマネージ.NET Frameworkクラスライブラリではなく、ネイティブのWin32 COM OLE DB APIです。

7
Ian Boyd

答えはBrakNickuでした。

パラメータのNumericScaleプロパティを1〜7の範囲に設定します。

コードの変更:

Parameter p = cmd.CreateParameter("", adDBTimeStamp, adParamInput, 0, v);

Parameter p = cmd.CreateParameter("", adDBTimeStamp, adParamInput, 0, v);
p.NumericScale = 1;

動作します。

SQL Server 2000に対するSQLOLEDBドライバーでも動作します。

さまざまなデータ型の精度とスケール

さまざまなデータ型を含むSQL Serverから行セットを返すと、OLEDBにさまざまなT-SQLデータ型のPrecisionNumericScaleを尋ねることができます。

SQL Server type   ADO type               Precision  NumericScale  DefinedSize
----------------  ---------------------  ---------  ------------  -----------
int               adInteger (3)          10         255           4
real              adSingle (4)           7          255           4
money             adCurrency (6)         19         255           8
bit               adBoolean (11)         255        255           2
tinyint           adUnsignedTinyInt (17) 3          255           1
bigint            adBigInt (20)          19         255           8
uniqueidentifier  adGUID (72)            255        255           16
char(35)          adChar (129)           255        255           35
nchar(35)         adWChar (130)          255        255           35
decimal(15,5)     adNumeric (131)        15         5             19
datetime          adDBTimeStamp (135)    23         3             16
varchar(35)       adVarChar (200)        255        255           35
text              adLongVarChar (201)    255        255           2147483647
varchar(max)      adLongVarChar (201)    255        255           2147483647
nvarchar(35)      adVarWChar (202)       255        255           35
nvarchar(max)     adLongVarWChar (203)   255        255           1073741823
xml               adLongVarWChar (203)   255        255           1073741823
image             adLongVarBinary (205)  255        255           2147483647
varbinary(max)    adLongVarBinary (205)  255        255           2147483647

SQL ServerはdatetimeフィールドをNumericScale で返します。そこにmay変更の美徳がある

Parameter p = cmd.CreateParameter("", adDBTimeStamp, adParamInput, 0, v);
p.NumericScale = 1;

Parameter p = cmd.CreateParameter("", adDBTimeStamp, adParamInput, 0, v);
p.NumericScale = 3;
0
Ian Boyd