はじめに
クラスクエリ InterSystemsCachéは、COSコードで直接SQLクエリを抽象化するために使用される便利なツールです。 最も単純なケースでは、次のようになります。同じSQLクエリを複数の場所で使用しますが、引数が異なるとします。
毎回書かないようにするために、要求テキストをクラス要求として指定し、将来この名前を名前で参照できます。 また、結果の次の行を自分で取得するためのロジックを記述するカスタムクエリもあります。 カットの下で、すべてを使用する方法についてお話します。
基本クラスクエリ
したがって、基本クラスクエリは、SELECT SQLクエリを表すためのメソッドです。 通常のSQLクエリと同様に、SQLオプティマイザーとコンパイラーによって処理されますが、COSコンテキスト内から呼び出す方が簡単です。 クラス定義では、これらはクエリタイプの要素です(たとえば、メソッドやプロパティに似ています)。 それらは次のように定義されます。
- タイプ- %SQLQuery
- 引数のリストで、SQLクエリ引数のリストをリストする必要があります
- リクエストのタイプ-SELECT
- 引数はコロンを介してアクセスされます(静的SQLと同様)
- ROWSPECパラメーターを定義します-返される結果の名前とデータ型、およびフィールドの順序に関する情報が含まれます
- (オプション)IDを含むフィールドのシリアル番号と等しいCONTAINIDパラメーターを定義します。 Idが返されない場合、CONTAINIDは不要です
- (オプション)COMPILEMODEパラメーターを定義します。 これは、静的SQLの同じパラメーターに似ており、SQL式のコンパイル時期を決定します。 IMMEDIATEが等しい場合(デフォルト)、クラスのコンパイル中にコンパイルが行われます。 DYNAMICと等しい場合、動的SQLと同様に、クエリの最初の実行前にコンパイルが行われます
- (オプション)SELECTMODEパラメーターの定義-クエリ結果の形式の宣言
- このクエリをSQLプロシージャとして呼び出す場合は、SqlProcプロパティを追加します
- クエリの名前を変更する場合は、SqlNameプロパティを設定します。 デフォルトでは、SQLコンテキストのクエリ名:PackageName.ClassName_QueryName
- Cachéスタジオは、クラス要求ウィザードを提供します
特定の文字で始まる名前を持つすべての人を返すByNameリクエストを含むSample.Personクラスの定義の例
クラスSample.Person Extends%Persistent
{
プロパティ 名 As%String ;
プロパティ DOB As%Date ;
プロパティ SSN As%String ;
クエリ ByName( name As%String = "" ) As%SQLQuery
( ROWSPEC = "ID:%整数、名前:%文字列、DOB:%日付、SSN:%文字列" 、
CONTAINID = 1 、 SELECTMODE = "RUNTIME" 、
COMPILEMODE = "IMMEDIATE" )[ SqlName = SP_Sample_By_Name 、 SqlProc ]
{
SELECT ID 、 Name 、 DOB 、 SSN
FROM サンプル 人
WHERE ( 名前 %STARTSWITH :name )
名前で 並べ替え
}
}
{
プロパティ 名 As%String ;
プロパティ DOB As%Date ;
プロパティ SSN As%String ;
クエリ ByName( name As%String = "" ) As%SQLQuery
( ROWSPEC = "ID:%整数、名前:%文字列、DOB:%日付、SSN:%文字列" 、
CONTAINID = 1 、 SELECTMODE = "RUNTIME" 、
COMPILEMODE = "IMMEDIATE" )[ SqlName = SP_Sample_By_Name 、 SqlProc ]
{
SELECT ID 、 Name 、 DOB 、 SSN
FROM サンプル 人
WHERE ( 名前 %STARTSWITH :name )
名前で 並べ替え
}
}
次のように、COSコンテキストからこの要求を使用できます。
Set ステートメント = ##クラス ( %SQL.Statement )。 %新規 ()
status = statementを 設定し ます 。 %PrepareClassQuery ( " Sample.Person " 、 "ByName" )
$$$ ISERR ( ステータス ) { Do $ system .OBJ 。 DisplayError ( status ) }
resultset = statementを 設定し ます。 %実行 ( "A" )
結果セット中 。 %Next () {
!、 Resultsetを記述し ます。 %Get ( "名前" )
}
または、生成されたqueryNameFuncメソッドを使用して、すぐに結果セットを取得します。
結果セット = ##クラス ( Sample.Person )を設定します。 ByNameFunc ( "A" )
結果セット中 。 %Next () {
!、 Resultsetを記述し ます。 %Get ( "名前" )
}
さらに、このクエリは、次の2つの方法でSQLコンテキストから呼び出すことができます。
Call Sample.SP_Sample_By_Name('A')
Select * from Sample.SP_Sample_By_Name('A')
このクラスは、Cachéに付属のSAMPLESエリアにあります。 それはすべて単純なクエリについてです。 それでは、カスタムクエリに移りましょう。
カスタムクラスリクエスト
ほとんどの場合、基本的なクラスクエリで十分です。 ただし、特に、アプリケーションが要求の動作を完全に制御する必要がある場合があります。
- どのレコードを結果に含めるかを決定する複雑なロジック。 カスタムリクエストでは、COSで次のクエリ結果を生成するメソッドを自分で記述するため、このロジックは任意に複雑になる可能性があります
- フォーマットが適切でないAPIを介してデータにアクセスする場合
- データがグローバルに保存されている場合、クラスはありません
- データへのアクセスに権利のエスカレーションが必要な場合
- データにアクセスするために外部APIをリクエストする必要がある場合
- データにアクセスするためにファイルシステムにアクセスする必要がある場合
- 要求自体を実行する前に、いくつかの追加操作が必要です(接続の確立、権限の確認など)
それでは、カスタムクラスクエリはどのように記述されますか? queryNameクエリを作成するには、作成から破棄まで、クエリのロジック全体を実装する4つのメソッドを定義します。
- queryName-クラスの基本的なリクエストと同様に、リクエストに関する情報を提供します
- queryName Execute-クエリを初期化します
- queryName Fetch-次の結果を取得します
- queryName Close-クエリデストラクタ
次に、これらのメソッドについて詳しく説明します。
QueryNameメソッド
queryNameメソッドは、クエリに関する情報を提供します。
- タイプ-%クエリ
- 定義を空白のままにします。
- ROWSPECパラメーターを定義します-返される結果の名前とデータ型、およびフィールドの順序に関する情報が含まれます
- (オプション)IDを含むフィールドのシリアル番号と等しいCONTAINIDパラメーターを定義します。 Idが返されない場合、CONTAINIDは不要です
例として、AllRecordsリクエスト(QueryName = AllRecords、メソッドは単にAllRecordsと呼ばれます)を作成します。これは、保存されたクラスのすべてのレコードを返します。
まず、新しいストアドクラスUtils.CustomQueryを作成します。
クラスUtils.CustomQuery Extends ( %Persistent 、 %Populate )
{
プロパティ Prop1 As%String ;
プロパティ Prop2 As%Integer ;
}
次に、AllRecordsリクエストの説明を記述します。
Query AllRecords() As%Query ( CONTAINID = 1 、 ROWSPEC = "Id:%String、Prop1:%String、Prop2:%Integer" )[ SqlName = AllRecords 、 SqlProc ]
{
}
QueryNameExecuteメソッド
queryNameExecuteメソッドは、必要なすべてのクエリの初期化を実行します。 次の署名が必要です。
ClassMethod queryNameExecute( ByRef qHandle As%Binary 、 args ) As%ステータス
どこで:
- qHandleは、他のクエリ実装メソッドと通信するために使用されます。
- このメソッドは、qHandleをqueryNameFetchメソッドが入力として受け取る状態にする必要があります
- qHandleは、OREF、変数、または多次元にすることができます
- argsは、リクエストに渡される追加のパラメーターです。 それらはarbitrarily意的に多くてもまったくなくてもかまいません
- リクエストの初期化ステータスが返されます
例に戻りましょう。 範囲を回避するための多くのオプションがあります(カスタムクエリを整理するための主なアプローチについては後述します)。 $ Order関数を使用してグローバルな回避を使用することをお勧めします。 qHandleはそれに応じて現在のID、この場合は空の文字列を格納します。 追加の引数は必要ないため、argは使用されません。 結果は次のとおりです。
ClassMethod AllRecordsExecute( ByRef qHandle As%Binary ) As%ステータス
{
qHandle = ""に 設定
$$$ OKを終了OK
}
QueryNameFetchメソッド
queryNameFetchメソッドは、 $ Listの形式で単一の結果を返します。 次の署名が必要です。
ClassMethod queryNameFetch( ByRef qHandle As%Binary 、
ByRef Row As%List 、
ByRef AtEnd As%Integer = 0 ) As%Status [ PlaceAfter = queryNameExecute]
どこで:
- qHandleは、他のクエリ実装メソッドと通信するために使用されます。
- クエリを実行するとき、qHandleはqueryNameExecuteまたは以前のqueryNameFetchの呼び出しによって設定された値を受け入れます
- 行は%List形式の値を受け入れるか、データがもうない場合は空の文字列と等しくなければなりません
- データの最後に到達するとき、AtEndは1でなければなりません
- PlaceAfterキーワードは、intコード内のメソッドの位置を決定します(ハブ上でintコードのコンパイルと生成に関する記事があります)。Fetchメソッドは、Executeメソッドの後に配置する必要があります。これは、 静的SQLを使用する場合、またはクエリ内でカーソルを使用する場合にのみ重要です。
このメソッド内では、一般に、次の操作が実行されます。
- データの終わりに達したかどうかを判別します。
- まだデータがある場合:Create%ListおよびRow変数の値を設定します
- それ以外の場合は、AtEndを1に設定します
- 後続の呼び出しにqHandleを設定します
- ステータスを返す
この例では、次のようになります。
ClassMethod AllRecordsFetch( ByRef qHandle As%Binary 、 ByRef Row As%List 、 ByRef AtEnd As%Integer = 0 ) As%Status
{
#; グローバル化^ Utils.CustomQueryD
#; 次のidをqHandleに書き込み、グローバルの値をvalに新しいidで書き込みます
qHandle = $ Orderに 設定 (^ Utils.CustomQueryD( qHandle )、1、 val )
#; データが最後に達したかどうかを確認します
qHandle = ""の 場合 {
AtEnd = 1に 設定
行を 設定 = ""
$$$ OKを終了OK
}
#; そうでない場合、form%List
#; val = $ Lb( ""、Prop1、Prop2)-ストレージ定義を参照
#; 行= $ Lb(Id、Prop1、Prop2)-ROWSPECリクエストAllRecordsを参照
行の 設定 = $ Lb ( qHandle 、 $ Lg ( val 、2)、 $ Lg ( val 、3))
$$$ OKを終了OK
}
QueryNameCloseメソッド
queryNameCloseメソッドは、すべてのデータを受信した後、クエリを終了します。 次の署名が必要です。
ClassMethod queryNameClose( ByRef qHandle As%Binary ) As%Status [ PlaceAfter = queryNameFetch]
どこで:
- Cachéは、queryNameFetchメソッドの最後の呼び出し後にこのメソッドを実行します
- このメソッドはリクエストデストラクタです
- このメソッドの実装では、使用されたSQLカーソル、クエリを閉じ、ローカル変数を削除します
- メソッドがステータスを返します
この例では、ローカルのqHandle変数を削除する必要があります。
ClassMethod AllRecordsClose( ByRef qHandle As%Binary ) As%ステータス
{
qhandleを殺す
$$$ OKを終了OK
}
以上です。 クラスをコンパイルした後、AllRecordsクエリは基本クラスクエリと同様に使用できます-using%SQL.Statement。
カスタムクエリロジック
それでは、カスタムクエリロジックをどのように整理できますか? 3つの主なアプローチがあります。
グローバルバイパス
アプローチは、$ Order関数などを使用してグローバルをバイパスすることです。 次の場合に使用する必要があります。
- データはクラスなしでグローバルに保存されます
- glorefの数を減らす必要があります-グローバルへの呼び出し
- 結果は、グローバルキーでソートする必要があります。
静的SQL
アプローチは、カーソルと静的SQLを使用することです。 これは次の目的で実行できます。
- intコードの読み取りを簡素化する
- カーソルの単純化
- コンパイル時間の短縮(静的SQLはクラス要求に配置され、一度だけコンパイルされます)
機能:
- %SQLQueryなどのクエリから生成されたカーソルは、たとえばQ14のように自動的に名前が付けられます。
- クラス内で使用されるすべてのカーソルには、異なる名前が必要です。
- エラーメッセージは、名前の末尾に余分な文字があるカーソルの内部名を指します。 たとえば、Q140カーソルのエラーは、おそらくQ14カーソルに関連しています
- PlaceAfterを使用して、カーソルの宣言と使用が1つのintプログラムで発生することを確認します
- INTOは、DECLAREではなくFETCHで配置する必要があります
Utils.CustomQueryの静的SQLの例
Query AllStatic() As%Query ( CONTAINID = 1 、 ROWSPEC = "Id:%String、Prop1:%String、Prop2:%Integer" )[ SqlName = AllStatic 、 SqlProc ]
{
}
ClassMethod AllStaticExecute( ByRef qHandle As%Binary ) As%ステータス
{
&sql( DECLARE C CURSOR FOR
SELECT ID 、 Prop1 、 Prop2
FROM Utils 。 カスタムクエリ
)
&sql( OPEN C )
$$$ OKを終了OK
}
ClassMethod AllStaticFetch( ByRef qHandle As%Binary 、 ByRef Row As%List 、 ByRef AtEnd As%Integer = 0 ) As%Status [ PlaceAfter = AllStaticExecute]
{
#; INTOはFETCHを使用する必要があります
&sql( FETCH C INTO :Id 、: Prop1 ,: Prop2 )
#; データが最後に達したかどうかを確認します
If ( SQLCODE '= 0) {
AtEnd = 1に 設定
行を 設定 = ""
$$$ OKを終了OK
}
行の 設定 = $ Lb ( Id 、 Prop1 、 Prop2 )
$$$ OKを終了OK
}
ClassMethod AllStaticClose( ByRef qHandle As%Binary ) As%Status [ PlaceAfter = AllStaticFetch]
{
&sql( CLOSE C )
$$$ OKを終了OK
}
{
}
ClassMethod AllStaticExecute( ByRef qHandle As%Binary ) As%ステータス
{
&sql( DECLARE C CURSOR FOR
SELECT ID 、 Prop1 、 Prop2
FROM Utils 。 カスタムクエリ
)
&sql( OPEN C )
$$$ OKを終了OK
}
ClassMethod AllStaticFetch( ByRef qHandle As%Binary 、 ByRef Row As%List 、 ByRef AtEnd As%Integer = 0 ) As%Status [ PlaceAfter = AllStaticExecute]
{
#; INTOはFETCHを使用する必要があります
&sql( FETCH C INTO :Id 、: Prop1 ,: Prop2 )
#; データが最後に達したかどうかを確認します
If ( SQLCODE '= 0) {
AtEnd = 1に 設定
行を 設定 = ""
$$$ OKを終了OK
}
行の 設定 = $ Lb ( Id 、 Prop1 、 Prop2 )
$$$ OKを終了OK
}
ClassMethod AllStaticClose( ByRef qHandle As%Binary ) As%Status [ PlaceAfter = AllStaticFetch]
{
&sql( CLOSE C )
$$$ OKを終了OK
}
動的SQL
アプローチは、他のクラスクエリと動的SQLを使用することです。 SQLの形式で表すことができる実際のクエリに加えて、いくつかの追加アクションを実行する必要がある場合、たとえば、SQLクエリを一度に1つずついくつかの領域で実行する必要がある場合に関連します。 または、リクエストを実行する前に、権利のエスカレーションが必要です。
Utils.CustomQueryの動的SQLの例
Query AllDynamic() As%Query ( CONTAINID = 1 、 ROWSPEC = "Id:%String、Prop1:%String、Prop2:%Integer" )[ SqlName = AllDynamic 、 SqlProc ]
{
}
ClassMethod AllDynamicExecute( ByRef qHandle As%Binary ) As%ステータス
{
qHandle = ## class ( %SQL.Statement )を設定します。 %ExecDirect (、 "SELECT * FROM Utils.CustomQuery" )
$$$ OKを終了OK
}
ClassMethod AllDynamicFetch( ByRef qHandle As%Binary 、 ByRef Row As%List 、 ByRef AtEnd As%Integer = 0 ) As%ステータス
{
qHandleの 場合 。 %Next ()= 0 {
AtEnd = 1に 設定
行を 設定 = ""
$$$ OKを終了OK
}
Set Row = $ Lb ( qHandle 。 %Get ( "Id" )、 qHandle 。 %Get ( "Prop1" )、 qHandle 。 %Get ( "Prop2" ))
$$$ OKを終了OK
}
ClassMethod AllDynamicClose( ByRef qHandle As%Binary ) As%ステータス
{
qhandleを殺す
$$$ OKを終了OK
}
{
}
ClassMethod AllDynamicExecute( ByRef qHandle As%Binary ) As%ステータス
{
qHandle = ## class ( %SQL.Statement )を設定します。 %ExecDirect (、 "SELECT * FROM Utils.CustomQuery" )
$$$ OKを終了OK
}
ClassMethod AllDynamicFetch( ByRef qHandle As%Binary 、 ByRef Row As%List 、 ByRef AtEnd As%Integer = 0 ) As%ステータス
{
qHandleの 場合 。 %Next ()= 0 {
AtEnd = 1に 設定
行を 設定 = ""
$$$ OKを終了OK
}
Set Row = $ Lb ( qHandle 。 %Get ( "Id" )、 qHandle 。 %Get ( "Prop1" )、 qHandle 。 %Get ( "Prop2" ))
$$$ OKを終了OK
}
ClassMethod AllDynamicClose( ByRef qHandle As%Binary ) As%ステータス
{
qhandleを殺す
$$$ OKを終了OK
}
代替アプローチ-%SQL.CustomResultSet
または、 %SQL.CustomResultSetクラスの子孫としてクエリを定義できます。 ハブに関する%SQL.CustomResultSetの使用に関する記事があります。 このアプローチの利点:
- わずかに高速
- すべてのメタ情報はクラス定義から取得され、ROWSPECは必要ありません
- OOP原則の遵守
%SQL.CustomResultSetクラスの継承を作成するときは、次の手順を実行する必要があります。
- 結果フィールドに一致するプロパティを定義します。
- 結果の一部ではなく、リクエストコンテキストを含むプライベートプロパティを定義する
- %OpenCursorメソッドをオーバーライドします-コンテキストの初期作成を担当するqueryNameExecuteメソッドに類似しています。 エラーが発生した場合は、%SQLCODEと%Messageを設定します
- %Nextメソッドをオーバーライドします-次の結果を取得するqueryNameFetchメソッドに類似しています。 プロパティを入力します。 メソッドは、データがもうない場合は0を返し、データがある場合は1を返します
- %CloseCursorメソッドをオーバーライドします-必要に応じて、queryNameCloseメソッドの類似物
Utils.CustomQueryに%SQL.CustomResultSetを使用する例
クラスUtils.CustomQueryRS Extends%SQL.CustomResultSet
{
プロパティ ID As%String ;
プロパティ Prop1 As%String ;
プロパティ Prop2 As%Integer ;
メソッド %OpenCursor() As%Library.Status
{
設定 .. Id = ""
$$$ OKを終了OK
}
メソッド %Next( ByRef sc As%Library.Status ) As%Library.Integer [ PlaceAfter =%実行]
{
sc = $$$ OKを 設定
設定 .. Id = $ Order (^ Utils.CustomQueryD(.. Id )、1、 val )
終了 :.. Id = "" 0
設定 .. Prop1 = $ Lg ( val 、2)
設定 .. Prop2 = $ Lg ( val 、3)
$$$ OKを終了OK
}
}
{
プロパティ ID As%String ;
プロパティ Prop1 As%String ;
プロパティ Prop2 As%Integer ;
メソッド %OpenCursor() As%Library.Status
{
設定 .. Id = ""
$$$ OKを終了OK
}
メソッド %Next( ByRef sc As%Library.Status ) As%Library.Integer [ PlaceAfter =%実行]
{
sc = $$$ OKを 設定
設定 .. Id = $ Order (^ Utils.CustomQueryD(.. Id )、1、 val )
終了 :.. Id = "" 0
設定 .. Prop1 = $ Lg ( val 、2)
設定 .. Prop2 = $ Lg ( val 、3)
$$$ OKを終了OK
}
}
次のようにCOSコードから呼び出すことができます。
結果セット = ##クラス ( Utils.CustomQueryRS )を設定します。 %新規 ()
結果セット中 。 %Next () {
resultset を書き込み ます。 Id 、!
}
また、SAMPLES領域には、 Samples.Personクラスのリクエストを実装するSample.CustomResultSetクラスの例があります。
結論
カスタムクエリを使用すると、COSでのSQLコードの抽象化や、SQLだけを実装するのが難しい動作の実装などの問題を解決できます。
参照資料
クラスのリクエスト
グローバルバイパス
静的SQL
動的SQL
%SQL.CustomResultSet
Utils.CustomQueryクラス
Utils.CustomQueryRSクラス
著者は、この記事の執筆に協力してくれたadaptun habrayzerに感謝しています。