Delphi Snappy DBExpress SQL执行器方法

Delphi Snappy DBExpress SQL执行器方法,delphi,dbexpress,Delphi,Dbexpress,鉴于以下情况 {------------------------------------------------------------------------------} function TTestClass.NewQuery(const ASql : String) : TSqlQuery; begin Result := TSqlQuery.Create(FConn); Result.SQLConnection := FConn; Result.Sql.Text := AS

鉴于以下情况

{------------------------------------------------------------------------------}
function TTestClass.NewQuery(const ASql : String) : TSqlQuery;
begin
  Result := TSqlQuery.Create(FConn);
  Result.SQLConnection := FConn;
  Result.Sql.Text := ASql;
  Result.Prepared := True;
end;

{------------------------------------------------------------------------------}
procedure TTestClass.ExecuteSql(const ASql : String);
begin
  with NewQuery(ASql) do
  try
    ExecSql();
  finally
    Free;
  end;
end;
如何创建将填充查询参数的
ExecSql
方法

我尝试了这个重载方法:

{------------------------------------------------------------------------------}
procedure TTestClass.ExecuteSql(const ASql : String; const ParamVals : Array Of Variant);
var
  i : integer;
  Qry : TSqlQuery;
begin
  Qry := NewQuery(ASql);
  with Qry do
  try
    for i := Low(ParamVals) to High(ParamVals) do
      Qry.Params[i].Value := ParamVals[i];
    ExecSql();
  finally
    Free;
  end;
end;
但我得到了错误信息:

Project MyProj.exe引发异常类EDatabaseError,消息为“参数“SomeParam”没有值”

对参数[0]进行监视显然表明该值已设置,并且参数名称与我预期的相同。有人能指出我做错了什么吗

我过去曾因使用“变体数组”而受到批评,我想知道是否有更好的方法

谢谢大家

我发现了一些有趣的事情:

ParamByName('SomeParam').Value := 1234567;
导致相同的错误消息,而

ParamByName('SomeParam').AsInteger := 1234567;
没有

我已经很多年没有使用DBExpress了-我忘记什么了吗

编辑

我想出了一个有效的方法,但我不满意;通过检查值的变量类型,我得到了一些结果

{------------------------------------------------------------------------------}
procedure TMyTestCase.SetParamValues(const AQuery : TSqlQuery; const ParamVals : Array Of Variant);
var
  i : Integer;
begin
  for i := 0 to AQuery.Params.Count - 1 do begin
    case VarType(ParamVals[i]) of
      varEmpty  :    AQuery.Params[i].AsInteger  := VarNull;       //The variant is Unassigned.
      varNull  :     AQuery.Params[i].AsInteger  := VarNull;       //The variant is Null.
      varAny  :      AQuery.Params[i].AsInteger  := VarNull;       //Represents a Variant that can hold any value.
      varSmallint  : AQuery.Params[i].AsInteger  := ParamVals[i];  //16-bit signed integer (type Smallint in Delphi, short in C++).
      varInteger  :  AQuery.Params[i].AsInteger  := ParamVals[i];  //32-bit signed integer (type Integer in Delphi, int in C++).
      varSingle  :   AQuery.Params[i].AsFloat    := ParamVals[i];  //Single-precision floating-point value (type Single in Delphi, float in C++).
      varDouble  :   AQuery.Params[i].AsFloat    := ParamVals[i];  //Double-precision floating-point value (type double).
      varCurrency  : AQuery.Params[i].AsFloat    := ParamVals[i];  //Currency floating-point value (type Currency).
      varDate  :     AQuery.Params[i].AsDateTime := ParamVals[i];  //Date and time value (type TDateTime).
      varOleStr  :   AQuery.Params[i].AsString   := ParamVals[i];  //Reference to a dynamically allocated UNICODE string.
      varDispatch  : AQuery.Params[i].AsInteger  := VarNull;       //Reference to an Automation object (an IDispatch interface pointer).
      varError  :    AQuery.Params[i].AsInteger  := VarNull;       //Operating system error code.
      varBoolean  :  AQuery.Params[i].AsBoolean  := ParamVals[i];  //16-bit Boolean (type WordBool).
      varVariant  :  AQuery.Params[i].AsInteger  := VarNull;       //Indicates another variant.
      varUnknown  :  AQuery.Params[i].AsInteger  := VarNull;       //Reference to an unknown object (an IInterface or IUnknown interface pointer).
      varShortInt  : AQuery.Params[i].AsInteger  := ParamVals[i];  //8-bit signed integer (type ShortInt in Delphi or signed char in C++).
      varByte  :     AQuery.Params[i].AsInteger  := ParamVals[i];  //A Byte.
      varWord  :     AQuery.Params[i].AsInteger  := ParamVals[i];  //Unsigned 16-bit value (Word).
      varLongWord  : AQuery.Params[i].AsInteger  := ParamVals[i];  //Unsigned 32-bit value (type LongWord in Delphi or unsigned long in C++).
      varInt64  :    AQuery.Params[i].AsInteger  := ParamVals[i];  //64-bit signed integer (Int64 in Delphi or __int64 in C++).
      varStrArg  :   AQuery.Params[i].AsString   := ParamVals[i];  //COM-compatible string.
      varString  :   AQuery.Params[i].AsString   := ParamVals[i];  //Reference to a dynamically allocated string (not COM-compatible).
      varArray  :    AQuery.Params[i].AsInteger  := VarNull;       //Indicates a Variant array.
      varByRef  :    AQuery.Params[i].AsInteger  := VarNull;       //Indicates that the variant contains a reference as opposed to a value.
      varTypeMask:   AQuery.Params[i].AsInteger  := VarNull;       //
    end;
  end;
end;
我肯定错过了一个步骤——为什么查询参数没有类型

再次编辑

我目前的“最佳”解决方案是依靠程序员提供正确的类型和数量的值。我已经在上面发布了完整的SetParamValues()方法。这并不是经过彻底测试的,但希望它能帮助某些人。

没有简单的解决方案
使用Delphi XE3和MySQL执行的所有测试


行为解释

在设计时,只要参数依赖于from表中不带别名的表字段,就可以获得正确的参数数据类型

SELECT id FROM items WHERE id = :id
但如果不这样做,它也会失败

SELECT id FROM items WHERE id/2 = :id
这也将失败

SELECT i.* FROM items i WHERE i.id = :id
在运行时,这两个参数都将产生数据类型为
ftUnknown
的参数

参数在私有方法中设置

// Delphi XE3
Data.SqlExpr.TCustomSQLDataSet.SetParameterFromSQL
在这个方法中,表名被提取出来,并且只有

if csDesigning in ComponentState then
使用创建临时数据集

SELECT * FROM <tablename> WHERE 0 = 1
1234567
具有变量类型
varLongword
,参数数据类型将设置为
ftLongword
,并导致此错误


解决方法

作为一种解决方法,您可以将参数数据类型设置为ftString/ftWideString,因为这在大多数情况下都有效

procedure TTestClass.ExecuteSql(const ASql : String; const ParamVals : Array Of Variant);
var
  i : integer;
  Qry : TSqlQuery;
begin
  Qry := NewQuery(ASql);
  with Qry do
  try
    for i := Low(ParamVals) to High(ParamVals) do
    begin
      Qry.Params[i].DataType := ftWideString;
      Qry.Params[i].Value := ParamVals[i];
    end;
    ExecSql();
  finally
    Free;
  end;
end;
为了获得更好的解决方案,您需要一个过程,将参数数据类型设置为
ftString
/
ftWidestring
,仅适用于关键变量类型(与方法SetParamValues类似,但更通用)


解决方案:艰难之路

正如我第一次说的,没有简单的解决办法。对于完整功能的解决方案,您必须解析整个WHERE语句

SELECT a.*, b*
FROM table1 a
JOIN table2 b ON a.id = b.id
WHERE a.id = :id AND b.count / 2 = :halfcount
并从中构建一个查询

SELECT a.id as Param1, b.count / 2 as Param2
FROM table1 a
JOIN table2 b ON a.id = b.id
WHERE 0 = 1
获取所需的数据类型


解决方案:漫长的道路

这是一个错误,应该向EMBA报告


解决方案:昂贵的方式

我用做了一个测试,一切都和dbExpress一样。参数数据类型为
ftUnknown
,设置参数值会将数据类型设置为
ftLongword


但有一种特殊情况:您不会得到错误,并且您的查询会按预期进行处理。

请显示ASql的值和查询的长度ParamVals@SirRufo-用例可能如下ExecuteSql('DELETE FROM SOMETABLE,其中ID=:ID',[1234567]);请注意,前两个方法按预期工作,并且参数是根据ASql的内容正确设置的。我也这样做,但我的参数赋值循环有点不同:对于I:=0到q.Params.Count-1到q.Params[I]。值:=Params[I]@我也试过了,结果是一样的。我还尝试了一种ParamByName()方法,这种方法有点愚蠢和复杂,没有解决问题。让我困惑的是,如果我使用NewQuery()然后在调用方法中填充参数,那么我就没有问题了。我想知道它是否与Oracle驱动程序有关???您使用什么RDBMS?我在不同的数据库中使用它,但我不确定是否与Oracle有任何联系。要进行快速测试,您可以尝试DevArt oracle驱动程序。这是一个非常好的答案,非常感谢。唯一的是,因为它是单元测试框架的一部分,所以大多数sql都是插入、更新和删除。这是因为它是关于为运行单元测试设置正确的条件,然后在之后进行整理。我倾向于同意你的观点,这是一个bug——当然是一个不受欢迎的‘特性’。@jachguate——你是否准备发布一些代码来演示如何解决这个问题?鉴于鲁福爵士的上述解释,我倾向于坚持我目前的解决方案。我对它的反对有点理论性(不是生产代码等),但在任何情况下都可能取得更好的结果。当然,最重要的“抓到”是TDateTime。为了解决这个问题,我发现最好的解决方案是在Db服务器上设置DateTime格式以匹配客户端。这一方针使国际问题消失。我很幸运能使用Oracle,但大概大多数数据库系统都有这个功能。看来赏金是你的了。谢谢你的帮助。
SELECT a.*, b*
FROM table1 a
JOIN table2 b ON a.id = b.id
WHERE a.id = :id AND b.count / 2 = :halfcount
SELECT a.id as Param1, b.count / 2 as Param2
FROM table1 a
JOIN table2 b ON a.id = b.id
WHERE 0 = 1