TFDQuery、TFDStoredProc、TFDUpdateSQL に関する質問
FAQ(FireDAC) への移動
このトピックでは、TFDQuery、TFDStoredProc、TFDUpdateSQL に関係する質問と回答の一覧を扱います。
Q1: TFDQuery を使ってデータセット プロバイダに接続し、Embarcadero クライアント データセット内のデータを取得することはできますか。
A: TFDQuery は、TFDMemTable と TFDTableAdapter といくつかの TFDCommand を混ぜて 1 つにしたものです。そのため、TFDQuery 内部には、SQL コマンドの実行、パラメータ データの送信、結果セットの取得と格納、結果セットの閲覧、データベースへの変更のポストを行うために必要なものがすべて含まれています。TFDQuery と DSP と CDS を組み合わせて使用する理由はまったくありません。
TFDQuery の代わりに、TFDMemTable、TFDTableAdapte、TFDCommand を直接使用することができます。それによって柔軟性は高くなりますが、必要なコーディング量も増えます。たとえば、キャッシュされた更新を複数のデータセットにまたがって同期的に行うことを考えてください。
言い換えると、TFDQuery は、日常的なデータ アプリケーション プログラミングの最適な "ショートカット" なのです。
Q2: 手動で指定したパラメータを FDStoredProc に使わせるにはどうすればよいでしょうか。
A: FetchOptions.Items から fiMeta を削除してください。
パラメータを手動で作成した場合には、FetchOptions.Items から fiMeta を削除する必要があります。これを指定していると、FireDAC はストアド プロシージャ パラメータ定義をデータベースから取得し、それをもとに Params コレクションの内容を設定し直します。
パラメータの手動定義がわかりにくければ、Params コレクションの内容を自動で設定して、パラメータがどう定義されるかを確認してください。それをご自分のコードと比較してみてください。
Q3: '[FireDAC][Phys]-308. 結果セットを返さないコマンドを開いたり定義することはできません' および '[FireDAC][Phys]-310. 結果セットを返すコマンドを実行できません' という例外はどういう意味ですか。
A: '[FireDAC][Phys]-308. 結果セットを返さないコマンドを開いたり定義することはできません' の例外が発生するのは、結果セットを返さない SQL コマンドの Open メソッドをアプリケーションで実行している場合です。SQL コマンドが実行されたけれども、DBMS からは結果セットが何も返されなかったときに、この例外が発生します。
'[FireDAC][Phys]-310. 結果セットを返すコマンドを実行できません' の例外が発生するのは、結果セットを返す SQL コマンドの ExecSQL メソッドをアプリケーションで実行している場合です。コマンドが結果セットを返すかどうかは、FireDAC SQL コマンド プリプロセッサが判断します。コマンドが SELECT またはその形式のいずれかと認識されると結果セットが返され、そうでなければ返されません。
場合によって、FireDAC は SQL コマンドが結果セットを返すかどうかを判断しそこねます。また、アドホック アプリケーションで、返される結果セットの数に関係なく SQL コマンドを実行しなければならないときもあります。では、そのような場合にはどうすればよいでしょうか。次の 2 つの基本の方法があります。
1)
FDQuery1.OpenOrExecute;
このコードでは、内部的に [FireDAC][Phys]-308 が発生するかもしれませんが、例外は OpenOrExecute の外部に伝播せず、SQL コマンドは実際に実行されます。また、コマンドが結果セットを返すと、このメソッドは True を返します。
2)
FDQuery1.Command.CommandKind := skInsert;
FDQuery1.ExecSQL;
必要なのは、結果セットを返さないコマンドの種類(INSERT など)を FireDAC に知らせることだけです。
Q4: FDQuery.Execute(FDQuery.Params.ArraySize) を呼び出すと 'Out of memory'(メモリ不足)の例外が発生します。ArraySize は 90,000 程度です。どこが間違っているのでしょうか。
A: 1) どの DBMS の場合でも 90,000 は多すぎます。データが複数回キャッシュされることになるからです(パラメータ、DBMS API バッファ、ネットワーク パケット バッファなど)。また、それぞれのレコードのサイズも大きいのかもしれません。アプリケーションが、配列サイズの上限など、DBMS API の制限に達している可能性もあります。これは、Oracle の場合、最大で $7FFF です。他の DBMS の場合には、ネットワーク パケット サイズなどによって変わります。
2) 90,000 の項目を、500 ~ 5000 項目ずつのチャンクに分割します。これについては AD03-ArrayDML デモを参照してください。一般には、チャンク サイズまで配列の内容を設定し、チャンク サイズを渡して Execute を呼び出し、さらに設定して Execute を呼び出すことを繰り返します。
Firebird では、ArraySize を 1,000,000(60 秒)にしても問題ありません。
経験に基づく計算式が見つかっていて、配列の最大サイズを判断することができます。FireDAC では、1 つの大きい配列を複数のチャンクに自動的に分割します。
Oracle の場合、同様の式を見つけるのは簡単ではありません。最大 $7FFF までのサイズがサポートされていますが、配列が大きい場合にはアクセス違反などの問題がやはり発生します。
いずれにせよ、レコード サイズが大きすぎるのであれば、Execute を呼び出す前にでもメモリ不足に陥る可能性があります。
Q5: すべての FDQuery レコードの取得が済んだかどうかを知ることはできますか。
A: SourceEOF プロパティを確認してください。
Q6: レコードを(通常の SQL で)挿入して、IDENTITY/SEQUENCE を返してほしいと思っています。最も効率的で、複数のデータベースに適した方法はどのようなものでしょうか。
A: 1) TFDConnection に GetLastAutoGenValue というメソッドがあります。DBMS によって異なりますが、このメソッドはセッション内で最後に自動生成した値を返します。たとえば Oracle の場合には次のようにします。
SELECT <AName>.CURRVAL FROM dual
MySQL の場合は、SQL クエリを使わず、MYSQL API にアクセスして値を取得します。また、DBMS でシーケンスやジェネレータをサポートしていない場合には、AName パラメータの値はそのまま無視されます。
2) レコードを挿入して自動生成された値を返す SQL コマンドを作成する共通の方法はありません。たとえば Oracle の場合には次のようにします。
INSERT ... INTO ... RETURNING ID INTO :ID
PostgreSQL の場合には、2 つのコマンドに分かれます。
INSERT ... INTO ..
SELECT CURRVAL(...)
TDataSet の Insert/Post メソッドを使って挿入をデータベースにポストすると、FireDAC は DBMS の種類に合わせて効率的な SQL コマンドを生成します。
Q7: ユーザーが amCancelDialog モードでクエリ実行をキャンセルした後、トランザクションをロールバックするにはどうすればよいでしょうか。
A: 方法は 2 つあります。
1)
FDTransaction1.StartTransaction;
try
FDQuery1.ExecSQL;
FDTransaction1.Commit;
except
on E: EAbort do
// user canceled the command execution
ADTransaction1.Rollback;
end;
2) TFDQuery.OnError イベント ハンドラを作成します。コマンド実行がキャンセルされると、このイベント ハンドラが呼び出され、AException パラメータは次の条件を満たす状態になっています。
EFDDBEngineException(AException).Kind = ekCmdAborted;
Q8: '&' や '!' の文字を含むクエリが正しく実行されません。どこが間違っているのでしょうか。
A: たとえば、デフォルトのオプションで次のクエリを実行すると失敗します。
FDQuery1.SQL.Text := 'select * from xy where Fieldname = ''xxx&n''';
FDQuery1.open;
DBMS が受け取る SQL コマンドには '&n' が含まれません。これは、'&' がマクロ変数の始まりを示すためです。デフォルトでは、どの変数も値が空です。つまり、'&n' は空の文字列に置き換えられます。マクロを使用しないのであれば、ResourceOptions.MacroCreate および MacroExpand を False に設定してください。
Q9: ストアド プロシージャ パラメータにアクセスすると、'Parameter Xxx not found'(パラメータ Xxx が見つかりません)というエラーが出力されます。どこが間違っているのでしょうか。
A: たとえば、次のようなコードについて考えてみます。
FDStoredProc1.StoredProcName := 'TestProc';
FDStoredProc1.Prepare;
FDStoredProc1.ParamByName('Par').AsInteger := 100;
以下のようなさまざまな理由が考えられます。
- データベースに "TestProc" というストアド プロシージャがなく、作成しなければならない。
- 一部の DBMS で、"TestProc" が無効な状態なので、有効化しなければならない。
- 一部の DBMS で、ストアド プロシージャ名に大文字/小文字が混在しているため、引用符で囲まなければならない。
- fiMeta が FetchOptions.Items から削除されているため、追加するか、パラメータを手動で作成しなければならない。
- ストアド プロシージャに
Par
パラメータがないため、パラメータ名を修正するかPar
パラメータを追加しなければならない。 - 一部の DBMS で、パラメータ名の前に '@' が付いているか、ResourceOptions.UnifyParams が True に設定されている可能性がある。