また、Cachéのすべてのデータは、多次元のスパース配列( グローバル)に格納されることも知られています 。 これにより、個々のSQLプロシージャのパフォーマンスが不十分な場合、標準のCachéSQLエンジンを使用するのではなく、CachéObjectScript(COS)サーバーのビジネスロジック言語で実行コードを書き換えることができます。 NoSQLデータ構造(グローバル)。
ただし、Caché標準クラスライブラリには、1つの制限があります。COSコードを使用して選択が実行されるSQLプロシージャの場合、コンパイル段階で返されるフィールドのセットを決定する必要があります。 NoSQL構造で動作するSQLプロシージャのメタデータを動的に設定する方法はありません。
この制限を削除する方法は、カットの下で説明されています。
CachéでのSQLプロシージャの使用
JDBC / ODBCを介した非リレーショナルCaché構造へのクエリは、以下のストアドプロシージャを使用して実装されます。
このようなストアドプロシージャは、1つ以上のレコードセット(ResultSets)、またはスカラー値を返すことができます。
例として、ODBCを操作するツールの1つを使用して、サンプル領域からストアドプロシージャsample.SP_Sample_By_Nameを呼び出します。
SQLプロシージャのシグネチャでは、何が返されるかはわかりませんが、これはプロシージャの実行中にのみ認識されます。
Cachéでは、値を返すかResultSetを返すクラスメソッドをSQLプロシージャに格納できます。 たとえば、ResultSetを返すストアドプロシージャが宣言されています。
ClassMethod SomeSqlProc( p1 As%Integer = 0 )[ ReturnResultsets 、 SqlProc ]
この構造を使用して、ResultSet(またはそれ以上)を返すストアドプロシージャとしてODBC経由で呼び出すことができるCachéObjectScriptコードを記述できます。
Cachéには、ResultSetの形式で返されるNoSQLデータを生成するための2つの標準メソッドがあります。
最初の方法。 クラスクエリの使用
クラスクエリの使用
ClassMethod SomeSqlProc( p1 As%Integer = 0 )[ ReturnResultsets 、 SqlProc ]
{
if ' $ isobject ( $ Get ( %sqlcontext )) { set sqlcontext = ## class ( %ProcedureContext )。 %新規 () }
クエリ = ##クラス ( %ResultSet )を 設定し ます 。 %新規 ( "User.SomeClass:Query" )
クエリを実行 し ます 。 実行 ( p1 )
%sqlcontextを実行し ます。 AddResultSet ( query )
}
{
if ' $ isobject ( $ Get ( %sqlcontext )) { set sqlcontext = ## class ( %ProcedureContext )。 %新規 () }
クエリ = ##クラス ( %ResultSet )を 設定し ます 。 %新規 ( "User.SomeClass:Query" )
クエリを実行 し ます 。 実行 ( p1 )
%sqlcontextを実行し ます。 AddResultSet ( query )
}
詳細はこちら
このメソッドを使用すると、CachéObjectScriptでデータを生成するための任意のコードを作成できますが、返されるResultSetのメタデータは、%Queryに基づいてコンパイラによって作成されます。#ROWSPECパラメータ、つまり コンパイル時に。
2番目の方法。 %SQL.CustomResultSetを使用する
%SQL.CustomResultSetを使用する
ClassMethod SomeSqlProc( p1 As%Integer = 0 )[ ReturnResultsets 、 SqlProc ]
{
if ' $ isobject ( $ Get ( %sqlcontext )) { set sqlcontext = ## class ( %ProcedureContext )。 %新規 () }
クエリ = ##クラス ( User.MyResultSet )を 設定し ます 。 %新規 (、 p1 )
%sqlcontextを実行し ます。 AddResultSet ( query )
}
{
if ' $ isobject ( $ Get ( %sqlcontext )) { set sqlcontext = ## class ( %ProcedureContext )。 %新規 () }
クエリ = ##クラス ( User.MyResultSet )を 設定し ます 。 %新規 (、 p1 )
%sqlcontextを実行し ます。 AddResultSet ( query )
}
実装例である %SQL.CustomResultSetの詳細をご覧ください。
このメソッドは前のメソッドと似ていますが、メタデータは、継承クラス%SQL.CustomResultSetの定義に基づいて生成されます-前の場合と同様に、コンパイル時に。
注:同じ方法でSQLデータを取得できます。
SQLデータの取得
ClassMethod SomeSqlProc( p1 As%Integer = 0 )[ ReturnResultsets 、 SqlProc ]
{
s sqltext = "SELECT * FROM dbo.Classname" ##; リクエストテキストを準備します
if ' $ isobject ( $ Get ( %sqlcontext )) { set sqlcontext = ## class ( %ProcedureContext )。 %新規 () }
クエリ = ##クラス ( %ResultSet )を 設定し ます 。 %新規 ( "%DynamicQuery:SQL" )
クエリを実行 し ます 。 準備 ( sqltext )
クエリを実行 し ます 。 実行 ()
%sqlcontextを実行し ます。 AddResultSet ( query )
}
{
s sqltext = "SELECT * FROM dbo.Classname" ##; リクエストテキストを準備します
if ' $ isobject ( $ Get ( %sqlcontext )) { set sqlcontext = ## class ( %ProcedureContext )。 %新規 () }
クエリ = ##クラス ( %ResultSet )を 設定し ます 。 %新規 ( "%DynamicQuery:SQL" )
クエリを実行 し ます 。 準備 ( sqltext )
クエリを実行 し ます 。 実行 ()
%sqlcontextを実行し ます。 AddResultSet ( query )
}
この場合、メタデータは実行時に生成されますが、データはSQLからのみ取得できます。
したがって、実行時に結果メタデータを生成し、任意のCachéObjectScriptを使用してデータを生成する場合、Cachéが提供する資金が十分でないことは明らかです。
問題解決
この問題には4つの解決策があります。
- 実行時に、生成されたオンザフライROWSPECでクラスクエリを含むクラスを作成します。
- 実行時に、%SQL.CustomResultSetから継承したクラスを、必要なフィールドセットとともに作成します。
- %SQL.CustomResultSetの代替を実装します。これは、コンパイル時ではなく、呼び出しパラメーターに基づいて実行時にメタデータを生成します。
- 実行時にメタデータを生成する%Queryの代替を実装します。
私は最後の方法を選びました-それは私には最もエレガントであるように見えました(先を見て、私はまだ松葉杖なしではできませんでした)。
最初に、User.Queryクラスを作成し、%Queryから継承します-all%Queryの実装を上書きしないようにします。 %Queryを使用する場合、コンシューマー(%ResultSet)は、GetInfoおよびGetODBCInfoの2つのクラスメソッドを介してメタデータを要求します。 後継クラスでは、これらのメソッドの代替実装を作成する必要があります。 いくつかの実験を通して(これはジェネレーターを理解するよりも簡単です)GetInfoパラメーター(.colinfo、.parminfo、.idinfo、.qHandle、extoption、.extinfo)について知りました:
- colinfo-$ lb($ lb(name、typeid、caption)、...)を追加します。ここで、nameはフィールドの内部名、typeidはCachéタイプの識別子、captionは列見出しです。
- parminfo-$ lbを追加します($ lb(name、typeid)、...)-前の段落と同じ形式ですが、タイトルなし;
- idinfo-$ lb(0,0)を追加できます(システム情報、インデックスに関連するもの、そうではないと仮定します)。
- qHandle-プログラマーによって形成された多次元ローカル配列。
- 残りはそのままにしておくことができます(オブジェクト参照の場合、オプションでオブジェクトがない場合に思われます)。
GetODBCInfoを使用すると、すべてが類似しており、もう少しフィールドがあり、結果を単一レベルのリストに追加する必要がありますが、一般的には同じです。
GetInfoおよびGetODBCInfoから正しいメタデータを返すために、あまり明確ではないいくつかのトリックを見つける必要があります。これらのトリックは主に以下のとおりです。
- タイプCaché(typeid)の識別子を取得するには、$$ externaltype ^%apiOLE(ctype、.type、 "0")を呼び出す必要があります。ctypeは、Cachéのタイプ名です(たとえば、%String [reference to the%string class])。 この関数は、識別子を型に入れます。
識別子を認識する前に、タイプ(ctype)を正規化(Package.Classにキャスト)する必要があります。これは、$$$ NormalizeClassnameマクロ(ctype)で実行できます。
GetODBCInfoの情報を取得するには、呼び出す必要があります
GetODBCColInfo ^%ourODBC( ctype 、。ColParms 、 。ColODBCTypeName 、 。ColODBCType 、 。MaxLen 、 。Precision 、 。Scale ) 、
ctypeは、Cachéの型名であり、必ずしも正規化されているわけではありません。
メタデータ(フィールド名とタイプ)を動的に生成するため、それらに関する情報をクエリに渡す必要があります。 これを行う最も明白な方法は、qHandleパラメーターを使用することです。 それを通じて、ResultSetに関する情報を送信します。 これを行うには、プログラマーは、クエリ実行(QueryExecute)の実装で、必須フィールドのROWSPEC行と正式な要求パラメーターの行(ROWSPECに類似)を形成し、それぞれqHandle(「rowspec」)およびqHandle(「params」)に入れる必要があります。
その結果、User.Queryクラスの次の実装を取得します。
クラスUser.QueryクラスUser.Query Extends%クエリ
{
ClassMethod GetInfo( ByRef colinfo As%List 、 ByRef parminfo As%List 、 ByRef idinfo As%List 、 ByRef qHandle As%Binary 、 extoption As%Integer = 0 、 ByRef extinfo As%List ) As%ステータス
{
if $ get ( qHandle ( "colinfo" ))= ""
{
RowSpec = qHandle ( "rowspec" )を設定します
set qHandle ( "colinfo" )= ""
sc = $$$ OKを 設定します
for i = 1:1: $ length ( RowSpec 、 "、" )
{
set col = $ piece ( RowSpec 、 "、" 、 i )
セット 名 = "p" _ i
set ctype = $$$ NormalizeClassname ( $ select ( $ piece ( col 、 ":" 、2) '= "" : $ piece ( col 、 ":" 、2)、1: "%String" ))
set sc = $$ externaltype ^%apiOLE( ctype 、。type 、 "0" )
終了 : $$$ ISERR ( sc )
キャプションを 設定 = $個 ( col 、 ":" 、1)
set qHandle ( "colinfo" )= qHandle ( "colinfo" )_ $ listbuild ( $ listbuild ( name 、 type 、 caption ))
}
終了 : $$$ ISERR ( sc ) sc
}
if $ get ( qHandle ( "parminfo" ))= ""
{
設定 パラメータ = qHandle ( "params" )
set qHandle ( "parminfo" )= ""
sc = $$$ OKを 設定します
for i = 1:1: $ length ( Params 、 "、" )
{
set col = $ piece ( Params 、 "、" 、 i )
セット 名 = "p" _ i
set ctype = $$$ NormalizeClassname ( $ select ( $ piece ( col 、 ":" 、2) '= "" : $ piece ( col 、 ":" 、2)、1: "%String" ))
set sc = $$ externaltype ^%apiOLE( ctype 、。type 、 "0" )
終了 : $$$ ISERR ( sc )
set qHandle ( "parminfo" )= qHandle ( "parminfo" )_ $ listbuild ( $ listbuild ( name 、 type ))
}
終了 : $$$ ISERR ( sc ) sc
}
set colinfo = qHandle ( "colinfo" )
set parminfo = qHandle ( "parminfo" )
set idinfo = $ listbuild (0,0)
$$$を終了OK
}
ClassMethod GetODBCInfo( ByRef colinfo As%List 、 ByRef parminfo As%List 、 ByRef qHandle As%Binary )
{ if $ get ( qHandle ( "colinfoodbc" ))= ""
{
RowSpec = qHandle ( "rowspec" )を設定します
set qHandle ( "colinfoodbc" )= $ listbuild ( $ LENGTH ( RowSpec 、 "、" ))
for i = 1:1: $ length ( RowSpec 、 "、" )
{
set col = $ piece ( RowSpec 、 "、" 、 i )
set ctype = $ select ( $ piece ( col 、 ":" 、2) '= "" : $ piece ( col 、 ":" 、2)、1: "%String" )
GetODBCColInfo ^%ourODBC( ctype 、。ColParms 、 。ColODBCTypeName 、 。ColODBCType 、 。MaxLen 、 。Precision 、 。Scale )を実行します
set bstr = "$ Char(0,0,0,0,0,0,0,0,0,0,0,0,0,0)"
セット 名 = $ piece ( col 、 ":" 、1)
set qHandle ( "colinfoodbc" )= qHandle ( "colinfoodbc" )_ $ listbuild ( name 、 colODBCType 、 precision 、 scale 、2、 name 、 "Query" 、 "%Library" 、 "" 、 bstr )
}
}
if $ get ( qHandle ( "parminfoodbc" ))= ""
{
設定 パラメータ = qHandle ( "params" )
set qHandle ( "parminfoodbc" )= $ listbuild ( $ LENGTH ( Params 、 "、" ))
for i = 1:1: $ length ( RowSpec 、 "、" )
{
set col = $ piece ( Params 、 "、" 、 i )
set ctype = $ select ( $ piece ( col 、 ":" 、2) '= "" : $ piece ( col 、 ":" 、2)、1: "%String" )
GetODBCColInfo ^%ourODBC( ctype 、。ColParms 、 。ColODBCTypeName 、 。ColODBCType 、 。MaxLen 、 。Precision 、 。Scale )を実行します
セット 名 = "p" _ i
set qHandle ( "parminfoodbc" )= qHandle ( "parminfoodbc" )_ $ listbuild ( colODBCType 、 precision 、 scale 、2、 name 、1)
}
}
set colinfo = qHandle ( "colinfoodbc" )
set parminfo = qHandle ( "parminfoodbc" )
$$$を終了OK
}
}
User.Queryクラスを適用する方法
User.Queryの使用は%Queryの使用に似ていますが、初期化中にメタデータを生成するために情報を渡す必要があります。
User.Queryを使用するクラスは次のようになります。
クラスUser.DynamicQueryクラスUser.DynamicQuery [ 要約 ]
{
クエリ クエリ( p1 As%Integer ) As User.Query
{
}
ClassMethod QueryExecute( ByRef qHandle As%Binary 、 p1 As%Integer ) As%Status
{
///すべての準備をする
; ...
///フォームROWSPEC
■ RowSpec = "ID:%整数、日付:%タイムスタンプ、情報:%文字列"
s qHandle ( "rowspec" )= RowSpec
///一連の仮パラメータ、定数を形成します
s qHandle ( "params" )= "p1:%Integer"
q $$$ OK
}
ClassMethod QueryClose( ByRef qHandle As%Binary ) As%Status [ PlaceAfter = QueryExecute]
{
$$$ OKを終了OK
}
ClassMethod QueryFetch( ByRef qHandle As%Binary 、 ByRef Row As%List 、 ByRef AtEnd As%Integer = 0 ) As%Status [ PlaceAfter = QueryExecute]
{
///クラスクエリのドキュメントの説明に従って、通常のQueryFetchを記述します
}
}
/// User.Queryを呼び出すストアドプロシージャのコード:
ClassMethod DynamicProc( p1 As%Integer = 0 )[ ReturnResultsets 、 SqlProc ]
{
if ' $ isobject ( $ Get ( %sqlcontext )) { set sqlcontext = ## class ( %ProcedureContext )。 %新規 () }
クエリ = ##クラス ( %ResultSet )を 設定し ます 。 %新規 ( "User.DynamicQuery:Query" )
クエリを実行 し ます 。 実行 ( p1 )
%sqlcontextを実行し ます。 AddResultSet ( query )
}
使用例
[サンプル]領域で、クエリクラスを作成します。 リクエストを1つだけ含むため、抽象化できます
クラスUser.QueriesクラスUser.Queries [ 要約 ]
{
クエリ NoSQL( ColCount As%Integer ) As User.Query
{
}
ClassMethod NoSQLExecute( ByRef qHandle As%Binary 、 ColCount As%Integer ) As%Status
{
RowSpec = "Id:%Integer"を設定します
colNum = 1:1: ColCountの場合
{
set RowSpec = RowSpec _ "、p" _ colNum _ ":%Integer"
}
set qHandle ( "rowspec" )= RowSpec
set qHandle ( "params" )= "ColCount:%Integer"
kill ^ || MyData(+ ## this )
rowNum = 1:1:100の場合 {
colNum = 1:1: ColCountの場合
{
set $ list (^ || MyData(+ ## this 、 rowNum )、 colNum )= $ R (1000)
}
}
set qHandle ( "colcount" )= ColCount
set qHandle ( "cursor" )= $ order (^ || MyData(+ ## this 、 "" ))
$$$を終了OK
}
ClassMethod NoSQLClose( ByRef qHandle As%Binary ) As%Status [ PlaceAfter = NoSQLExecute]
{
kill ^ || MyData(+ ## this )、 qHandle
$$$ OKを終了OK
}
ClassMethod NoSQLFetch( ByRef qHandle As%Binary 、 ByRef Row As%List 、 ByRef AtEnd As%Integer = 0 ) As%Status [ PlaceAfter = NoSQLExecute]
{
if qHandle ( "cursor" )= ""
{
Row = "" 、 AtEnd = 1に設定
$$$を終了OK
}
rowNum = qHandle ( "カーソル" )を 設定し ます
set Row = $ listbuild ( rowNum )_ ^ || MyData(+ ## this 、 rowNum )
set qHandle ( "cursor" )= $ order (^ || MyData(+ ## this 、 rowNum ))
$$$ OKを終了OK
}
}
このクエリは列の数を受け入れ、乱数で満たされた100個のレコードを返します。 ここで、Proceduresクラスを作成します。このクラスには、クエリを使用してクラスに格納されたプロシージャメソッドが含まれます。
クラスUser.ProceduresクラスUser.Procedures Extends%Persistent
{
ClassMethod ProcNoSQL( p1 As%Integer )[ ReturnResultsets 、 SqlName = proc_nosql 、 SqlProc ]
{
if ' $ isobject ( $ Get ( %sqlcontext )) { set sqlcontext = ## class ( %ProcedureContext )。 %新規 () }
クエリ = ##クラス ( %ResultSet )を 設定し ます 。 %新規 ( "User.Queries:NoSQL" )
クエリを実行 し ます 。 実行 ( p1 )
%sqlcontextを実行し ます。 AddResultSet ( query )
}
ClassMethod ProcSQL( p1 As%String = "" )[ ReturnResultsets 、 SqlName = proc_sql 、 SqlProc ]
{
set sqltext = "SELECT ID、名前、DOB、SSN"
set sqltext = sqltext _ "FROM Sample.Person "
set sqltext = sqltext _ "WHERE(Name%STARTSWITH '" _ p1 _ "')"
set sqltext = sqltext _ "ORDER BY Name"
if ' $ isobject ( $ Get ( %sqlcontext )) { set sqlcontext = ## class ( %ProcedureContext )。 %新規 () }
クエリ = ##クラス ( %ResultSet )を 設定し ます 。 %新規 ( "%DynamicQuery:SQL" )
クエリを実行 し ます 。 準備 ( sqltext )
クエリを実行 し ます 。 実行 ()
%sqlcontextを実行し ます。 AddResultSet ( query )
}
}
これで、NoSQLクエリを実行する作成済みのSQLプロシージャをxDBC経由で呼び出すことができます。
おわりに
動的に定義されたSQLプロシージャのNoSQLクエリを作成するために提案された方法が、SQLプロシージャのパフォーマンスを改善するための特定の実用的なタスクを実装するときに役立つことが判明したため、誰かに役立つことを願っています。これについては、次の記事で説明します。