如何使用dbExpress执行SQL脚本?

时间:2012-03-01 14:09:49

标签: delphi dbexpress

我正在将旧的Delphi应用程序(使用ZeosDB)迁移到Delphi XE2。我想使用dbExpress作为ZeosDB替代Firebird 2.5或MS-SQL的数据库访问。 有很多sql脚本用于创建我需要运行的表,视图和存储过程。 Firebird脚本命令与^,MS-SQL脚本命令分开,带有“GO”。

如何使用dbexpress连接在数据库上运行这些脚本? ZeosDB提供了一个TZSqlProcessor,但我找不到dbExpress的任何等效组件。

5 个答案:

答案 0 :(得分:6)

我不使用DBExpress,但据我所知,您可以一次只执行一个SQL命令(通过Execute或ExecuteDirect)。换句话说,您不能将整个脚本放入Execute方法中。

这与FireBird和MS SQL(^与GO)使用的不同命令语法无关。你必须要理解' ^'签署或“去”#39;命令不是" TSQL命令"!两者都是用于执行针对SQL引擎的命令的相应应用程序使用的特定命令分隔符。相反,它是" Firebird Manager" (或者它是如何调用的)和" SQL Query Profiler" (或" SQL Server Management Studio")。

解决方案是使用某种解析器,将脚本拆分为单个命令列表,并且TSQLConnection.Execute这些命令一个接一个。

像这样的伪代码:

var
  DelimiterPos: Integer;
  S: String;
  Command: String;
begin
  S:= ScriptFile; // ScriptFile: String - your whole script
  While True Do
  begin
    DelimiterPos:= Pos('^', ScriptFile);
    if DelimiterPos = 0 then DelimiterPos:= Length(S);
    Command:= Copy(S, 1, DelimiterPos - 1);
    SQLConnection.Execute(Command);
    Delete(S, 1, DelimiterPos);
    if Lengh(S) = 0 Then Exit;
  end;
end;

请注意,上述示例仅适用于' ^'标志不会在脚本中的任何位置使用,而是在命令分隔符中使用。

作为旁注,我确信有一些已经构建的组件可以为你做这些(比如TZSQLProcessor)。我不知道有任何指向你。

Sidenote 2:我很确定,您必须修改脚本才能与MS SQL完全兼容。虽然Firebird和MS SQL都是SQL服务器,但DML / DDL语法总是存在差异。

修改

  1. 如果可以"重写"将SQL脚本放入代码中,可以使用Jedi VCL jvStringHolder组件。将每个单独的命令作为一个项目(TStrings类型)放在jvStringHolder

  2. 创建解析器相当复杂,但不可撤消。借助SynEdit的灵感,我根据您的需要制作了这些内容:使用TSQLScript.ParseScript加载脚本,然后迭代Command [index:integer]属性。 SQLLexer不是完整的SQL Lexer,而是通过respec注释,括号,代码折叠等实现关键字分离。我还在注释中添加了一个特殊的语法($ sign in comment block),这有助于我将标题放入脚本中。 这是我的一个项目的完全复制粘贴。我没有给出任何解释,但我希望你能得到这个想法并让它在你的项目中运行。

  3. 单元SQLParser;

    interface
    
    type
    
      TTokenKind = (tkUknown, tkEOF, tkComment, tkKeyword, tkIdentifier,
                    tkCommentParam, tkCommentParamValue, tkCommandEnd, tkCRLF);
    
      TBlockKind = (bkNone, bkLineComment, bkBlockComment);
    
      TSQLLexer = class
      private
        FBlockKind: TBlockKind;
        FParseString: String;
        FPosition: PChar;
        FTokenKind: TTokenKind;
        FTokenPosition: PChar;
        function GetToken: String;
        procedure Reset;
        procedure SetParseString(Value: String);
      protected
        procedure ReadComment;
        procedure ReadCommentParam;
        procedure ReadCommentParamValue;
        procedure ReadCRLF;
        procedure ReadIdentifier;
        procedure ReadSpace;
      public
        constructor Create(ParseString: String);
        function NextToken: TTokenKind;
    
        property Position: PChar read FPosition;
        property SQLText: String read FParseString write SetParseString;
        property Token: String read GetToken;
        property TokenKind: TTokenKind read FTokenKind;
        property TokenPosition: PChar read FTokenPosition;
      end;
    
    
    
    implementation
    
    uses SysUtils;
    
    { TSQLLexer }
    
    constructor TSQLLexer.Create(ParseString: string);
    begin
      inherited Create;
      FParseString:= ParseString;
      Reset;
    end;
    
    function TSQLLexer.GetToken;
    begin
      SetString(Result, FTokenPosition, FPosition - FTokenPosition);
    end;
    
    function TSQLLexer.NextToken: TTokenKind;
    begin
      case FBlockKind of
        bkLineComment, bkBlockComment: ReadComment;
        else
          case FPosition^ of
          #0: FTokenKind:= tkEOF;
          #1..#9, #11, #12, #14..#32:
            begin
              ReadSpace;
              NextToken;
            end;
          #10, #13: ReadCRLF;
          '-':
            if PChar(FPosition +1)^ = '-' then
              ReadComment
            else
              Inc(FPosition);
          '/':
            if PChar(FPosition +1)^ = '*' then
              ReadComment
            else
              Inc(FPosition);
          'a'..'z', 'A'..'Z': ReadIdentifier;
          ';':
            begin
              FTokenPosition:= FPosition;
              Inc(FPosition);
              FTokenKind:= tkCommandEnd;
            end
          else
            Inc(FPosition);
          end;
      end;
      Result:= FTokenKind;
    end;
    
    
    procedure TSQLLexer.ReadComment;
    begin
      FTokenPosition:= FPosition;
      if not (FBlockKind in [bkLineComment, bkBlockComment])  then
      begin
        if FPosition^ = '/' then
          FBlockKind:= bkBlockComment
        else
          FBlockKind:= bkLineComment;
        Inc(FPosition, 2);
      end;
      case FPosition^ of
        '$': ReadCommentParam;
        ':': ReadCommentParamValue;
      else
        while not CharInSet(FPosition^, [#0, '$']) do
        begin
          if FBlockKind = bkBlockComment then
          begin
            if (FPosition^ = '*') And (PChar(FPosition + 1)^ = '/') then
            begin
              Inc(FPosition, 2);
              FBlockKind:= bkNone;
              Break;
            end;
          end
          else
          begin
            if CharInSet(Fposition^, [#10, #13]) then
            begin
              ReadCRLF;
              FBlockKind:= bkNone;
              Break;
            end;
          end;
          Inc(FPosition);
        end;
        FTokenKind:= tkComment;
      end;
    end;
    
    procedure TSQLLexer.ReadCommentParam;
    begin
      Inc(FPosition);
      ReadIdentifier;
      FTokenKind:= tkCommentParam;
    end;
    
    procedure TSQLLexer.ReadCommentParamValue;
    begin
      Inc(FPosition);
      ReadSpace;
      FTokenPosition:= FPosition;
      while not CharInSet(FPosition^, [#0, #10, #13]) do
        Inc(FPosition);
      FTokenKind:= tkCommentParamValue;
    end;
    
    procedure TSQLLexer.ReadCRLF;
    begin
      while CharInSet(FPosition^, [#10, #13]) do
        Inc(FPosition);
      FTokenKind:= tkCRLF;
    end;
    
    procedure TSQLLexer.ReadIdentifier;
    begin
      FTokenPosition:= FPosition;
      while CharInSet(FPosition^, ['a'..'z', 'A'..'Z', '_']) do
        Inc(FPosition);
    
      FTokenKind:= tkIdentifier;
    
      if Token = 'GO' then
        FTokenKind:= tkKeyword;
    end;
    
    procedure TSQLLexer.ReadSpace;
    begin
      while CharInSet(FPosition^, [#1..#9, #11, #12, #14..#32]) do
      Inc(FPosition);
    end;
    
    procedure TSQLLexer.Reset;
    begin
      FTokenPosition:= PChar(FParseString);
      FPosition:= FTokenPosition;
      FTokenKind:= tkUknown;
      FBlockKind:= bkNone;
    end;
    
    procedure TSQLLexer.SetParseString(Value: String);
    begin
      FParseString:= Value;
      Reset;
    end;
    
    end.
    

    解析器:

    type
      TScriptCommand = class
      private
        FCommandText: String;
      public
        constructor Create(ACommand: String);
        property CommandText: String read FCommandText write FCommandText;
      end;
    
      TSQLScript = class
      private
        FCommands: TStringList;
        function GetCount: Integer;
        function GetCommandList: TStrings;
        function GetCommand(index: Integer): TScriptCommand;
      protected
        procedure AddCommand(AName: String; ACommand: String);
      public
        Constructor Create;
        Destructor Destroy; override;
        procedure ParseScript(Script: TStrings);
    
        property Count: Integer read GetCount;
        property CommandList: TStrings read GetCommandList;
        property Command[index: integer]: TScriptCommand read GetCommand;
      end;
    
    { TSQLScriptCommand }
    
    constructor TScriptCommand.Create(ACommand: string);
    begin
      inherited Create;
      FCommandText:= ACommand;
    end;
    
    { TSQLSCript }
    
    constructor TSQLScript.Create;
    begin
      inherited;
      FCommands:= TStringList.Create(True);
      FCommands.Duplicates:= dupIgnore;
      FCommands.Sorted:= False;
    end;
    
    destructor TSQLScript.Destroy;
    begin
      FCommands.Free;
      inherited;
    end;
    
    procedure TSQLScript.AddCommand(AName, ACommand: String);
    var
      ScriptCommand: TScriptCommand;
      S: String;
    begin
      if AName = '' then
        S:= SUnnamedCommand
      else
        S:= AName;
      ScriptCommand:= TScriptCommand.Create(ACommand);
      FCommands.AddObject(S, ScriptCommand);
    end;
    
    function TSQLScript.GetCommand(index: Integer): TScriptCommand;
    begin
      Result:= TScriptCommand(FCommands.Objects[index]);
    end;
    
    function TSQLScript.GetCommandList: TStrings;
    begin
      Result:= FCommands;
    end;
    
    function TSQLScript.GetCount: Integer;
    begin
      Result:= FCommands.Count;
    end;
    
    procedure TSQLScript.ParseScript(Script: TStrings);
    var
      Title: String;
      Command: String;
      LastParam: String;
      LineParser: TSQLLexer;
      IsNewLine: Boolean;
      LastPos: PChar;
    
      procedure AppendCommand;
      var
        S: String;
      begin
        SetString(S, LastPos, LineParser.Position - LastPos);
        Command:= Command + S;
        LastPos:= LineParser.Position;
      end;
    
      procedure FinishCommand;
      begin
        if Command <> '' then
          AddCommand(Title, Command);
        Title:= '';
        Command:= '';
        LastPos:= LineParser.Position;
        if LastPos^ = ';' then Inc(LastPos);
      end;
    
    begin
      LineParser:= TSQLLexer.Create(Script.Text);
      try
        LastPos:= LineParser.Position;
        IsNewLine:= True;
        repeat
          LineParser.NextToken;
          case LineParser.TokenKind of
            tkComment: LastPos:= LineParser.Position;
            tkCommentParam:
              begin
                LastParam:= UpperCase(LineParser.Token);
                LastPos:= LineParser.Position;
              end;
            tkCommentParamValue:
              if LastParam = 'TITLE' then
              begin
                Title:= LineParser.Token;
                LastParam:= '';
                LastPos:= LineParser.Position;
              end;
            tkKeyword:
                if (LineParser.Token = 'GO') and IsNewLine then FinishCommand
                else
                  AppendCommand;
            tkEOF:
              FinishCommand;
            else
              AppendCommand;
          end;
          IsNewLine:= LineParser.TokenKind in [tkCRLF, tkCommandEnd];
        until LineParser.TokenKind = tkEOF;
      finally
        LineParser.Free;
      end;
    end;
    

答案 1 :(得分:3)

您需要使用TSQLConnection。这个组件有两种方法ExecuteExecuteDirect。第一种方法不接受参数,但第二种方法不接受。

使用第一种方法:

procedure TForm1.Button1Click(Sender: TObject);
var
   MeuSQL: String;
begin
   MeuSQL := 'INSERT INTO YOUR_TABLE ('FIELD1', 'FIELD2') VALUES ('VALUE1', 'VALUE2')';
   SQLConnection.ExecuteDirect(MeuSQL);
end;

如果需要,您可以使用交易。

使用第二种方法:

procedure TForm1.Button1Click(Sender: TObject);
var
  MySQL: string;
  MyParams: TParams;
begin
  MySQL := 'INSERT INTO TABLE ("FIELD1", "FIELD2") VALUE (:PARAM1, :PARAM2)';
  MyParams.Create;
  MyParams.CreateParam(ftString, 'PARAM1', ptInput).Value := 'Seu valor1';
  MyParams.CreateParam(ftString, 'PARAM2', ptInput).Value := 'Seu valor2';

  SQLConnection1.Execute(MySQL,MyParams, Nil);
end;

答案 2 :(得分:1)

我大约90%肯定你不能,至少没有解析GO之间的各个命令,然后连续执行它们,正如你已经指出的那样,这是有问题的。

(我很乐意对上述内容进行反驳,并且非常有兴趣看到解决方案......)

如果您只是将脚本用作初始化逻辑(例如创建表等),您可以考虑的另一个解决方案是在批处理文件中触发脚本并通过'Sqlcmd'执行它们,这可以执行通过你的delphi应用程序(使用ShellExecute),然后在继续之前等待它完成。

不如使用组件那么优雅,但如果仅用于初始化逻辑,则可能是一种快速,可接受的折衷方案。我当然不会考虑上述任何处理后初始化。

答案 3 :(得分:1)

这似乎不是dbExpress限制,而是SQL语言限制。我不确定T-SQL,但看起来GO类似于Oracle PL / SQL中的匿名块。您可以将以下PL / SQL代码放在TSqlDataSet.CommandText中,并调用ExecSQL来创建多个表。也许T-SQL有类似的方法:

begin
execute immediate 'CREATE TABLE suppliers 
( supplier_id number(10) not null, 
  supplier_name varchar2(50) not null, 
  contact_name varchar2(50)  
)'; 
execute immediate 'CREATE TABLE customers 
( customer_id number(10) not null, 
  customer_name varchar2(50) not null, 
  address varchar2(50),  
  city varchar2(50),  
  state varchar2(25),  
  zip_code varchar2(10),  
  CONSTRAINT customers_pk PRIMARY KEY (customer_id) 
)';
end;

答案 4 :(得分:0)

我不知道您需要多长时间创建这些表,但是如何将所有单独的SQL创建脚本放在表中,并使用顺序/版本编号? 你可以通过那张桌子逐一执行。 您需要将脚本拆分一次,但之后它可以更容易维护。