我正在将旧的Delphi应用程序(使用ZeosDB)迁移到Delphi XE2。我想使用dbExpress作为ZeosDB替代Firebird 2.5或MS-SQL的数据库访问。 有很多sql脚本用于创建我需要运行的表,视图和存储过程。 Firebird脚本命令与^,MS-SQL脚本命令分开,带有“GO”。
如何使用dbexpress连接在数据库上运行这些脚本? ZeosDB提供了一个TZSqlProcessor,但我找不到dbExpress的任何等效组件。
答案 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语法总是存在差异。
修改强>
如果可以"重写"将SQL脚本放入代码中,可以使用Jedi VCL jvStringHolder组件。将每个单独的命令作为一个项目(TStrings类型)放在jvStringHolder。
创建解析器相当复杂,但不可撤消。借助SynEdit的灵感,我根据您的需要制作了这些内容:使用TSQLScript.ParseScript加载脚本,然后迭代Command [index:integer]属性。 SQLLexer不是完整的SQL Lexer,而是通过respec注释,括号,代码折叠等实现关键字分离。我还在注释中添加了一个特殊的语法($ sign in comment block),这有助于我将标题放入脚本中。 这是我的一个项目的完全复制粘贴。我没有给出任何解释,但我希望你能得到这个想法并让它在你的项目中运行。
单元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
。这个组件有两种方法Execute
和ExecuteDirect
。第一种方法不接受参数,但第二种方法不接受。
使用第一种方法:
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创建脚本放在表中,并使用顺序/版本编号? 你可以通过那张桌子逐一执行。 您需要将脚本拆分一次,但之后它可以更容易维护。