3

鉴于以下

{------------------------------------------------------------------------------}
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;

但我收到错误消息:

MyProj.exe 项目引发异常类 EDatabaseError,并带有消息“参数 'SomeParam' 没有值”。

观察 Parameter[0] 显然表明该值已设置 - 参数名称与我预期的一样。谁能建议我做错了什么?

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

谢谢大家。

我发现了一些有趣的东西:

ParamByName('SomeParam').Value := 1234567;

产生相同的错误消息,而

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

才不是。

我使用 DBExpress 已经很多年了——我忘记了什么吗?

编辑

我想出了一个可行的方法,但对它不满意;通过检查 VALUE 的变体类型,我设法得到了一些结果

{------------------------------------------------------------------------------}
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() 方法。这绝不是经过彻底测试,但希望它会对某人有所帮助。

4

1 回答 1

3

没有简单的解决方案


使用 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

在运行时,两者都会产生带有 datatype 的参数ftUnknown

参数在私有方法中设置

// Delphi XE3
Data.SqlExpr.TCustomSQLDataSet.SetParameterFromSQL

在此方法中,表名被提取并且仅

if csDesigning in ComponentState then

创建一个临时数据集

SELECT * FROM <tablename> WHERE 0 = 1

根据该数据集中的字段名称检查每个参数,如果名称匹配,则设置参数数据类型。

ftUnknown这就是您在运行时获取参数的原因。

为了解决 Delphi 的问题,您尝试使用您的解决方案,但 dbexpress 有时会失败。参数设置器位于

Data.DB.TParam.SetAsVariant

并且 value1234567具有变体类型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 但更通用)

procedure SetParamValues( const AParams : TParams; const AValues : array of Variant );
var
  LIdx : Integer;
  LParam : TParam;
  LValue : Variant;
begin
  for LIdx := 0 to Pred( AParams.Count ) do
  begin
    LParam := AParams[LIdx];
    LValue := AValues[LIdx];
    // only handle the critical parts
    case VarType( LValue ) of
      varByte, varLongword : LParam.DataType := ftWideString;
    end;
    // all other will be set here
    LParam.Value := LValue;
  end;
end;

解决方案:艰难的方式

正如我首先所说,没有简单的解决方案。对于功能齐全的解决方案,您必须解析整个 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 ...


解决方案:昂贵的方式

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

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

于 2012-11-27T01:09:05.123 回答