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は、メッセージ「パラメータ「SomeParam」の値がありません」で例外クラスEDatabaseErrorを発生させました。

Parameter [0]を監視すると、値が設定されていることがわかります。パラメーター名は、私が期待するとおりです。誰かが私が間違っていることを提案できますか?

そして、私は過去に「バリアントの配列」を使用したことで批判されてきました-もっと良い方法があるのではないかと思います。

皆さんありがとう。

私は何か面白いものを見つけました:

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

同じエラーメッセージが表示されますが、

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

ではない。

DBExpressを使ってから何年も経ちますが、何か忘れてしまいましたか?

編集

私はうまくいく方法を思いついたが、それに満足していない。VALUEのVariantTypeを調べることで、いくつかの結果を得ることができました。

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

これに対する簡単な解決策はありません


DelphiXE3および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

すべてのパラメーターがこのデータセットのフィールド名と照合され、名前がパラメーターと一致する場合はデータ型が設定されます。

ftUnknownこれが、実行時にパラメーターを取得する理由です。

これを解決するために、Delphiはソリューションで試したのと同じことを行いますが、dbexpressが失敗することがあります。パラメータのセッターは次の場所にあります。

Data.DB.TParam.SetAsVariant

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のように、より一般的です)

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

ただし、1つの特殊なケースがあります。エラーが発生せず、クエリが期待どおりに処理されます。

于 2012-11-27T01:09:05.123 に答える