2012-03-01 106 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;