Вставьте поле TDateTime в базу данных SQL 2008, используя собственный клиент 10

У нас есть унаследованное приложение, написанное на Delphi 2007 и все еще использующее BDE (да, его нужно переключить на ADO, но с более чем 500 тыс. Строк, это БОЛЬШАЯ работа). Он подключается к БД SQL Server 2008 с использованием ODBC-соединения SQL SERVER. Я играю с переключением на SQL Server Native Client 10.0 вместо этого и столкнулся с интересной проблемой. При попытке вставить запись в таблицу, содержащую поля даты и времени, мы получаем следующую ошибку:

Project DateTimeParamTest.exe raised exception class EDBEngineError with message 'General SQL error.
[Microsoft][SQL Server Native Client 10.0]Datetime field overflow. Fractional second precision exceeds the scale specified in
the parameter binding.'.

Проводя некоторые исследования, я видел комментарии, чтобы поиграть с параметрами NumericScale, Precision и Size объекта TParameter. TADOQuery автоматически установит параметры на 3, 23 и 16 соответственно и не будет иметь проблем со вставкой. Если для объекта TQuery установить одинаковые параметры, я получу ту же ошибку, что и выше.

Кто-нибудь имеет опыт работы с этим и знает, как легко обойти это? Я создал следующий пример кода для тех, кто хочет попробовать. Вам просто нужно изменить соединение и код SQL.

DateTimeParamTest_Main.dfm:

object Form10: TForm10
  Left = 0
  Top = 0
  Caption = 'Form10'
  ClientHeight = 111
  ClientWidth = 181
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Button2: TButton
    Left = 20
    Top = 16
    Width = 75
    Height = 25
    Caption = 'BDE'
    TabOrder = 0
    OnClick = Button2Click
  end
  object dbPMbde: TDatabase
    AliasName = 'PMTest'
    DatabaseName = 'DB'
    LoginPrompt = False
    SessionName = 'Default'
    Left = 20
    Top = 52
  end
  object qryBDE: TQuery
    DatabaseName = 'DB'
    SQL.Strings = (
      'INSERT INTO TRAN_DETAIL (ID, STARTDATE, ENDDATE)'
      'VALUES (:ID, :STARTDATE, :ENDDATE);')
    Left = 88
    Top = 52
    ParamData = <
      item
        DataType = ftInteger
        Name = 'ID'
        ParamType = ptInput
      end
      item
        DataType = ftDateTime
        Precision = 23
        NumericScale = 3
        Name = 'STARTDATE'
        ParamType = ptInput
        Size = 16
      end
      item
        DataType = ftDateTime
        Precision = 23
        NumericScale = 3
        Name = 'ENDDATE'
        ParamType = ptInput
        Size = 16
      end>
  end
end

DateTimeParamTest_Main.pas:

unit DateTimeParamTest_Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, DB, DBTables;

type
  TForm10 = class(TForm)
    Button2: TButton;
    dbPMbde: TDatabase;
    qryBDE: TQuery;
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form10: TForm10;

implementation

{$R *.dfm}

procedure TForm10.Button2Click(Sender: TObject);
begin
  dbPMbde.Open;
  with qryBDE do
  begin
    parambyname('ID').Value := 99999999;
    parambyname('StartDate').Value := now;
    parambyname('EndDate').Value := now;
    execsql;
  end;
  dbPMbde.Close;
end;

end.    

1 ответ

Решение

Это похоже на проблему с миллисекундами, вызывающую переполнение типа данных (на основе комментариев выше). Если так, то на ум приходит быстрое решение.

Замените присвоение столбцам даты следующим образом:

qryBDE.ParamByName('STARTDATE').AsDateTime := FixBDEDateTime(Now);

где FixBDEDateTime это просто

function FixBDEDateTime(const Value: TDateTime): TDateTime;
var
  Year, Mon, Day, Hr, Min, Sec, MS: Word;
begin
  DecodeDate(Value, Year, Mon, Day);
  DecodeTime(Value, Hr, Min, Sec, MS);
  Result := EncodeDate(Year, Mon, Day) + 
            EncodeTime(Hr, Min, Sec, 0);
end;

Редактировать: как @TLama указывает в комментариях (и я не упомянул, потому что я пропустил Delphi 2007 в открывшемся предложении и не увидел версию в тегах), вы также можете просто использовать:

uses
  DateUtils;
...

qryBDE.ParamByName('STARTDATE').AsDateTime := RecodeMillisecond(Now, 0);
Другие вопросы по тегам