2012-03-01 109 views
2

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

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

+0

饶了自己很多麻烦和使用第三方商业部分的西装,只是我的两分钱... – ComputerSaysNo 2012-03-06 02:42:30

+0

只是一个具有讽刺意味地指出:在Delphi XE2(以及其他一些更新的版本),所有ADO组件都被标记为“dbGo”的标签,但是我没有发现任何内容(我知道)使用'GO'语句支持... – 2012-03-11 00:11:55

回答

6

我不使用dbExpress但据我所知,您可以执行(通过执行或ExecuteDirect)在同一时间只有一个SQL命令。换句话说,你不能把整个脚本放到Execute方法中。

这是不相关于由火鸟和MS SQL(^与GO)使用了不同的命令语法。您必须了解'^'符号或'GO'命令不是“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)。我不知道有什么可以指向你的。

旁注2:我敢肯定,你就必须修改脚本,使其与MS SQL完全兼容。尽管Firebird和MS SQL都是SQL服务器,但在DML/DDL语法中总是有所不同。

编辑:

  1. 如果你能 “改写” SQL脚本到代码,你可以用绝地VCL组件jvStringHolder。将每个单独的命令作为一个项目(类型TStrings)放入jvStringHolder

  2. 创建解析器是相当复杂的,但不能撤消。从SynEdit中获得灵感,我将这些类创建为您所需要的:使用TSQLScript.ParseScript加载脚本,然后遍历Command [index:integer]属性。SQLLexer不是完整的SQL Lexer,但是实现了与注释,括号,代码折叠等相关的关键字分离。我还在注释中添加了特殊的语法($注释块中的$),可以帮助我将标题放入脚本中。 这是从我的一个项目完整复制粘贴。我没有给出任何解释,但我希望你能明白这个想法,并让它在你的项目中运行。

unit 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; 
+0

有人能指点我一个很好的SQL脚本处理器吗?我不明白为什么embarcadero忘记执行这样一个重要的功能。 – cytrinox 2012-03-05 20:26:44

+0

第二条评论:编写解析器要复杂得多。对于GO关键字,您必须编写一个标记器并检查一些语法规则,以防止CREATE等错配。 - 我的评论或INSERT ...'GO FOO BAR'。你必须检查文字,注释等。脚本必须与相应的数据库实用程序兼容,所以用GO或其他东西代替GO不是解决方案。 – cytrinox 2012-03-05 20:39:33

+0

我也写过一个SQL解析器,就像其他人已经描述的一样。我在单行上使用'GO'来指示解析器执行自'last GO'以来的所有内容。你可以很容易地检查'GO',就像这样:'如果SameText(Trim(OneLineOfSQL),'GO'),那么ExecuteSQLStatement;'。 – 2012-03-08 02:40:01

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; 
+0

[.. 。]用于从SQL脚本文件创建表,视图和存储过程。你的回答对这个问题没有任何回应! – cytrinox 2012-03-01 15:37:32

+1

当然有。我的同事想知道哪个组件用来运行脚本和花了什么。 TSQLConnection是执行此操作的最佳组件。但是,您也可以使用TStoredProc来连接或运行脚本存储过程。 – user558516 2012-03-02 20:41:41

+0

如果我想使用“order by”子句,如何为第二个方案构造SQL语句? – Dev 2012-05-24 04:54:06

1

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

(我会很乐意在上面进行反驳,并在看到该解决方案很感兴趣......)

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

不像使用组件那样优雅,但如果它只是初始化逻辑,它可能是一个快速的,可接受的折中方案。我当然不会考虑上面的任何处理后初始化。

+0

我们公司发现从批处理文件中使用OSQL非常有用。这很容易,一个命令可能看起来像“OSQL -Usa -P MyPass -S MyServer -d MyDatabase -i MySQLScript.sql -o MyOutputFile.txt”(在MSSQL数据库上) – 2012-03-11 00:08:10

+0

嗨,杰里,是的,这正是我的指的是,除了SqlCmd已经取代了osql之外。 – Peter 2012-03-12 01:18:58

0

我不知道你需要多长时间才能创建这些表,但是如何将所有单独的SQL创建脚本放入表中并使用顺序/版本编号? 比你可以通过该表,并执行'一个一个。 您需要将脚本拆分一次,但之后更易于维护。

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;