0 フォロワー · 156 投稿

SQLは、リレーショナルデータベースにデータを格納、操作、および取得するための標準言語です。

記事 Toshihiko Minamoto · 3月 22, 2021 5m read

Caché 2013.1 より、InterSystems は特殊な値を持つフィールドが使われるクエリプランのセレクションを改善する目的で Outlier Selectivity (外れ値の選択性) を導入しました。

この記事では、「Project」テーブルを例に使い、Outlier Selectivity の概要やそれが SQL のパフォーマンスを向上させる仕組み、またクエリを書く際の注意点などについて解説したいと思います。

Selectivity (選択性)

まずは、Selectivity についてさっと説明します。 Selectivity とは、テーブル内の 1 つの列の中にある値に関するメタ情報のことです。 データが典型的なかたちで分布されていると想定した場合、「このテーブル内のこの列に特定の値を持つすべての行を要求するとしたら、通常取得できるのはテーブル内のどの程度の割合であろうか?」という疑問の答えとなる情報です。

Owener と Status という 2 つのフィールドを持つ「Project」という架空のテーブルについて考えます。 Owner にはプロジェクトを担当する従業員が入り、Status には PREP、OPEN、REVIEW、COMPLETE という 4 つのオプションの 1 つが入ります。 Tune Table を実行すると、クラスのストレージに Selectivity の値があるのが確認できます。

<Property name="Owner">
    <Selectivity>3.3726%</Selcetivity>
</Property>
<Property name="Status">
    <Selectivity>25.000%</Selcetivity>
</Property>

では、次の 2 つのクエリについて考えます。

SELECT * FROM Projects WHERE Owner = ?

SELECT * FROM Projects WHERE Status = ?

1 つ目のクエリが返すプロジェクトの割合は、平均で「Project」テーブルにあるすべてのプロジェクトの 3% をわずかに超える程度です。 2 つ目のクエリの平均は 25% です。 こうしたテーブルが JOIN や複数の WHERE 条件を伴うクエリで使用されるとなれば、3% と 25% では実行時間に大きな差が生じるほか、Caché が実行するクエリプランも変更される可能性があります。

Outlier Selectivity (外れ値の選択性)

Selectivity を見ればすべてが分かるという訳ではありません! フィールド内の潜在的な値は特殊なかたちで分布される場合があります。 Outlier Selectivity を使用することで、特殊な値、すなわち、外れ値を 1 つ持つフィールドを賢く取り扱うことができます。

「Project」テーブルでは、プロジェクトのステータスは先ほどふれた 4 つのうちの 1 つになりますが、数年ほど経てば COMPLETE のプロジェクトの数が他のステータスのプロジェクトよりも大分多くなります。

先ほども言いましたが、次のクエリは平均で「Project」テーブルの 25% を返します。

SELECT * FROM Projects WHERE Status = ?

ですが、もっと細かく推測できるはずです! もし、WHERE 節が「WHERE Status = 'COMPLETE'」であれば、テーブルのほぼすべてを取得できますが、 「WHERE Status = 'PREP'」だと、取得できる割合はごくわずかです。

保管する前の WHERE:

<Property name="Status">
    <Selectivity>0.25</Selectivity>
</Property>

Outlier Selectivity の導入により、以下を格納できます。

<Property name="Status">
    <OutlierSelectivity>0.9:"COMPLETE"</OutlierSelectivity>
    <Selectivity>0.03333</Selectivity>
</Property>

これで、以下の 2 つのクエリを区別することができます。

SELECT * FROM Projects WHERE Status = 'COMPLETE'

SELECT * FROM Projects WHERE Status = 'PREP'

1 つ目のクエリはテーブル内にあるすべてのプロジェクトの 90% を返し、2 つ目はわずか 3% しか返さないと推測できます。

複数のテーブルや複数のインデックスから選べる選択肢があるクエリの場合、90% と 3% ではパフォーマンスに大きな差が生じるほか、この場合も SQL エンジンが選択するクエリプランが変更される可能性があります。

Outlier Selectivity を使ったクエリ

Outlier Selectivity には間違いなくメリットがあり、アプリケーションに変更を加える必要もありません。しかし、フル活用するには注意すべき点がいくつかあります。 デフォルトで、Caché は同じ形式が使われたすべてのクエリに対し、クエリプランを 1 つだけ生成します。 (先ほどの WHERE Status = 'COMPLETE' や WHERE Status = 'PREP' など)

デフォルトで、Caché は、クエリのパラメーターの値は外れ値ではないと想定します。 クエリに強制的に外れ値を考慮させるには、丸かっこを二重にして、外れ値のリテラル置換を抑制します。

SELECT * FROM Projects WHERE Status = (('COMPLETE'))

SELECT * FROM Projects WHERE Status = 'PREP'

丸かっこを二重にすると、SQL エンジンがクエリ内のパラメーターの特定の値に対してプランを生成することを強制できます。 これで Caché は、プロジェクトの 90% が取得されるときと、3% が取得されるときが分かるため、このクエリに対して 2 種類のプランを使うことができます。

また、BiasQueriesAsOutlier の値を 1 か 0 に設定すれば、Caché がデフォルトで外れ値以外の値を想定するかどうかも制御できます。 以下を実行すると、Caché は、外れ値を使用するクエリは稀なクエリではないと想定します。

<Property name="Status">
    <BiasQueriesAsOutlier>1</BiasQueriesAsOutlier>
    <OutlierSelectivity>0.9:"COMPLETE"</OutlierSelectivity>
    <Selectivity>0.03333</Selectivity>
</Property>

以上の例は、Outlier Selectivity の概要、およびそれがクエリのパフォーマンスを向上させる仕組みについて理解する手掛かりとしてお役に立ちましたでしょうか? この情報の別のプレゼン資料や SQL の他の統計に関する詳細は、Selectivity と Outlier Selectivity と題した DocBook 文書をご覧ください。

0
0 168
記事 Mihoko Iijima · 3月 19, 2021 3m read

これは InterSystems FAQ サイトの記事です。

複数の SQL 文を実行する GUI はありませんが、複数の SQL 文を含むファイルを作成しファイルをインポートしながら SQL を実行する方法で対応できます。

ご利用バージョンによって使用するユーティリティメソッドが異なります。

2020.3以降をご利用の場合は %SYSTEM.SQL.Schema クラスの ImportDDL() メソッドを使用します。

2020.2以前をご利用の場合は %SYSTEM.SQL クラスの DDLImport() メソッドを使用します。 

1)バージョン 2020.3 以降での方法:%SYSTEM.SQL.Schema クラスの ImportDDL() メソッドの利用

インポート用ファイルに記述するSQL文が複数行ある場合は、記述する SQL の後ろに GO 文を記述する必要があります。

インポートファイル例は以下の通りです。

INSERT INTO Test2.Person (Name) values('test1')
go
INSERT INTO Test2.Person (Name) values('test2')
go
INSERT INTO Test2.Person (Name) values('test3')
go
0
0 10735
記事 Mihoko Iijima · 3月 9, 2021 1m read

これは InterSystems FAQ サイトの記事です。

INFORMATION_SCHEMA スキーマを使用して取得できます。

INFORMATION_SCHEMA はシステム用スキーマのため、デフォルトでは管理ポータルの SQL メニューに表示されません。

表示させる方法は以下の通りです。

  1. 管理ポータル→システムエクスプローラ→SQL メニューを開きます。
  2. スキーマのプルダウン左にある「システム」をチェックします。
  3. スキーマのプルダウンから INFORMATION_SCHEMA を選択します。

指定のテーブル(Test.Person)に対するID、フィールド名(COLUMN_NAME)、データタイプ(DATA_TYPE)、説明(DESCRIPTION)を取得するSQLは以下の通りです。

SELECT ID,COLUMN_NAME,DATA_TYPE,DESCRIPTION
FROM INFORMATION_SCHEMA.COLUMNS
where TABLE_SCHEMA='Test' AND TABLE_NAME='Person'

 

関連するFAQトピックもご参照ください:「プログラムから、クラス定義に記述されたプロパティ定義を取得する方法はありますか?」

0
0 332
記事 Mihoko Iijima · 3月 5, 2021 1m read

これは InterSystems FAQ サイトの記事です。

永続クラス(=テーブル)定義に提供される %BuildIndices() メソッドの引数に、インデックスを再構築したい ID の開始値と終了値を指定することにより、その範囲内のインデックスのみが再構築できます。
 

例えば、Sample.Person クラスにある NameIDX インデックスと ZipCode インデックスを ID=10~20 のみ再構築する場合は、以下のように実行します(ID の範囲は、第5引数、第6引数に指定してます)。

 set status = ##class(Sample.Person).%BuildIndices($LB("NameIDX","ZipCode"),1,,1,10,20) 

 

$LB() は $ListBuild() 関数で、%BuildIndices() メソッドでは、インデックス名を指定するために使用しています。

インデックスの再構築方法については、ドキュメントもご参照ください。

2018.1 以下はこちらのドキュメントをご参照ください。

0
0 454
記事 Toshihiko Minamoto · 3月 4, 2021 10m read

マッピングの例

三連載で 4 記事目を書いてしまったら、これまでのハイライトとして 5 記事目を書かないわけにはいかないでしょう!

注意:  何年か前に Dan Shusman 氏が私に「グローパルのマッピングは芸術だ」と言いました。  そのやり方に正解も不正解もありません。  どのようなマッピングを行うかは、データをどう解釈するかで決まります。  例のごとく、最終的な結論を出す方法は 1 つに限られません。  ここでご紹介する例の中には、同じ型のデータを異なる方法でマッピングする例がいくつかあります。

この記事の最後には、私が長年お客様のために書いてきたマッピングの例をまとめた zip ファイルをご用意しています。  過去 4 つの記事で触れた内容をまとめたハイライトとして、いくつか例を挙げていきたいと思います。  この記事は単なるハイライトですので、過去 4 記事ほどの詳細はカバーいたしません。  不明な点があれば、遠慮なくご連絡ください。もっと詳しく説明させていただきます。

Row ID Spec: クラス例:  Mapping.RowIdSpec.xml

これについては、過去の記事で何度か断言していますが、 これを定義する必要があるのは、添え字の式が単純なフィールドではない場合に限ります。  ご紹介した例では、添え字に格納された値を 100 で乗算しましたので、グローバルを見ると必要なのは 1.01 ですが、論理値として欲しいのは 101 ということになります。  したがい、Subscript 2 には {ClassNumber}/100 という式があり、RowIdSpec は {L2}*100 となっています。  最初はいつも逆にやってしまいます。  Subscript Expression は論理値を受け取り、それをグローバルの $ORDER() で使うので、100 で除算します。  一方の RowId Spec は、グローバルから値を受け取り、論理値を作成しているので、100 で乗算します。 

これは、Next Code に加え、乗算と除算を処理する Invalid Condition を書いて行うこともできます。

Subscript Expression Type: Other / Next Code / Invalid Condition / Data Access: クラス例:  Mapping.TwoNamespacesOneGlobal.xml

このクラスはまさに便利な機能満載です!  ご紹介したい機能の半分がこのクラスで使用されます。  このクラスは、同じグローバルを 2 つの異なるネームスペースでループします。  これらのネームスペースでは、添え字の同じ値が使用されるので、添え字レベルのマッピングは使用できません。  代わりに、Global Reference の拡張構文 ^|namespace|GlobalName を使用し、最初に USER ネームスペースでグローバルをイテレーションしてから、今度は SAMPLES ネームスペースで同じグローバルをイテレーションします。  このテーブルの IDKey は、Namespace と Sub1 の 2 つの要素で構成されます。

                Subscript Expression Other: Subscript Level 1 では、$ORDER() や $PIECE() を使わずに、ハードコーディングされた 2 つの値 USER と SAMPLES のどちらかに {L1} を設定します。  ここでは、グローバルが一切使用されていないため、タイプは ‘Other’ になっています。

                Next Code:  Subscript Expression のタイプ ‘Other’ を使用している場合は、Next Code を指定する必要があります (これを実行するために複雑なコードを書いても構いませんが、どちらにしろコードを提供する必要があります)。  Next Code が最初に呼び出されるとき、{L1} は ‘Start Value’ に設定されます。デフォルトは空の文字列です。  ループが終了したら、Next Code は {L1} を空の文字列に設定します。  この例では、{L1} は 3 回呼び出され、「USER」、「SAMPLES」、「“”」に設定されます。  Subscript Level 2 の Next Code は、Subscript Level 1 が有効な値を 2 つ返した後、「“”」にリセットされます。  この Next Code は、拡張参照付きのグローバルに対し実行されるシンプルな $$ORDER() です。

                Invalid Condition:  Subscript Level 1 と 2 に Next Code があるということは、両方が Invalid Condition を必要とすることを意味します。   この Subscript Level の値を考慮すると、条件の評価結果として値が無効となれば、1 が返されます。  {L1} については、値が「USER」でも「SAMPLES」でもなければ、1 が返されます。    例えば、 

SELECT * FROM Mapping.RowIdSpec WHERE NS = “%SYS”

を実行しても、{L1} の Invalid Condition が評価され、行は 1 つも返されません。

                Data Access:  グローバルの 2 つのプロパティ (例えば ^glo({sub1},{Sub2})) に基づいた IdKey を持つグローバルがあるとします。{Sub2} に対しループを開始する前に {Sub1} の値を指定している場合は、$DATA(^glo({Sub1}) を実行して前のレベルにデータがないかどうかをチェックします。  この例では、Subscript Level 1 にグローバルがないため、何をテストするのかという指示が必要になります。  Data Access 式は、^|{L1}|Facility となります。  次の例でも Data Access 式が必要になりますが、そちらの方が理解しやすいかもしれません。  これが分かりにくいと思う方は、次の例をご覧ください。

データアクセス / 行全体の参照: クラス例:  Mapping.TwoNamespacesOneGlobal2.xml

このクラスは、前のクラスと同じデータをマッピングしています。  違うのは Subscript Level 1 です。  このクラスでは、ネームスペースの値をハードコードする代わりに、2 つ目のグローバルを設け、その中にループする必要があるネームスペース  ^NS(“Claims”,{NS}) を含めています。  これにより、マッピングが簡素化されるほか、クラスのマッピングを変更する代わりにグローバルを設定するだけで、新しいネームスペースを追加できるので柔軟性も増します。

                データアクセス:   マッピング内の ‘Global’ は ^NS として定義されていますが、それは、Subscript Level 1 と 2 でそのグローバルに対しループを実行するためです。  Subscript Level 3 では、^|{NS}|Facility({Sub1}) に切り替えます。  Next Code で別のグローバルを使用するには、それを ‘Data Access’ で定義する必要があります。  {L1} は ^NS グローバルで制約となっていただけで、^Facility グローバルで使用されてもいないので、単純に整列させています。  {L2} は、グローバルの拡張構文  ^|{L2}|Facility 内でネームスペース参照として使用されています。 このクラスでは、‘Next Code’ は定義されていません (前の例で必要なかったのもこのためです)。  クラスのコンパイラーが ‘Data Access’ を受け取り、それを使ってこのレベルで必要な $ORDER() を生成します。

               Full Row Reference:  これは、‘Invalid Condition” と似ていて、^|{L2}|Facility({L3}) のように IdKey のすべての部分が使用されている場合に、行を参照する目的で使用されます。  このクラスでは、‘Full Row Reference’ を定義しなくても大丈夫だと思いますが、定義しても損はありません。  グローバルをイテレートする前に添え字の論理値を変更する ‘Next Code’ がある場合は、‘Full Row Reference’ を定義することが必要になります。  例えば、先ほども触れましたが、RowIdSpec クラスには ‘Next Code’ を使うこともできたと思います。  そのアプローチをとっていたら、‘Full Row Reference’ は ^Mapping({L1},{L2}/100) となっていたでしょう。

Subscript の Access Type 'Global': クラス例:  Mapping.TwoGlobals.xml

このクラスは、^Member と ^Provider という 2 つの別のグローバルからデータを表示しています。  前の例では、行にアクセスするのに、1 つのグローバルからはじめ、その後に別のグローバルに切り替えています。  今回は、行を含むグローバルが 2 つあり、 1 つ目のグローバルのすべての行をループしてから、もう 1 つのグローバルの行をループします。

               Subscript Expression Type 'Global': 1 つ目の Subscript Level を 'Global' として定義した場合は、マップの Global Property を「*」に設定する必要があります。  このスタイルのマッピングは、Mapping.TwoNamespacesOneGlobal でも使用できたのではないかと思います。  このようなマッピングを作成するあなたは、まさにアーティストです!

{L1} の Access Type は ‘Global’、そして ‘Next Code’ は「Membe」、「Provider」、「“”」を返します。  Access Type を ‘Global’ として定義すると、コンパイラーは {L1} をグローバルの名前として使う必要があると分かるため、{L2} と {L3} は単なる添え字ということになります。   {L4} も添え字ではありますが、2 つのグローバルがプロパティを別の場所に格納するというやっかいな事実に対処するコードを持っています。

このクラスでは、マッピングの Data セクションでもう 1 つ興味深いことが見られます。  ^Member グローバルだけをマッピングしていたなら、Subscript Level は 3 つだけ定義していたでしょう。また、Data セクションの Node 値 は 4、5、6、7、8、および 9 になっていたと思います。  Zip Code が Node 9 または 16 にあることに対処するために、ベースノードを IdKey 4 もしくは 10 に追加し、Zip Code に対するオフセットを取得するために Node 内で +6 を使用します。

アクセス変数: クラス例: Mapping.SpecialAccessVariable.xml

               特殊アクセス変数 は、どの Subscript Level でも定義できるほか、 1 つのレベルに複数個設けることができます。  この例では、{3D1} という変数が 1 つ定義されています。  「3」は Subscript Level 3 を意味し、「1」はこのレベルで定義された最初の変数であることを意味ます。  コンパイラーはこのために一意の変数名を生成し、その定義と削除を行います。  変数は、‘Next Code’ を実行した後に定義されます。  この例では、Level 4 の ‘Next Code’ にある変数を使いたいと思ったので、Level 3 で既に定義しておきました。  この例では、無効な日付の値があればそれを「*」に変更し、かつループの同じ場所に戻れるよう、ループのどの要素まで行ったのかを「覚えておくため」に {3D1} を使用しています。

Bitmaps: クラス例:  Mapping.BitMapExample.xml

Bitmap インデックスは比較的新しいものですが、だからといって Cache SQL Storage を使うアプリケーションには追加しない方がいいというわけでもありません。  Bitmap インデックスは、単純な正の %Integer IdKey を持つクラスなら、どのクラスにでも追加できます。

‘Type’ を「bitmap」として定義し、IdKey は添え字として含めません。  Bitmap を定義するときは、Bitmap Extent が必要なことも覚えておきましょう。  何ら特別なものではありません。Extent は IdKey の値のインデックスなので、添え字は必要ありません。また ‘Type” は「bitmapextent」です。

このクラスには、Sets や Kills を使ってデータを変更する際に、Bitmap インデックスを管理するために呼び出せるメソッドがあります。  SQL もしくは Objects を使って変更を加えることができるなら、インデックスは自動的に管理されます。

確かに少しややこしい例も含まれています!  これらのクラスを見て、内容が理解できるでしょうか。  よく解らないという方は、Brendan@interSystems.com までご連絡ください。頑張るアーティストの皆さんのためなら、いつでも喜んでサポートさせていただきます。  スマイル

クラスの例はこちら

マッピングに関する他の記事の内容をおさらいしたいという方は、以下のリンクをご利用ください。

グローバルをクラスにマッピングする技術 (1/3)

グローバルをクラスにマッピングする技術 (2/3)

グローバルをクラスにマッピングする技術 (3/3)

グローバルをクラスにマッピングする技術 (4/3)

0
0 201
記事 Hiroshi Sato · 3月 3, 2021 3m read

これは InterSystems FAQ サイトの記事です。
 

通常SQLCODE -110(Locking conflict in filing)のエラーはロックが競合した場合に発生します。

大量レコードが一度に更新された場合、その件数がロック閾値を超えてロックエスカレーションを起こしてテーブルロックとなる可能性があり、そのためにロックの競合が起きやすくなっていることが考えられます。

このロック閾値を上げることにより、この競合を回避できる可能性もあります。

しかしこの閾値を上げることにより、システムが必要とするロック管理用のメモリが増えるという副作用がありますので、慎重な検討が必要です。

あるいはテーブルロックになる可能性を排除できずに、更新タイミング等アプリケーションの仕様を見直す必要があるかもしれません。

またSQLCODE -110はロックテーブルの空き容量が不足した場合にも発生する可能性があります。

コンソールログファイル(※)をご確認いただき、「LOCK TABLE FULL!!!」のエラーが記録されているようであれば、ロックテーブルの容量不足が原因です。

※ InterSystems IRIS のコンソールログファイル名は messages.log、Caché/Ensemble/HealthShare コンソールログファイル名は cconsole.log

この場合は、

0
0 1409
記事 Toshihiko Minamoto · 3月 1, 2021 11m read

グローバルをクラスにマッピングする技術 (4/3)

三連載のはずが 4 記事目に突入してしまいました。『銀河ヒッチハイク・ガイド』のファンという方はいませんか?

古くなった MUMPS アプリケーションに新たな生命を吹き込みたいとお考えですか? 以下にご紹介するステップを実行すれば、グローバルをクラスにマッピングし、美しいデータを Object や SQL に公開できます。

上の内容に馴染みが無い方は、以下の記事を初めからお読みください。

グローバルをクラスにマッピングする技術 (1/3)

グローバルをクラスにマッピングする技術 (2/3)

グローバルをクラスにマッピングする技術 (3/3)

この記事は Joel、あなたのために書きます!  前回の例で定義した親子関係を土台に、今度は孫クラスを作成し、^ParentChild グローバルに追加された季節情報を処理したいと思います。

前回と同じ免責事項:  これらの記事を読んでもグローバルがよく理解できないという方は、WRC (Support@InterSystems.com) までメールでお問い合わせください。喜んでサポートさせていただきます。

グローバルをクラスにマッピングするステップ。

  1. グローバルデータが繰り返し使用されるパターンを特定する。
  2. 固有キーの構成を特定する。
  3. プロパティとそれぞれの型を特定する。
  4. クラス内のプロパティを定義する (変数の添え字をお忘れなく)。
  5. IdKey のインデックスを定義する。
  6. Storage Definition を以下の手順で定義する。
    1. 添え字を IdKey まで (IdKey を含む) 定義する。
    2. Data セクションを定義する。
    3. Row ID セクションには触れない。  デフォルトが 99% の割合で適切なので、これはシステムに任せます。
</ol>  7. クラス / テーブルをコンパイルし、テストします。

^ParentChild(1)="Brendan^45956"

^ParentChild(1,"Hobbies",1)="Pit Crew"

^ParentChild(1,"Hobbies",1,"Seasons")="Fall*Winter"

^ParentChild(1,"Hobbies",2)="Kayaking"

^ParentChild(1,"Hobbies",2,"Seasons")="Spring*Summer*Fall"

^ParentChild(1,"Hobbies",3)="Skiing"

^ParentChild(1,"Hobbies",3,"Seasons")="Summer*Winter"

^ParentChild(2)="Sharon^46647"

^ParentChild(2,"Hobbies",1)="Yoga"

^ParentChild(2,"Hobbies",1,"Seasons")="Spring*Summer*Fall*Winter"

^ParentChild(2,"Hobbies",2)="Scrap booking"

^ParentChild(2,"Hobbies",2,"Seasons")="Spring*Summer*Fall*Winter"

^ParentChild(3)="Kaitlin^56009"

^ParentChild(3,"Hobbies",1)="Lighting Design"

^ParentChild(3,"Hobbies",1,"Seasons")="Spring*Summer*Fall*Winter"

^ParentChild(3,"Hobbies",2)="pets"

^ParentChild(3,"Hobbies",2,"Seasons")="Spring*Summer*Fall*Winter"

^ParentChild(4)="Melissa^56894"

^ParentChild(4,"Hobbies",1)="Marching Band"

^ParentChild(4,"Hobbies",1,"Seasons")="Fall"

^ParentChild(4,"Hobbies",2)="Pep Band"

^ParentChild(4,"Hobbies",2,"Seasons")="Winter"

^ParentChild(4,"Hobbies",3)="Concert Band"

^ParentChild(4,"Hobbies",3,"Seasons")="Spring*Summer*Fall*Winter"

^ParentChild(5)="Robin^57079"

^ParentChild(5,"Hobbies",1)="Baking"

^ParentChild(5,"Hobbies",1,"Seasons")="Spring*Summer*Fall*Winter"

^ParentChild(5,"Hobbies",2)="Reading"

^ParentChild(5,"Hobbies",2,"Seasons")="Spring*Summer*Fall*Winter"

^ParentChild(6)="Kieran^58210"

^ParentChild(6,"Hobbies",1)="SUBA"

^ParentChild(6,"Hobbies",1,"Seasons")="Summer"

^ParentChild(6,"Hobbies",2)="Marching Band"

^ParentChild(6,"Hobbies",2,"Seasons")="Fall"

^ParentChild(6,"Hobbies",3)="Rock Climbing"

^ParentChild(6,"Hobbies",3,"Seasons")="Spring*Summer*Fall"

^ParentChild(6,"Hobbies",4)="Ice Climbing"

^ParentChild(6,"Hobbies",4,"Seasons")="Winter"

ステップ 1:

この新しいクラスも繰り返し使用されるデータを見つけるのは簡単ですね、そう Season サブノードです。  “Spring*Summer*Fall” をすべて同じ行に並べる代わりに、“Spring”、“Summer”、“Fall” という個別の行を 3 つ作成する、という場合に少しややこしくなります。 

^ParentChild(1)="Brendan^45956"

^ParentChild(1,"Hobbies",1)="Pit Crew"

^ParentChild(1,"Hobbies",1,"Seasons")="Fall*Winter"

^ParentChild(1,"Hobbies",2)="Kayaking"

^ParentChild(1,"Hobbies",2,"Seasons")="Spring*Summer*Fall"

^ParentChild(1,"Hobbies",3)="Skiing"

^ParentChild(1,"Hobbies",3,"Seasons")="Summer*Winter"

各趣味 (Hobby) には、季節を最大 4 つ割り当てることができます。  Example3Child クラスにプロパティをあと 4 つ作成できないことはないですが、誰かが新しい季節を勝手に作ってしまったらどうなるでしょう。 そこで、もっと柔軟な解決策として、孫テーブルを作り、季節の数を動的に割り当てられるようにします。

ステップ 2:

添え字に注目すれば、IdKey に含まれる部分が分かるので、添え字 1 と 3 は IdKey に含まれるということが分かります。ところが、それぞれの季節を一意に識別するにはもう 1 つ添え字が必要なのですが、使える添え字が残っていません!

マッピングに入力している情報は、クエリを実行するためのコードを生成するために使用されます。  どのような COS コマンドを使えばこの情報を取得できるのかと考えることで、マッピングを定義しやすくなるかもしれません。  ここで、実行する必要のある重要なコマンドが 3 つあります。

                SET sub1=$ORDER(^Parentchild(sub1))

                SET sub2=$ORDER(^Parentchild(sub1,”Hobbies”,sub2))

                SET season=$PIECE(^ParentChild(sub1,”Hobbies”,sub2”,”Seasons”),”*”,PC)

マッピングの Subscripts セクションでも同じことを行えます。  Caché SQL Storage は、4 種類の添え字をサポートしています (Piece、Global、Sub、Other)。  ここまでは、デフォルトの Sub を使っています。必需品的な存在として活躍する $ORDER() ループを使えるのもそのおかげです。 

この例では、Piece オプションをご紹介します。  このレベルで使用するプロパティは、Piece Counter (上の例で PC として表示) として使用されます。  デフォルトの動作として、文字列の最後に達するまでこれを 1 ずつ増加させます。

ステップ 3:

3 つのプロパティ:  Data は単純な Season、そして Relationship プロパティ HobbyRef があり、最後に childsub として PieceCount が必要になります。

ステップ 4:

Property Season As %String;
Property PieceCounter As %Integer;
Relationship HobbyRef As Mapping.Example3Child [ Cardinality = parent, Inverse = Seasons ];

ステップ 5:

Subscripts のマッピングを見ると、変数のレベルが 3 つありますが、IdKey のインデックスでは、2 つのプロパティ HobbyRef と PieceCounter だけを参照しています。

Index Master On (HobbyRef, PieceCounter) [ IdKey ];

ステップ 6:

これまで使用してきたお馴染みの 3 つのセクションです。  Row ID は引き続きそのままにしておきます。  このステップでは、Subscripts についてもう少し詳しく説明して、Access Type を定義する必要があります。  このクラスでは、‘Sub’ と ‘Piece’ を使います。  ‘Global’ と ‘Other’ を使った例をご覧になりたい方は、私が例をまとめた zip ファイルをダウンロードしてください。

ステップ 6a:

Subscripts のメインページはいつもと同じですが、レベルが 2 つ追加されています。  Parent 参照の 2 つの部分については、先ほど参照した 2 つのクラス {Mapping.Example3Parent} と {Mapping.Example3Child} を参照し直す必要があることを覚えておきましょう。  ウィンドウの左側にある Subscripts Levels の 1 つをクリックすると、違いが表示されます。

下の画像では、Subscripts Level で行える様々なアクションをご覧いただけます。  ‘Sub’ の Access Type では、「“”」からはじめて、「“”」 に到達するまで $ORDER() を実行しようとお考えではないでしょうか。  そうでない場合は、Start Value または Stop Value またはその両方を指定できます。 

‘Data Access’ を使うと、確認する内容を前のレベルから変更できます (イテレーションするグローバルを変更するなど)。 

‘Next Code’ と ‘Invalid Conditions’ は一緒に使います。このウィンドウで一番頻繁に使うことになるでしょう。  シンプルな $ORDER() では有効な値を順に取得できないという場合は、代わりに独自のコードを書いてください。 

‘Next Code’ は、$ORDER() と同様、1 つの有効な値からその次の有効な値に移動するために使用されます。 

‘Invalid Condition’ は特定の値を評価するのに使用されます。  ‘subscriptX’ の値を指定した場合は、それを見つけるためにわざわざ ‘Next’ を呼び出す必要がなくなります。  必要なのは、その値が有効なのかどうかを見極めるコードです。 

長い間にわたってお約束してきた zip ファイルには、Next Code’ と ‘Invalid Conditions’ を使うクラスがたくさん入っています。 

ページの最後に登場する ‘Access Variables’ ですが、使うことは滅多にありません。  簡単に言うと、変数を設定し、1 つの Subscript Level で値を割り当て、それを上位の Subscript Level で使用するためのものです。  スコーピングは生成されるテーブルのコードが代わりにやってくれます。

Subscript Level 5 では、‘Access Type’ は ‘Piece’、‘Delimiter’ は “*” となります。  生成されたコードは、Piece 1 からスタートし、$PIECE の値がなくなるまで 1 ずつ増加していきます。  ここでも、Start Value または Stop Value またはその両方を指定すれば、これを制御できます。

ステップ 6b:

Data セクションにはプロパティが 1 つしかないので、‘Piece’ も ‘Delimiter’ も必要ありません。  このテーブルにもっとフィールドがあれば、‘Piece’ と ‘Delimiter’ を指定することになると思いますが、それはそれで問題ありません。

ステップ 6c:

まだ空白のままにしておきます。

ステップ 7:

すべてが正常にコンパイルします。

Compilation started on 11/30/2016 08:17:42 with qualifiers 'uk/importselectivity=1 /checkuptodate=expandedonly'
Compiling 2 classes, using 2 worker jobs
Compiling class Mapping.Example3Child
Compiling class Mapping.Example3GrandChild
Compiling table Mapping.Example3GrandChild
Compiling table Mapping.Example3Child
Compiling routine Mapping.Example3Child.1
Compiling routine Mapping.Example3GrandChild.1
Compilation finished successfully in 1.021s.

3 つのテーブルの結合

SELECT P.ID, P.Name, P.DateOfBirth,

C.ID, C.Hobby, G.ID, G.Season

FROM Mapping.Example3Parent P

JOIN Mapping.Example3Child C ON P.ID = C.ParentRef

JOIN Mapping.Example3Grandchild G ON C.ID = G.HobbyRef

WHERE P.Name = 'Kieran'

この結果、以下が出力されます。

<td><p  style-"margin-left: 9pt; text-align: center;">
  <strong>Name</strong></p>
</td>

<td><p  style-"margin-left: 9pt; text-align: center;">
  <strong>DateOfBirth</strong></p>
</td>

<td style="margin-left: 9pt; text-align: center;">
  <strong>ID</strong></p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  <strong>Hobby</strong></p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  <strong>ID</strong></p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  <strong>Season</strong></p>
</td>
<td><p style="margin-left: 9pt; text-align: center;">
  Kieran</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  05/16/2000</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  6||1</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  SUBA</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  6||1||1</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  Summer</p>
</td>
<td><p style="margin-left: 9pt; text-align: center;">
  Kieran</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  05/16/2000</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  6||2</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  Marching Band</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  6||2||1</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  Fall</p>
</td>
<td><p style="margin-left: 9pt; text-align: center;">
  Kieran</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  05/16/2000</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  6||3</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  Rock Climbing</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  6||3||1</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  Spring</p>
</td>
<td><p style="margin-left: 9pt; text-align: center;">
  Kieran</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  05/16/2000</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  6||3</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  Rock Climbing</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  6||3||2</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  Summer</p>
</td>
<td><p style="margin-left: 9pt; text-align: center;">
  Kieran</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  05/16/2000</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  6||3</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  Rock Climbing</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  6||3||3</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  Fall</p>
</td>
<td><p style="margin-left: 9pt; text-align: center;">
  Kieran</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  05/16/2000</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  6||4</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  Ice Climbing</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  6||4||1</p>
</td>

<td><p style="margin-left: 9pt; text-align: center;">
  Winter</p>
</td>

ID

6

6

6

6

6

6

 

子テーブルの IdKey は、常に Parent 参照と Childsub の 2 つで構成されると説明したことを覚えておいてください。 

最初の行は、Example3Child ID が 6||1、 Parent 参照が 6、Childsub が 1 となっています。 

Example3GrandChild の IdKey は、3 つの部分 (6||1||1) で構成されていますが、それでも Parent 参照と Childsub を表しています。  Parent 参照は少し複雑な感じに 6||1 となって 、Childsub は 1 となっています。 

Example3Child では、Subscripts のプロパティの数が IdKey のプロパティの数に一致しています。 親子構造の入れ子状態が深まると、IdKey は複合化され、添え字の数は増えていきます。

今回の例で使用した 3 つのクラスはこちらにエクスポートしておきました: MappingExample4.zip。

0
0 212
記事 Toshihiko Minamoto · 2月 25, 2021 9m read

古くなった MUMPS アプリケーションに新たな生命を吹き込みたいとお考えですか? 以下にご紹介するステップを実行すれば、グローバルをクラスにマッピングし、美しいデータを Object や SQL に公開できます。

上の内容に馴染みが無い方は、以下の記事を初めからお読みください。

グローバルをマッピングする技術 1

グローバルをマッピングする技術 2

この記事の例では、典型的な親子構造をマッピングする方法をお見せします。

前回と同じ免責事項:  これらの記事を読んでもグローバルがよく理解できないという方は、WRC (Support@InterSystems.com) までメールでお問い合わせください。喜んでサポートさせていただきます。

グローバルをクラスにマッピングするステップ。

  1. グローバルデータが繰り返し使用されるパターンを特定する。
  2. 固有キーの構成を特定する。
  3. プロパティとそれぞれの型を特定する。
  4. クラス内のプロパティを定義する (変数の添え字をお忘れなく)。
  5. IdKey のインデックスを定義する。
  6. Storage Definition を以下の手順で定義する。
    1. 添え字を IdKey まで (IdKey を含む) 定義する。
    2. Data セクションを定義する。
    3. Row ID セクションには触れない。  デフォルトが 99% の割合で適切なので、これはシステムに任せます。
</ol>  7. クラス / テーブルをコンパイルし、テストします。

 

前回の例では、家族のメンバーは Activity をそれぞれ 1 つずつしか持てませんでしたが、それでは面白くないので、今回は複数の Activity を持てるようにしたいと思います。  以下の例のステップ 1 は、繰り返し使用されるデータが 2 種類あるので、少しだけ複雑になります。

^ParentChild(1)="Brendan^45956"

^ParentChild(1,"Hobbies",1)="Pit Crew"

^ParentChild(1,"Hobbies",2)="Kayaking"

^ParentChild(1,"Hobbies",3)="Skiing"

^ParentChild(2)="Sharon^46647"

^ParentChild(2,"Hobbies",1)="Yoga"

^ParentChild(2,"Hobbies",2)="Scrap booking"

^ParentChild(3)="Kaitlin^56009"

^ParentChild(3,"Hobbies",1)="Lighting Design"

^ParentChild(3,"Hobbies",2)="pets"

^ParentChild(4)="Melissa^56894"

^ParentChild(4,"Hobbies",1)="Marching Band"

^ParentChild(4,"Hobbies",2)="Pep Band"

^ParentChild(4,"Hobbies",3)="Concert Band"

^ParentChild(5)="Robin^57079"

^ParentChild(5,"Hobbies",1)="Baking"

^ParentChild(5,"Hobbies",2)="Reading"

^ParentChild(6)="Kieran^58210"

^ParentChild(6,"Hobbies",1)="SUBA"

^ParentChild(6,"Hobbies",2)="Marching Band"

^ParentChild(6,"Hobbies",3)="Rock Climbing"

^ParentChild(6,"Hobbies",4)="Ice Climbing"

ステップ 1:

                このグローバルには、繰り返し使用されるデータが 2 種類あります。1 つ目のデータは、添え字の最初のレベルにあり、個人情報が格納されています。

               ^ParentChild(1)="Brendan^45956"

^ParentChild(2)="Sharon^46647"

^ParentChild(3)="Kaitlin^56009"

^ParentChild(4)="Melissa^56894"

^ParentChild(5)="Robin^57079"

^ParentChild(6)="Kieran^58210"

2 つ目のデータは、添え字の 3 つ目のレベルにあり、趣味が格納されています。

^ParentChild(1,"Hobbies",1)="Pit Crew"

^ParentChild(1,"Hobbies",2)="Kayaking"

^ParentChild(1,"Hobbies",3)="Skiing"

^ParentChild(2,"Hobbies",1)="Yoga"

^ParentChild(2,"Hobbies",2)="Scrap booking"

                …

               クラスは合計 2 つ、繰り返し使用されるデータのブロックそれぞれに 1 つずつ定義します。

ステップ 2:

クラスが 2 つあるということは、固有の識別子が各テーブル (Example3Parent と Example3Child) に 1 つずつ、合計で 2 つ必要になるということです。  Parent テーブルは添え字が 1 つしかないので簡単です。  一方の Child テーブルには、最初の添え字 (Parent への参照) と 3 つ目の添え字 (childsub) を組み合わせた複合キーが使用されます。

ステップ 3:

データを見ていると、簡単に 5 種類のプロパティを特定できます。  Example3Parent には、ParentId、Name、DateOfBirth と、プロパティが 3 つあり、 Example3Child には 2 つ、ChildId と Hobby があります。  この例では、プロパティをあと 2 つ定義する必要があります。  これらは、Relationship Properties (関係プロパティ) と呼ばれています。  別のクラスとの関係を定義するもので、各クラスに 1 つずつ記述します。  親クラスは子クラスを多く持てる一方で、子クラスは親クラスを 1 つしか持てません。

親クラスには、以下が定義されています。

Relationship HobbyRef As Mapping.Example3Child [ Cardinality = children, Inverse = ParentRef ];

子クラスでは、以下が定義されています。

Relationship ParentRef As Mapping.Example3Parent [ Cardinality = parent, Inverse = HobbyRef ];

これらのプロパティは、Property ウィザードを使えば定義できます。  Relationship オプションボタンをクリックすると、以下のページが表示されます。

ステップ 4:

ステップ 3 で少し先走って Relationship プロパティを紹介しましたが、 それ以外のプロパティは以下のとおりです。

                Example3Parent:

Property Name As %String;
Property DateOfBirth As %Date;
Property ParentId As %Integer;

                Example3Child:

Property Hobby As %String;
Property ChildId As %Integer;

ステップ 5:

                Example3Parent の IdKey はいたって単純。添え字のレベルは 1 つだけです。

      Index Master On ParentId [ IdKey ];

一方の Example3Child の IdKey は、添え字 1 と 3 に基づいています。しかし、親子関係は少しややこしくなります (十代の女の子をお持ちの親御さんなら共感していただけるのではないかと思います)。  IdKey インデックスには、ParentRef と ChildId の両方、または ChildId だけを指定することができますが、どちらの場合でもきちんとコンパイルします。

Index Master On (ParentRef, ChildId) [ IdKey ];

もしくは

Index Master On ChildId [ IdKey ];

個人的には最初の方が適切だと思いますが、私は面倒くさがり屋なので、自分がお見せする例には 2 つ目の例が登場する可能性が高いです。

ステップ 6:

Example3Parent については何の特記事項もありませんので、細い説明は割愛します。 

一方の Example3Child では、添え字の扱いが少し違うので、その手順を説明します。

ステップ 6a:

下にあるのは、子クラスのグローバルです。

                ^ParentChild({ParentRef},"Hobbies",{ChildId})=””

添え字は 3 つ必要なことが分かります。  1 つ目の添え字に手こずる人が多いです。  先ほどお見せした Relationship プロパティは使わず、代わりに親クラスのプロパティを使います。  それを行うには、完全なフィールド構文 {SchemaName.TableName.FieldName} を使います。  今回の例だと、1 つ目の添え字は {Mapping.Example3Parent.ParentId} となり、

2 つ目の添え字は定数の “Hobbies”、

そして 3 つ目にはフィールド {ChildId} を使います。

ステップ 6b:

Data セクションは極めてシンプル。フィールド {Hobby} だけです。Piece も Delimiter もありません。

ステップ 6c:

では皆さんご一緒に「お見せするものがないので、空白にしておきます!」(笑)

ステップ 7:

後はコンパイルするだけです。

Compilation started on 11/28/2016 08:26:31 with qualifiers 'fuk/importselectivity=1 /checkuptodate=expandedonly'
Compiling 2 classes, using 2 worker jobs
Compiling class Mapping.Example3Parent
Compiling class Mapping.Example3Child
Compiling table Mapping.Example3Parent
Compiling table Mapping.Example3Child
Compiling routine Mapping.Example3Parent.1
Compiling routine Mapping.Example3Child.1
Compilation finished successfully in 0.900s.

そして、単純な JOIN を実行し、データが正しく表示されるのを確認します。

SELECT P.ParentId, P.Name, P.DateOfBirth, C.ID, C.Hobby

FROM Mapping.Example3Parent P

JOIN Mapping.Example3Child C ON P.ParentId = C.ParentRef

<td>
  Name
</td>

<td>
  DateOfBirth
</td>

<td>
  ID
</td>

<td>
  Hobby
</td>
<td>
  Brendan
</td>

<td>
  10/28/1966
</td>

<td>
  1||1
</td>

<td>
  Pit Crew
</td>
<td>
  Brendan
</td>

<td>
  10/28/1966
</td>

<td>
  1||2
</td>

<td>
  Kayaking
</td>
<td>
  Brendan
</td>

<td>
  10/28/1966
</td>

<td>
  1||3
</td>

<td>
  Skiing
</td>
<td>
  Sharon
</td>

<td>
  09/18/1968
</td>

<td>
  2||1
</td>

<td>
  Yoga
</td>
<td>
  Sharon
</td>

<td>
  09/18/1968
</td>

<td>
  2||2
</td>

<td>
  Scrap booking
</td>
<td>
  Kaitlin
</td>

<td>
  05/07/1994
</td>

<td>
  3||1
</td>

<td>
  Lighting Design
</td>
<td>
  Kaitlin
</td>

<td>
  05/07/1994
</td>

<td>
  3||2
</td>

<td>
  pets
</td>
<td>
  Melissa
</td>

<td>
  10/08/1996
</td>

<td>
  4||1
</td>

<td>
  Marching Band
</td>
<td>
  Melissa
</td>

<td>
  10/08/1996
</td>

<td>
  4||2
</td>

<td>
  Pep Band
</td>
<td>
  Melissa
</td>

<td>
  10/08/1996
</td>

<td>
  4||3
</td>

<td>
  Concert Band
</td>
<td>
  Robin
</td>

<td>
  04/11/1997
</td>

<td>
  5||1
</td>

<td>
  Baking
</td>
<td>
  Robin
</td>

<td>
  04/11/1997
</td>

<td>
  5||2
</td>

<td>
  Reading
</td>
<td>
  Kieran
</td>

<td>
  05/16/2000
</td>

<td>
  6||1
</td>

<td>
  SUBA
</td>
<td>
  Kieran
</td>

<td>
  05/16/2000
</td>

<td>
  6||2
</td>

<td>
  Marching Band
</td>
<td>
  Kieran
</td>

<td>
  05/16/2000
</td>

<td>
  6||3
</td>

<td>
  Rock Climbing
</td>
<td>
  Kieran
</td>

<td>
  05/16/2000
</td>

<td>
  6||4
</td>

<td>
  Ice Climbing
</td>
         ParentId
1
1
1
2
2
3
3
4
4
4
5
5
6
6
6
6

 

子クラスに ParentId_”||”_ChildId で構成される ID というフィールドがあることに注目してください (どのクラスにも ID というフィールド / プロパティがあります)。

自分で入力したくないという方は、グローバルとクラスが記述されたこちらのファイルをお使いください:  MappingExample3.zip

0
0 227
記事 Mihoko Iijima · 2月 21, 2021 3m read

皆さんこんにちは!

VSCode の SQLTools エクステンションを使うと、VSCode から SQLTools に対応しているデータベースへ接続/クエリ実行が行えるようです。

1 つの IDE で 各種言語を操作でき、さらにクエリも発行できるなんて VSCode って便利ですね!👏👏

実は、まだプレビュー機能ではありますが、InterSystem IRIS も接続できます!🎊🎊

正式リリース前なのですが、どんな感じでご利用いただけるかをご紹介したいと思います。

解説ビデオ(4分ちょっと)もあります。ぜひご参照ください。

※ ObjectScript エクステンションの基本的な操作方法については、こちらの記事をぜひご参照ください。

手順1:SQLTools エクステンションをインストール

(ビデオでは、0:00~0:32 で解説しています)

図の手順でインストールします(右画面の SQLTools の説明文下の方に対応データベースリストがあり、「InterSystems IRIS」の文字も見えます!)。

手順2:SQLTools に対応するドライバをインストール(ここでは IRIS 用ドライバのインストール)

(ビデオでは、0:32~1:09 で解説しています)

SQLTools のインストールが終わると、VSCode の左端の黒いバーのところに   アイコンが見えるのでクリックします。

2
0 1471
記事 Toshihiko Minamoto · 2月 18, 2021 9m read

 古くなった MUMPS アプリケーションの新たな生命を吹き込みたいとお考えでしたら、以下にご紹介するステップを実行すれば、グローバルをクラスにマッピングし、美しいデータを Object や SQL に公開できます。

今回ご紹介する例には、パート 1 ではカバーしなかった内容を 4 つないし 5 つ程度盛り込んでいます。 

その後は親子マッピングの例を紹介して完結となります。それを修得したらマッピングはもう完璧でしょう。

前回と同じ免責事項:  これらの記事を読んでもグローバルがよく理解できないという方は、WRC (Support@InterSystems.com) までメールでお問い合わせください。喜んでサポートさせていただきます。 

グローバルをクラスにマッピングするステップ。

  1. グローバルデータが繰り返し使用されるパターンを特定する。
  2. 固有キーの構成を特定する。
  3. プロパティとそれぞれの型を特定する。
  4. クラス内のプロパティを定義する (変数の添え字をお忘れなく)。
  5. IdKey のインデックスを定義する。
  6. Storage Definition を以下の手順で定義する。
    1. 添え字を IdKey まで (IdKey を含む) 定義する。
    2. Data セクションを定義する。
    3. Row ID セクションには触れない。  デフォルトが 99% の割合で適切なので、これはシステムに任せます。
</ol>  
  1. クラス / テーブルをコンパイルし、テストします。

 

以下のようなグローバルが 2 種類 (mapping と index) あるとします。

^mapping("Less Simple",1,1)="Bannon,Brendan^Father"

^mapping("Less Simple",1,1,"Activity")="Rock Climbing"

^mapping("Less Simple",1,2)="Bannon,Sharon^Mother"

^mapping("Less Simple",1,2,"Activity")="Yoga"

^mapping("Less Simple",1,3)="Bannon,Kaitlin^Daughter"

^mapping("Less Simple",1,3,"Activity")="Lighting Design"

^mapping("Less Simple",1,4)="Bannon,Melissa^Daughter"

^mapping("Less Simple",1,4,"Activity")="Marching Band"

^mapping("Less Simple",1,5)="Bannon,Robin^Daughter"

^mapping("Less Simple",1,5,"Activity")="reading"

^mapping("Less Simple",1,6)="Bannon,Kieran^Son"

^mapping("Less Simple",1,6,"Activity")="Marching Band"

^index("Less Simple","FName","BRENDAN",1,1)=""

^index("Less Simple","FName","KAITLIN",1,3)=""

^index("Less Simple","FName","KIERAN",1,6)=""

^index("Less Simple","FName","MELISSA",1,4)=""

^index("Less Simple","FName","ROBIN",1,5)=""

^index("Less Simple","FName","SHARON",1,2)=""

そうです、グローバルマッピングについて学ぶと同時に、私の家族についても知っていただく必要があります。  創造力が足らなくて、これ以外のデータは思いつきませんでした。

ステップ 1:

今回は、繰り返しデータが、1 つではなく、2 つのグローバルノードに広がっています。

ステップ 2:

データがこれしかないので、確信は持てませんが、 1 つ目の添え字、もしくは 1 つ目と 2 つ目の添え字の両方が定数だと思います。  今回の例では、2 つ目の添え字が変数 FamilyId、3 つ目が PersonId であると想定します。

ステップ 3:

変数の添え字は FamilyId と PersonId の 2 つ、さらに Name、Relation、Activity があります。  ヒントを求めてインデックスのグローバルをもう一度確認した結果、Name を 2 つのプロパティ FirstName と LastName にマッピングすることにしました。  これで、定義する必要のあるプロパティが全部で 6 個になりました。

ステップ 4:

ここで新しいものが 2 つ登場します。  すべてのプロパティに SQL Field Name があり、FirstName の照合が UPPER になっています。  SQL Field Names を定義しておけば、プロパティの話なのか、SQLフィールドの話なのかがはっきりします。  照合については、後ほどインデックスのマッピングと一緒に説明します。

Property FamilyId As %Integer [ SqlFieldName = Family_Id ];
Property PersonId As %Integer[ SqlFieldName = Person_Id ];
Property FirstName As %String(COLLATION = "UPPER")[ SqlFieldName = First_Name ];
Property LastName As %String[ SqlFieldName = Last_Name ];
Property Relation As %String[ SqlFieldName = Relation ];
Property Activity As %String[ SqlFieldName = Activity ];

ステップ 5:

変数の添え字が 2 つあるため、IdKey は 2 つのプロパティに基づくことを意味します。


Index Master On (FamilyId, PersonId)[IdKey];

ここでも、1 つのインデックスが 1 つのプロパティ FirstName に対して定義されています。

Index FNameIndex On FirstName;

ステップ 6:

まず最初に Storage Definition を作成します。  Storage アイコンをクリックするか、Inspector を使って、Storage を選択し、New Storage を右クリックします。  今回は、Map Name をデフォルト値のままにして、Global Name と ^mapping だけを入力しました。

最初の例では、Storage Definition を作成してからウィザードに戻る方法を説明していなかったので、ここでご紹介いたします。  ウィザードに戻るには、Inspector から Storage を選択した後に、Storage Name (今回は NewStorage1) を選択し、SQL Storage Map と右側の端の方にあるボックスを順にクリックします。

ステップ 6a:

もう一度 Subscripts セクションから始めます。  まず最初に、IdKey 以下のすべてを定義するために添え字のレベルが 3 つ (定数が 1 つ、フィールドが 2 つ) 必要になります。  このウィンドウに表示されるフィールドは、IdKey のインデックスに定義されているプロパティと一致している必要があります。

注意:  Storage Definition では、常に、プロパティ名 FamilyId ではなく、SQL のフィールド名 Family_Id を参照しています。

ステップ 6b:

Data セクションでは、1 つのグローバルノードにフィールドが 3 つ、そして添え字の下位レベル ( “Activity”) にはフィールドが 1 つ格納されています。  ファーストネームとラストネームを 2 つの個別のフィールドとして取得するには、入れ子になった $PIECE() に対応する Piece と Delimiter を複数個使う必要があります。  これは、ObjectScript では「set FirstName=$PIECE($PIECE(^mapping(“Less Simple”,1,1),”^”,1),”,”,2)」のように記述されます。 ウィザードでは、最も内側にある $PIECE() からすべての Delimiter と Piece を順にリストアップする必要があります。  Activity は別のグローバルノードにあるため、Node には定数を追加します。  Piece と Delimiter の既定値は、それぞれ 1 と “^” なので、そのままにしています。

ステップ 6c:

ここはまだお見せできるものがありません。 

Row ID セクションを定義する必要があるケースの例を見たくてしょうがない、という皆さまのお気持はよく分かります。  後ほどすべての例を取り込んだ zip ファイルをお見せするときに、Mapping.RowIdSpec という名前の例を入れておきますので、それを見てご確認ください。

それでは、インデックスマップに対してステップ 6 を実行します。

ステップ 6

Map2 を作成し、Global Name を「^index」に設定します。

ステップ 6a

インデックスグローバルには、定数が 2 個、IdKey フィールドが 2 個、インデックス付きフィールド First_Name が 1 つ、と添え字は全部で 5 つあります。  ^mapping グローバルと ^index グローバルを見ると、First_Name の値が違うことが分かります。  ^mapping グローバルでは、名前に大文字と小文字が混ざっている一方で、^index グローバルの名前は大文字だけが使われています。  Caché には、文字列データの変換に使えるコレーション機能がいくつか備え付けられています。  使用できる各機能については、こちらのリンクをご覧ください。

http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQL_basics#GSQL_basics_collation

Caché SQL Storage では、コレーションのレガシータイプが必要になる可能性が強いです。

http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQL_basics#GSQL_basics_collationlegacy

ここで使用する照合機能がプロパティ定義の照合と一致するものであることは極めて重要です。  インデックスマップがクエリオプティマイザーに使用されない最も一般的な理由は、これらが一致しないということがあるためです。  %String のデフォルトの照合は SQLUPPER となっていますが、マッピングで照合機能を提供しないことは、EXACT 照合を使うのと同じであることを覚えておきましょう。

ステップ 6b

^index グローバルにはデータがないため、ここで定義するものはありません。

ステップ 6c

もうお分かりでしょう、ここもなしです。

ステップ 7:

Compilation started on 08/22/2016 15:42:16 with qualifiers 'fck /checkuptodate=expandedonly'
Compiling class Mapping.Example2
Compiling table Mapping.Example2
Compiling routine Mapping.Example2.1
Compilation finished successfully in 0.144s.

SELECT Family_ID, Person_ID, First_Name, Last_Name, Relation, Activity

FROM Mapping.Example2

Family_Id            Person_Id           First_Name        Last_Name         Relation               Activity

1                              1                              Brendan               Bannon                 Father                   Rock Climbing

1                              2                              Sharon                  Bannon                 Mother                 Yoga

1                              3                              Kaitlin                    Bannon                 Daughter             Lighting Design

1                              4                              Melissa                 Bannon                 Daughter             Marching Band

1                              5                              Robin                    Bannon                 Daughter             Reading

1                              6                              Kieran                   Bannon                 Son                        Marching Band

今回、Storage Definition はスキップしておきます。  確認したい方は、mapping.example2.zip をダウンロードして、XML ファイルを読み込んでください。

0
0 284
記事 Mihoko Iijima · 2月 12, 2021 3m read

これは InterSystems FAQ サイトの記事です。

以下例のクラスメソッド getLatestID() のように ObjectScript のクラスメソッドを用意します。
返したい値を戻り値に指定し、SQLストアドプロシージャ(SqlProc)キーワードを指定するだけで値が返せます。

ClassMethod getLatestID() As %Integer [ SqlName = getLatestID, SqlProc ]
{
    set latestID=$Order(^ISJ.TestClass1D(""),-1)
    quit latestID
}

 

操作を試す場合は、以下のクラス定義をご準備ください。

Class ISJ.TestClass1 Extends (%Persistent, %Populate)
{
Property name As %String;

ClassMethod getLatestID() As %Integer [ SqlName = getLatestID, SqlProc ]
{
    set latestID=$Order(^ISJ.TestClass1D(""),-1)
    quit latestID
}
}
0
0 424
記事 Toshihiko Minamoto · 1月 26, 2021 7m read

2019年 10月 17日

Anton Umnikov
InterSystems シニアクラウドソリューションアーキテクト
AWS CSAA、GCP CACE

AWS Glue は、完全に管理された ETL (抽出、変換、読み込み) サービスです。データの分類、クリーンアップ、強化、そして様々なデータストア間でデータを確実に移動させるという作業を簡単にかつコスト効率の良いかたちで行えるようにするものです。

InterSystems IRIS の場合、AWS Glue を使用すれば、大規模なデータをクラウドとオンプレミスのデータソースの両方から IRIS に移動させることができます。 ここで考えられるデータソースは、オンプレミスのデータベース、CSV、JSON、S3 バケットに保管されている Parquet ファイルならびに Avro ファイル、AWS Redshift や Aurora といったクラウドネイティブのデータベースを含みますが、これらに限定されません。

本記事では、読者の皆さんが、AWS Glue について少なくとも AWS Glue の入門チュートリアル を完了している程度の基本的な知識をお持ちであるという前提で話を進めていきます。 InterSystems IRIS をデータターゲット、すなわち「データシンク」として使用するかたちで AWS Glue Jobs を設定する際の技術的な側面に着目します。

画像ソース https://docs.aws.amazon.com/glue/latest/dg/components-key-concepts.html
 

AWS Glue Jobs は「サーバーレス」で実行されます。 ジョブの実行に必要なリソースはすべて、ジョブが実際に実行されている間だけ AWS により動的にプロビジョニングされ、ジョブが完了すると同時に破棄されます。つまり、必要なインフラストラクチャにかかる継続的なコストをプロビジョニングしたり、管理したり、負担したりすることが不要であり、ジョブが実際に実行されている間だけ請求が発生するため、ジョブのコードを書くことだけに労力を費やすことができます。 「アイドル」時間中は、S3 バケット、ジョブコードの保管、および設定に使用される以外のリソースが消費されることはありません。

通常、Glue Job は動的にプロビジョニングされる Apache Spark で実行され、また PySpark コードで記述されますが、選択肢は他にも複数存在します。 Glue Job は、データがデータソースから抽出される _ _the "Extract"、Glue API を使ってビルドされる一連の "Transformations"、そして最後に最終変換データがターゲットシステムに書き込まれる "Load" または _「シンク」_の各部分で構成されます。

AWS Glue が IRIS と対話できるようにするには、以下を確認する必要があります。

  • Glue から関連する IRIS インスタンスへのネットワークアクセスが確立されている
  • Glue Job は IRIS JDBC ドライバーの JAR ファイルにアクセスできる
  • Glue Job は InterSystems IRIS JDBC と互換性のある API を使用している

それでは、必要な各ステップを詳しく見ていきましょう。

IRIS との接続を確立する

AWS コンソールで、AWS Glue->Connections->Add Connection、と順に選択します。

接続名を入力し、Connection Type に「JDBC」を選択します。

JDBC の URL 欄に、IRIS インスタンスの JDBC 接続文字列、ユーザー名、パスワードを入力します。

次のステップは非常に大切で、_Glue がそのエンドポイントを IRIS インスタンスと同じ VPC に配置することを確認する必要があります。_IRIS インスタンスの VPC と Subnet を選択します。 すべての TCP ポートにおいて自己参照する受信規則が設定されたセキュリティグループであれば、どれでも使用できます。 (IRIS インスタンスのセキュリティグループなど)

JDBC ドライバーへのアクセス権を持つ IAM ロール

まだ行っていない場合は、S3 バケットに IRIS JDBC ドライバーの JAR ファイル「intersystems-jdbc-3.0.0.jar」をアップロードしてください。 この例では、s3://irisdistr バケットを使用しています。 アカウントによって異なると思います。

_そのファイルにアクセスできる (Glue Job の) IAM ロール_を Glue がスクリプトやログなどを保管するのに使用する別の S3 バケットと一緒に作成する必要があります。

それが JDBC ドライバーのバケットに対して読み取りアクセスを持っていることを確認してください。 今回は、このロール (GlueJobRole) と事前定義されている AWSGlueServiceRole にすべてのバケットへの読み取り専用アクセスを与えています。 このロールのアクセス権限はさらに制限することができます。

Glue Job の作成と設定

新規 Glue Job を作成します。 先ほどのステップで作成した IAM ロールを選択します。 それ以外はすべてデフォルトのままにします。

「Security configuration, script libraries, and job parameters (optional)」で、「Dependent jars path」を  S3 バケット内にある intersystems-jdbc-3.0.0.jar の場所に設定します。

 

ソースには、既存のデータソースの 1 つを使用します。 先ほど触れたチュートリアルの内容を実行された方は、少なくとも 1 つはお持ちのはずです。

「Create tables in your data target」オプションを使い、前のステップで作成した IRIS への接続を選択します。 それ以外はすべてデフォルトのままにします。

ここまでの手順を正しく行っていれば、下のような画面が表示されるはずです。

 

もう少しです! IRIS にデータを読み込むには、スクリプトに小さな変更を 1 つだけ加える必要があります。

スクリプトの調整

Glue が生成したスクリプトには、Spark を対象とした AWS の専用拡張機能 AWS Glue Dynamic Frame が使用されています。 ETL ジョブにいくつかのメリットがあるほか、AWS がマネージドサービスオファリングを提供していないデータベースにはデータが書き込まれないようにしてくれます。

ここで良いお知らせです。データベースにデータを書き込む時点では、「ダ―ディ」なデータに対してスキーマを強制しないなど、Dynamic Dataframe が提供する利点はすべて不要となりました (データを書き込む段階でデータは「クリーン」と判断されるため)。また、Dynamic Dataframe を AWS に管理されるターゲットだけでなく IRIS にも対応できる Spark のネイティブ Dataframe に変換するという作業も簡単に行えます。

変更が必要な行は、上の画像で示す40行目です。 最終行の 1 つ前の行です。

以下のように変更する必要があります。

#datasink4 = glueContext.write_dynamic_frame.from_jdbc_conf(frame = dropnullfields3, catalog_connection = "IRIS1", connection_options = {"dbtable": "elb_logs", "database": "USER"}, transformation_ctx = "datasink4")
dropnullfields3.toDF().write \
    .format("jdbc") \
    .option("url", "jdbc:IRIS://172.30.0.196:51773/USER/") \
    .option("dbtable", "orders") \
    .option("user", irisUsername) \
    .option("password", irisPassword) \
    .option("isolationlevel","NONE") \
    .save()

irisUsername と irisPassword には、IRIS JDBC への接続に使うユーザー名とパスワードが入ります。

注意: パスワードは絶対にソースコードに保管してはいけません!! AWS Secrets Manager などのツールを使用することをおすすめしますが、本記事でセキュリティの詳細をそこまで深く掘り下げるのは、割愛させていただきます。 AWS Glue における AWS Secrets Manager の使用法については、こちらの記事をおすすめします。

「Run Job」ボタンをクリックした後は、AWS Glue が ETL を実行してくれるのでリラックスしてお待ちください。

まあ最初はエラーがいくつか出ると思いますが... 珍しいことではないと思います。 入力ミスがあったり、セキュリティグループのポートが間違っていたり... AWS Glue は CloudWatch を使って、すべての実行データやエラーログを保存します。 問題の原因については、ロググループ /aws-glue/jobs/error と _ /aws-glue/jobs/output_ を参照してください。

クラウドで ETL をお楽しみください!

-Anton

0
0 716
お知らせ Mihoko Iijima · 1月 11, 2021

開発者の皆さん、こんにちは!

第9回のマルチモデルコンテストの 続報 📣 の「テクノロジーボーナス」についてご紹介します。

  • InterSystems Globals (key-value)
  • InterSystems SQL
  • InterSystems Objects 
  • Your data model
  • ZPM Package deployment
  • Docker container usage

詳細は以下ご参照ください。<--break->

InterSystems Globals (key-value) - 2 points

InterSystems グローバルは、InterSystems IRIS に任意のデータを格納するために使用される多次元スパース配列です。
各グローバル・ノードはキーとみなされ、値(バリュー)を設定することができます。InterSystems IRIS は、グローバルを管理するための ObjectScript のコマンドや Native API を含む一連の API を提供しています。

ObjectScript または Native API を介してグローバルを使用すると、2 ポイント獲得できます。

ツール:

ドキュメント:

記事:

0
0 125
記事 Toshihiko Minamoto · 12月 23, 2020 3m read

新しい動的 SQL クラス(%SQL.Statement および %StatementResult)のパフォーマンスは %ResultSet より優れてはいますが、%ResultSet の使用方法をせっかく学習したので、しばらくの間新しい方を使用せずにいましたが、 やっとチートシートを作ったので、新しいコードを書いたり古いコードを書き直す際に役立てています。 皆さんのお役に立てればいいなと思っています。

次に示すのは、私のチートシートの詳細版です。

<th>
  %ResultSet::%New()
</th>

<th>
  %SQL.Statement::%New()
</th>
<td>
     Prepare() インスタンスメソッドを呼び出す
</td>

<td>
     %Prepare() インスタンスメソッドを呼び出す
</td>
<td>
     前のステップがステータスを返すので、それを確認
</td>

<td>
     前のステップがステータスを返すので、それを確認
</td>
<td>
     Execute() インスタンスメソッドを呼び出す
</td>

<td>
     %Execute() インスタンスメソッドを呼び出す
</td>
<td>
     前のステップがステータスを返すので、それを確認
</td>

<td>
     前のステップが %SQL.StatementResult のインスタンスを返すので、次のステップでそれを使用
</td>
<td>
     Next() インスタンスメソッドを呼び出す(while ループでイテレートなど)
</td>

<td>
     %Next() インスタンスメソッドを呼び出す(while ループでイテレートなど)
</td>
<td>
     GetData() インスタンスメソッドを呼び出して、列番号で列を取得
</td>

<td>
     %GetData() インスタンスメソッドを呼び出して、列番号で列を取得
</td>
<td>
     %Get() インスタンスメソッドを呼び出して、列番号で列を取得
</td>
1
2
3
4
5
6
7
   Get() または Data() インスタンスメソッドを呼び出して、列番号で列を取得

 

そして、これが私が実際に使用している簡易版チートシートです。

<th>
  %ResultSet::%New()
</th>

<th>
  %SQL.Statement::%New()
</th>
<td>
     Prepare()
</td>

<td>
     %Prepare()
</td>
<td>
     ステータスを確認
</td>

<td>
     ステータスを確認
</td>
<td>
     Execute()
</td>

<td>
     %Execute()
</td>
<td>
     ステータスを確認
</td>

<td>
     %Execute の戻り値を次のステップに使用
</td>
<td>
     Next()
</td>

<td>
     %Next()
</td>
<td>
     GetData()
</td>

<td>
     %GetData()
</td>
<td>
     %Get()
</td>
1
2
3
4
5
6
7
   Get() または Data()
0
0 226
記事 Mihoko Iijima · 12月 20, 2020 3m read

これは InterSystems FAQ サイトの記事です

揮発性テーブル(多数のINSERT、DELETEが行われるテーブル)では、ビットマップ・インデックス用ストレージは徐々に効率が低下する可能性があります。

例えば、以下の定義からなるデータが数千件あり、一定期間保持した後 TRUNCATE TABLE で一括削除を行うオペレーションが繰り返し行われているとします。

Class MyWork.MonthData Extends (%Persistent, %Populate)
{
/// 満足度
Property Satisfaction As %String(VALUELIST = ",満足,やや満足,やや不満,不満,");
/// 年齢
Property Age As %Integer(MAXVAL = 70, MINVAL = 20);
Index AgeIdx On Age [ Type = bitmap ];
}

 

INSERT によってできたビットマップ・インデックスのストレージのイメージ(一部)は以下の通りです。

0
0 299
記事 Megumi Kakechi · 12月 17, 2020 1m read

これはInterSystems FAQ サイトの記事です。

選択性(または Selectivity)の数値とは、カラムに対するユニークデータの割合を示す数値です。

例) Person テーブルの個別値である PID カラム、住所のうち都道府県名が入る Pref カラム
 Pref カラムの選択性 = 約 2%
 PID カラム(ユニーク属性のカラム)の選択性 = 1

InterSystems製品のクエリオプティマイザは、クエリ実行経路(プラン)とインデックスの選択を決定するため、エクステントサイズ(テーブル内のレコード数)と選択性の数値を使用します。

詳細は下記ドキュメントページをご参照ください。

テーブルの最適化【IRIS】

テーブルの最適化

なお、選択性の数値は、テーブル単位に計算するまで設定されていません。

計算方法については関連トピックをご参照ください。

【関連情報】(コミュニティ/FAQトピックをリンクしています)

0
0 386
記事 Hiroshi Sato · 12月 16, 2020 2m read

これはInterSystems FAQ サイトの記事です。
 

SQLインジェクションに関しては、様々なWebサイトで対策等が公開されていますが、InterSystems SQLを使ったアプリケーションでも、他のRDBMSと同様にそれらの対策を適切に実施することで、SQLインジェクションは防げると考えられます。

さらに、InterSystems Data Platform(以下IRIS)の場合、一般的なRDBMSに比較して、SQLインジェクションを実行しづらい、幾つかの施策が組み込まれています。

0
0 239
記事 Megumi Kakechi · 12月 15, 2020 1m read

これはInterSystems FAQ サイトの記事です。

%SYSTEM.SQL クラスの Purge* メソッドを使用して削除することが可能です。

※各メソッドの詳細は、以下ドキュメントをご参照ください。

%SYSTEM.SQLクラスについて【IRIS】

%SYSTEM.SQLクラスについて

①システム内のすべてのクエリキャッシュを削除する場合

Do $SYSTEM.SQL.PurgeAllNamespaces()


②ネームスペース内のクエリキャッシュを削除する場合

// ネームスペース内のすべてのクエリキャッシュを削除する
Do $SYSTEM.SQL.Purge()
// 日付指定してクエリキャッシュを削除する場合
// 以下は、過去30日間に使用されていないクエリキャッシュを削除

Do $SYSTEM.SQL.Purge(30) 

③クエリキャッシュを指定して削除する場合

Do $SYSTEM.SQL.PurgeCQClass("%sqlcq.USER.cls13")


④特定テーブルのクエリキャッシュを削除する場合

Do $SYSTEM.SQL.PurgeForTable("MedLab.Patient")
0
0 525
記事 Minoru Horita · 12月 14, 2020 14m read

優れた執筆者は、題名で読者を引き込み、答えを記事のどこかに隠すべきだと考えられています。  だとすると、私は出来の悪い執筆者ということになってしまいます。私の自尊心は見知らぬインターネットユーザーの皆さんのご意見にかかっていますので、お手柔らかにお願いします。

私の同僚 Brendan が Developer Community に記載されている情報をレビューしていたとき、Tune Table について書き忘れがあったことに気が付いたのです!  これはクエリパフォーマンスにおいて 2番目に重要なツール (1 番はインデックス、インデックスがないと処理が遅い) なので、時間をかけて理解しておく価値があります。  今回使用する例の多くは完全なものではありませんが、詳細は簡単に入力できます。 すべての例において、特に明記が無い限り、フィールドはすべて個別にインデックスが作成されているものと想定してください。

それでは、以下のクエリがあるとしましょう。
SELECT *
FROM   People
WHERE  Home_State = 'MA'
       AND PersonId = '123-45-6789' 

どのインデックスを使うべきでしょうか?  冗談なしで、一旦読むのをやめて、答えを聞かせてください。  大声で答えてください。  同僚の皆さんに変な人だなんて思われませんよ、大丈夫です。
正解は、もちろん、PersonId のインデックスです。  なぜこれが正解なのか?  各 State (州) には何百万もの人々が生活している可能性がある中で、PersonId はほぼ一意の数値 (的な) 識別子であることが分かります。  ここで理解する必要があるのは、この答えが分かるのは、列の識別子を見るとスキーマに関する情報がある程度は分かるためである、という点です。  コンピューターにとって、これらの名前は何の意味もなしません。  この点を分かりやすく説明するために、こちらのクエリを見てみましょう。
SELECT *
FROM   TheTable
WHERE  Field1 = 32
       AND Field2 = 0 

使うのはインデックスは Field1?それとも Field2?  上のクエリは、クエリオプティマイザによって最適化されるようなクエリです。  この場合はどのようにインデックスを選べばいいのでしょう?  適切な決断をするにはデータについて知る必要があります。  これを解決するのが Tune Table です。 

Tune Table は、テーブル内のデータサンプルを調べ、テーブルに関する統計を保管することで、クエリオプティマイザが適切な決断をできるようにします。  今まで Tune Table を実行したことが無い場合は、クエリがランダムでも効果的にインデックスを選択しているということです (厳密には違いますが、ほぼそういうことです)。  実際、クエリが遅いことについてサポートに電話されることがあれば、まず最初に「Tune Table は実行されましたか?」と聞かれると思います。  ‘はい’ と言えるようにしておいてください。 

Tune Table の実行方法
Tune Table を実行するには、ターミナルに移動して、次を実行します。
d $SYSTEM.SQL.TuneTable(<Table>)
これに渡せるフラグは沢山ありますが、私のおすすめはこちらです。

d $SYSTEM.SQL.TuneTable(<Table> ,1,1,.msg,1)


 

これらのフラグにより、クラス定義が更新され、新しい値が表示されるほか、最後のフラグによりクラスが最新の状態に維持されます。  クエリが Tune Table の新しい情報を利用できるようにするには、すべての埋め込みクエリをコンパイルし、キャッシュされているクエリを消去する必要があります。  フラグに関する詳細は、こちらをお読みください。
https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQL#METHOD_TuneTable

Tune Table の機能
Tune Table の実行ステップを紹介します。  まず最初に、SELECT COUNT(*) を実行してテーブル内の行数を計算します。  その行数を基に、サンプルとして使用する行数を決定します (漸近的には行数の平方根に似ています)。  そして、テーブルで定義されている各 ID に対し、加重サンプルを適切に判断した上で、その行をサンプルに含めるかどうかを決定します。  このサンプルを使って統計を算出し、それを使ってクエリを最適化します。

ここには重要なポイントが 2 つあります。  1 つ目はカウントを得る必要があること。  2 つ目は、テーブル内の各 ID を取得できる必要があることです。  この結果、エクステントインデックスがあることにより、Tune Table の実行が大幅に改善されるのです。  テーブルがビットマップに対応している (つまり正の整数 IDKEY がある) 場合は、ビットマップのインデックスがあれば、自動的にビットマップのエクステントを得ることができます。 


Tune Table はどのような指標を測定するのか?  年代順に古い方から説明していきます。

2 つの OG (旧世代の指標)

次に紹介する 2 つの指標は Tune Table の最も古い指標です。  どれだけ古いと思いますか?  私が高校に入学する前 (1999年) に追加されたものです。  Y2K 以前の Caché を実行している方がおられましたら、私までメールをください。是非、お話しさせていただきたいです。
Extent Size は、テーブル内の行数を測定します。 

これは一番理解しやすい指標で、JOIN を実行する順序を決定するのに便利です。  では、下のクエリがあるとしましょう。
SELECT *
FROM   Table1 T1
       JOIN Table2 T2
         ON T1.Field1 = T2.Field1 

T1 を読み取ってから、T2 のフィールドで JOIN を実行するべきか、またはその逆にするべきか? Extent Size は、行の数が少ない方のテーブルを示すので、どちらのテーブルから始めるべきなのかが分かります。  極端なケースとして、T1 には 100 億行あり、T2 には 100 行しかないと仮定します (T1 はこれまで病院に行った回数の合計を示すテーブルで、T2 は現在病院にいる患者の数を示すテーブルだと考えてください)。 この場合は、小さい方のテーブルから始め、大きな方で JOIN を実行すると良いでしょう。それは、JOIN を実行すると、読み取る必要のある行の数が制限されるためです。 

Selectivity は、指定されたフィールドの値の一意性を測定します。
この指標を理解するにはもう少し説明が必要です。  考え方は 2 種類あります。

  1. 1/x。x はフィールドに設定され得る値の数。
  2. クエリ SELECT * FROM T WHERE field=? の実行結果として返されるテーブルの平均パーセンテージ

いずれの説明もこれだけでは分かりにくいので、いくつか例を挙げてはっきりさせたいと思います。 

先ほどのクエリに、Home_State というフィールドがありました。  (アメリカの場合だと) そのフィールドの Selectivity は 2.0% になります。  なぜかと言いますと、 アメリカには 50 の州があるため、1/50 = .02 = 2% という計算になります。  これは簡単ですね。  しかし、分布はどうなのか?  明らかに、複数の州で人口が均等に分布しているはずがありません!  それは事実なのですが、Selectivity はそれを考慮しません。 均等な分布は、ほとんどのクエリにおいて適切な予測とされる上に、計算も簡単になるため、クエリはよりスピーディにコンパイルします。
 

もう 1 つの例として、PersonId について考えます。  人は亡くなったり、新しく生まれたりするので、 PersonId はたまに再利用する必要があるかもしれません。なので、その数字は完全に一意という訳でははありません。  しかし、一意であることに変わりはありません。  そのため、.00001% の Selectivity が見られる場合があるかもしれません。  それは非常に適切な Selectivity と言えます!  クエリ SELECT * FROM People WHERE PersonId = ? は、 実行される度に 1 つまたは 2 つの行を返します。  一般的に、Selectivity は低いほど良いとされています。  しかし、これには例外があります…

一意のフィールドがあるとしたらどうでしょう。ID はいい例ですね。  一意のフィールドの Selectivity は 1 です。  ちなみに、これまで述べた Selectivity はすべてパーセンテージです。  1 はパーセンテージではなく、行数です。  したがい、クエリ SELECT * FROM People WHERE ID = ? は、 いつも 1 行を返します。 

1 つのフィールドについて沢山の情報をカバーしましたが、おそらく一番重要な情報であると言えるでしょう。  原則として、パーセンテージは低い方が良く、1 が最適な値です。 

「昔から」存在していた指標

次に紹介する統計は、いつも予測されてはいたものの、バージョン 2013.1 までは明示的に測定されていませんでした。
Block Count – 各マップ (インデックスとマスターマップ) がディスク上で占めるブロックの数を測定します。 

Block Count は長年に渡って予測してきましたが、2013 年になって初めて明示的に測定する決断をしました。  Extent Size があるので不必要に思えるかもしれませんが、他にも実用性があります。  こちらのクエリ
SELECT COUNT(*) FROM MyTable
を説明すると理解に役立つでしょう。
各フィールドにインデックスが付いているとしたら、どれを読み取ればいいでしょうか?  読み取りたいのは、ディスク上で占めるスペースが一番少ないフィールドです。  この指標はその情報を明示的に示してくれます。  より複雑な状況でインデックスのかたまりを読み取る際のコストを予測するときにも表示されます。  通常、これはマップの幅を測定するのに優れている一方で、Extent Size はその長さを測定するのに優れています。

Block Count は、親 / 子関係を使用する場合にも便利です。  この場合は、(デフォルトで) 両方のテーブルが同じグローバル変数に格納されます。しかし、Block Count がなければ、クエリオプティマイザは、潜在的に小さな親テーブルで読み取られたマップには膨大な数の子が含まれているために、実はとても大きなマップである可能性があるということを全く知ることができません。 実に、これは私たちが新規開発において親 / 子関係の使用を一切おすすめしていない理由の 1 つです (どうしてもという場合は、サポートにお電話の上、ご相談ください!)。
 

Brendan からのメモ / トリビア: Extent Size、Selectivity、Block Count は FDBMS が使われていた時代、すなわち 1992 年には既に存在していたのですが、当時は手動で設定する必要がありました。


すべてを一変させた指標

バージョン 2014.1 では、フィールドの 1 つの値が過剰に大きな割合を占めていないかどうかを判断するための指標を追加しました。  これにより、Selectivity を計算する方法が大幅に変わりました。

Outlier Selectivity (外れ値の選択性) -  テーブル内で過剰な割合を占めるフィールドの選択性。

外れ値を伴う状況を理解するために、病院について考えてみましょう。  病院は一般的に地域のコミュニティにサービスを提供するものです。  Home_State フィールドは、そのほとんどの値が病院のある州で同じになります。  では、MA (マサチューセッツ州) の病院について考えます (私が住んでいる州なので)。  私の地元の病院は、患者の 90% が MA の人たちです。それ以外は他の州から来た人たちや州を訪れている人たちです。  Home_State=’MA’ を検索することは選択的でない一方で、それ以外の Home_State を検索することは選択的である、ということを示す必要があります。  これを実現するのが Outlier Selectivity です。 
例えば、上述した Home_State フィールドだと、Tune Table はその Selectivity を0.04% として算出し、Outlier Selectivity を 90%、Outlier 値 (外れ値) を ‘MA’ として示します。  通常、Selectivity は低下します。  Selectivity の計算方法が変わり、人々をビックリさせたクエリプランもいくつか考案されたこともあり、これは大きな変化であったと言えます。  バージョン 2014.1 全体を更新する予定の方は、この点に注意してください。

最後の指標

この最後の指標は、一時ファイルのサイズをより正確に予測することを目的に、バージョン 2015.2 で追加したものです。
Average Field Size – フィールドの平均サイズ。

これも簡単に理解できる指標です。  サンプルを取る間に、テーブル内の各フィールドの平均サイズを計算してくれます。 これは一時ファイルのサイズを判断するのに便利です。また、これにより、オプティマイザは一時ファイルをメモリ内で構築できるのか (ローカル配列)、またはディスクベースの構造 (プライベートグローバルを処理する) が必要なのかを判断できます。

実用的な検討事項

次に考えるのが「どれくらいの頻度で実行する必要があるのか?」という疑問です。  これといった答えはないのですが、 少なくとも (絶対に!!!) 一度は実行してください。でなければ、自分で値を用意しなくてはいけなくなります。  その後は、もう二度と実行しなくてもよいかもしれません。  現在のパフォーマンスに満足なら、実行する理由はありません、というのが一般的な答えでしょう。  実に、データベースの成熟度が高く、テーブル内のデータも安定したペースで成長しているのなら、今後もおそらく適切な値が得られるでしょう。 

もし、テーブルの変化と共に SQL のパフォーマンスが劣化することがあれば、Tune Table を再度実行することをおすすめします。  また、新しいデータが多く、テーブルが大幅に変化している場合は、Tune Table を実行したほうが無難かもしれません。  しかし、これは「すべてのクエリが遅い」という状況にあれば予測できることです。  この状況に陥っている方は、WRC までお問い合わせください。システム設定を確認させていただきます。

Tune Table を実行する場合は、調整するテーブルに関連しているすべてのテーブルに対して実行してください。 そうしないと、テーブル間で Extent Sizes と Block Counts を比較てきなくなります (これによりクエリのパフォーマンスが低下します)。
2016.2 よりも新しいバージョンをご利用の方は、Tune Table を実行する前にクエリプランをフリーズできる Frozen Query Plans をご利用いただけます。  このプラクティスは、Tune Table を実行したときに、新しいプランが役に立つと判断した場合にそれだけを利用できるという機能があるため、強くお奨めしています (%NOFPLAN を使ってクエリをテストすれば、新しいプランが役に立つのかどうかを確認できます)。 

Frozen Query Plans についてはもう 1 つ大切なことがあり、このテクノロジーを使えば、Tune Table を実行するときに、システム上で既に実行したクエリをもう一度実行してしまうことを避けられます。  これは、現在のパフォーマンスに影響を与えることを心配せずに、Tune Table を実行するための手段です!  もう言い訳はできません! Frozen Query Plans の詳細に興味がある方は、私が以前行ったウェビナーをご覧ください:
https://learning.intersystems.com/course/view.php?id=969

最後になりましたが、クエリパフォーマンスについては、Tune Table の統計を把握しておくことが大切です。  Tune Table は、クエリオプティマイザが適切な判断をするのに必要な情報を与えてくれます。  既にテーブルにインデックスを追加されている方は、次のステップとして Tune Table を実行すれば、クエリを超高速化できること間違いなしです。

今回の記事で使用しました SQL フォーマッタ (http://dpriver.com/pp/sqlformat.htm) を私に紹介してくれた Aaron Bentley に感謝の意を送りたいと思います。
 

また、私の記事を実際に理解できる内容に編集してくれる Brendan Bannon にも感謝いたします。

0
0 312
記事 Hiroshi Sato · 12月 8, 2020 1m read

これはInterSystems FAQ サイトの記事です。
 

連番を生成する関数($INCREMENT)を使用してユニークな番号を自動付番することができます。

SQLのSELECT文で使用する場合には OracleのSequence相当の機能を実装したクラスを利用する方法があります。

サンプルを以下のGitHubリポジトリより取得することができます。

OracleのSequence機能を実装するサンプル

0
0 478
記事 Mihoko Iijima · 12月 7, 2020 2m read

これは、InterSystems FAQサイトの記事です。

SELECT ... FOR UPDATE は明示的な行ロック取得の方法として多くの RDBMS で実装されているため、この機能を使われているケースも多いと思います。

このシンタックスは InterSystems 製品ではエラーにはなりませんが、期待されるような行ロックの取得は行われません。

この記事では同等の機能を実現する方法をご紹介します。

DECLARE CURSOR C1 IS
SELECT Name FROM Person WHERE Name LIKE 'A%' FOR UPDATE
OPEN C1
LOOP FETCH C1 INTO name 
... name を表示
... 終了ならLOOPをEXIT
END LOOP
CLOSE C1

 

上記のようなSQL文は、下記のSQL文で代替可能です。

※下記2行目のUPDATE文の実行により対象行に対して排他ロックがかかるため、他DBの動作と異なる点ご注意ください。
 

0
0 491
記事 Toshihiko Minamoto · 11月 18, 2020 5m read

クラス、テーブル、グローバルとその仕組み

InterSystems IRIS を技術的知識を持つ人々に説明する際、私はいつもコアとしてマルチモデル DBMSであることから始めます。

個人的には、それが(DBMSとして)メインの長所であると考えています。 また、データが格納されるのは一度だけです。 ユーザーは単に使用するアクセス API を選択するだけです。

  • データのサマリをソートしたいですか?SQL を使用してください!
  • 1 つのレコードを手広く操作したいですか?オブジェクトを使用してください!
  • あなたが知っているキーに対して、1 つの値にアクセスしたりセットしたいですか? グローバルを使用してください!

これは短く簡潔なメッセージで、一見すると素晴らしく聞こえます。しかし、実際には intersystems IRIS を使い始めるたユーザーには クラス、テーブル、グローバルはそれぞれどのように関連しているのだろうか? 互いにどのような存在なのだろうか? データは実際にどのように格納されているのだろうか?といった疑問が生じます。

この記事では、これらの疑問に答えながら実際の動きを説明するつもりです。

パート 1. モデルに対する偏見。

データを処理するユーザーは多くの場合、処理対象のモデルに偏見を持っています。

開発者はオブジェクトで考えます。 このようなユーザーにとって、データベースとテーブルは CRUD(Create-Read-Update-Delete、ORM の使用が望ましい)を介して操作する箱のようなものですが、その基礎となる概念モデルはオブジェクトです(これは主に私たちのような多くのオブジェクト指向言語の開発者に当てはまります)。

一方、リレーショナル DBMS に多くの時間を費やしているデータベース管理者は往々にしてデータをテーブルと見なしています。 この場合、オブジェクトはレコードの単なるラッパー扱いです。

また、InterSystems IRIS では永続クラスはデータをグローバルに格納するテーブルでもあるため、いくつかの説明が必要になります。

パート 2. 具体例

次のような Point クラスを作成したとします。

Class try.Point Extends %Persistent [DDLAllowed]
{
    Property X;
    Property Y;
}

次のように DDL/SQL を使用して同じクラスを作成することもできます。

CREATE Table try.Point (
    X VARCHAR(50),
    Y VARCHAR(50))

コンパイル後、新しいクラスがグローバルにネイティブに格納されているデータをカラム(またはオブジェクト指向のユーザーの場合はプロパティ)にマッピングするストレージ構造を自動生成します。

Storage Default
{
<Data name="PointDefaultData">
    <Value name="1">
        <Value>%%CLASSNAME</Value>
    </Value>
    <Value name="2">
        <Value>X</Value>
    </Value>
    <Value name="3">
        <Value>Y</Value>
    </Value>
</Data>
<DataLocation>^try.PointD</DataLocation>
<DefaultData>PointDefaultData</DefaultData>
<IdLocation>^try.PointD</IdLocation>
<IndexLocation>^try.PointI</IndexLocation>
<StreamLocation>^try.PointS</StreamLocation>
<Type>%Library.CacheStorage</Type>
}

ここでは何が起きているのでしょうか?

下から順番に説明します(太字の単語が重要です。残りは無視してください)。

  • Type - 生成されたストレージタイプ。この場合は永続オブジェクトのデフォルトストレージです。
  • StreamLocation - ストリームを格納するグローバルです。
  • IndexLocation - インデックス用のグローバルです。
  • IdLocation - ID の自動インクリメントカウンターを格納するグローバルです。
  • DefaultData - グローバルの値をカラム/プロパティにマッピングするストレージの XML 要素です。
  • DataLocation - データを格納するグローバルです。

ここでは「DefaultData」が PointDefaultData となっていますので、その構造をもう少し詳しく見てみましょう。 基本的に、グローバルノードは次の構造を持っていると言われています。

  • 1 - %%CLASSNAME
  • 2 - X
  • 3 - Y

したがって、グローバルは次のようになると予想されます。

^try.PointD(id) = %%CLASSNAME, X, Y

しかし、グローバルを出力すると空になります。ここではデータを追加していなかったためです。

zw ^try.PointD

オブジェクトを 1 つ追加しましょう。

set p = ##class(try.Point).%New()
set p.X = 1
set p.Y = 2
write p.%Save()

すると、グローバルはこのようになります。

zw ^try.PointD
^try.PointD=1
^try.PointD(1)=$lb("",1,2)

ご覧のように、期待する構造 %%CLASSNAME, X, Y はオブジェクトの X プロパティと Y プロパティに対応する $lb("",1,2) とセットになっています(%%CLASSNAME はシステムプロパティですので無視してください)。

次のように SQL を使用してレコードを追加することもできます。

INSERT INTO try.Point (X, Y) VALUES (3,4)

すると、グローバルの内容は次のようになります。

zw ^try.PointD
^try.PointD=2
^try.PointD(1)=$lb("",1,2)
^try.PointD(2)=$lb("",3,4)

つまり、オブジェクトまたは SQL を介して追加するデータは、ストレージ定義に従ってグローバルに格納されます(補足:PointDefaultData の X と Y を置き換えることでストレージ定義を手動で変更できます。その場合に新しいデータがどうなるかを確認してください!)。

では、SQL クエリを実行したい場合はどうなるのでしょうか?

SELECT * FROM try.Point

これは ^try.PointD グローバルを反復処理し、ストレージ定義(正確にはその PointDefaultData 部分)に基づいてカラムにデータを入力する ObjectScript コードに変換されます。

今度は変更を行います。 テーブルからすべてのデータを削除しましょう。

DELETE FROM try.Point

すると、この時点でグローバルの内容は次のようになります。

zw ^try.PointD
^try.PointD=2

ここでは ID カウンターのみが残っているため、新しいオブジェクト/レコードの ID は 3 になることに注意してください。 また、クラスとテーブルは引き続き存在します。

しかし、次を実行するとどうなるでしょうか。

DROP TABLE try.Point

これはテーブルとクラスを破棄し、グローバルを削除します。

zw ^try.PointD

皆さんがこの具体例に従い、グローバル、クラス、テーブルがどのように統合され、相互に補完しているかをより深く理解できたことを願っています。 手元の仕事に適切な API を使用すれば、開発がより高速かつアジャイルになり、バグが少なくなります。

0
0 557
記事 Mihoko Iijima · 11月 10, 2020 4m read

これはInterSystems FAQ サイトの記事です

インデックスが複数定義されているクラス/テーブルへ csv 形式等のシーケンシャルファイルから大量データをデータベースに登録する際、推奨される登録方法として、データ登録時インデックスを生成させず、登録完了後に一括でインデックスを生成する 方法があります。

この方法は、新規に大量のレコードを一括登録する際に最も有効な手段となります。

<メモ>
大量のデータを追加登録する際には、既存のデータ量と新規データ量のバランスにより、この手法が有効でないケースもあります。その場合は、インデックスの再構築を範囲指定で行うこともできます。

説明に使用するクラス定義例は以下の通りです。

0
0 526
記事 Mihoko Iijima · 11月 6, 2020 2m read

これはInterSystems FAQ サイトの記事です。

SELECT のみを実行できるユーザを作成するには、ユーザかロールに対してテーブルへのアクセス権限を設定することで対応できます。

設定は、管理ポータルか、GRANT 文を実行することで設定できます。GRANT 文については、以下ドキュメントもご参照ください。
InterSystems SQL リファレンス - GRANT【IRIS】
CachéSQLリファレンス - GRANT

管理ポータルでの設定は、ユーザまたはロールの編集画面内「SQLテーブル」タブを使用します。

ユーザに直接設定する場合は以下のメニューを利用します。
  [ホーム] > [システム管理] > [セキュリティ] > [ユーザ] > [ユーザ編集] 

ロールに設定する場合は、以下のメニューを使用します。
 [ホーム] > [システム管理] > [セキュリティ] > [ロール] > 新規ロール作成
 ※ ロール名を指定し、保存するまで詳細設定画面が表示されません。
 ※ 作成したロールをユーザに付与することで、テーブルへのアクセス権限をロールで一元管理できます。 SQLテーブルでの権限設定

手順は以下のとおりです。

0
0 893
記事 Mihoko Iijima · 11月 6, 2020 4m read

これはInterSystems FAQ サイトの記事です。

テーブルチューニングを行った際に、フィールドに値がほとんど登録されていない(Null)場合や、特定の値がほとんどを占める場合、その値を[外れ値] として除外して選択性計算を行います。 また、外れ値が全レコードの何 % を占めているかの値[外れ値の選択性] として記録されます。

InterSystems 製品のクエリオプティマイザは、選択性数値とエクステントサイズを使用してクエリの経路を決定しますが、クラスクエリ、埋め込み SQL に使用しているクエリに外れ値が含まれる場合は、外れ値の選択性が自動的に考慮され、インデックスの使用有無を決定しています。

ダイナミック SQL 、ODBC/JDBC 経由でのクエリについては、外れ値が Null である場合、自動的に外れ値の選択性が考慮されますが、Null 以外の特定の値が外れ値に検出される場合は、明示的に指示を与えるまで考慮しません。

詳細は、ドキュメント(異常値に対する述語条件【IRIS】異常値に対する述語条件【Caché/Ensemble】)をご参照ください。

0
0 552
記事 Toshihiko Minamoto · 10月 27, 2020 14m read

秩序(順序)はだれにとっても必要であるが、皆が同じように秩序(順序)を理解しているわけではない (ファウスト・セルチニャーニ)

免責事項: この記事では、例としてロシア語とキリル文字を使用しますが、英語以外のロケールでCachéを使用するすべての方に関連のある記事です。この記事は主にNLS照合について言及しており、SQL照合とは異なることに注意してください。 SQL照合(SQLUPPER、SQLSTRING、照合なしを意味するEXACT、TRUCATEなど)は、値に明示的に適用される実際の関数であり、その結果はグローバルサブスクリプトに明示的に格納されることがあります。 サブスクリプトに格納されると、これらの値は当然、有効なNLS照合(「SQLおよびNLS照合」)に従うことになります。

Cachéのデータ、メタデータ、クラス、ルーチンはすべてグローバルに格納されます。 グローバルは永続的です。 グローバルノードはサブスクリプト値によって順序付けされ、検索やディスクフェッチのパフォーマンスを向上させるために、挿入された順ではなくソート順でストレージデバイスに格納されます。

USER>set ^a(10)=""
USER>set ^a("фф")=""
USER>set ^a("бб")=""
USER>set ^a(2)=""
USER>zwrite ^a
^a(2)=""
^a(10)=""
^a("бб")=""
^a("фф")=""

ソート中、Cachéは数値と文字列を区別します。2は数値として扱われ、10より前にソートされます。 コマンドZWriteおよび関数$Order$Queryは、これらのサブスクリプトが格納された順でグローバルサブスクリプトを返します。初めに空の文字列(サブスクリプトとして使用できません)、その後に負の数値、ゼロ、正の数値、そして照合で定義された順で文字列を返します(照合)。

Cachéの標準的な照合は(当然ながら)Caché標準と呼ばれており、 文字列はUnicode文字コードに従ってソートされます。

現在のプロセスのローカル配列の照合はロケールによって定義されます(管理ポータル > システム管理 > 構成 > システム構成 > 国言語設定 > Locale Definitions)。 CachéのUnicodeインストールのロシア語ロケールはruswで、ruswのデフォルトの照合はCyrillic3です。 ruswロケールで指定できる照合には、Caché標準、Cyrillic1、Cyrillic3、Cyrillic4、Ukrainian1があります。

ClassMethod ##class(%Collate).SetLocalName()は現在のプロセスのローカル配列に使用する照合を設定します。

USER>write ##class(%Collate).GetLocalName()
Cyrillic3
USER>write ##class(%Collate).SetLocalName("Cache standard")
1
USER>write ##class(%Collate).GetLocalName()
Cache standard
USER>write ##class(%Collate).SetLocalName("Cyrillic3")
1
USER>write ##class(%Collate).GetLocalName()
Cyrillic3

すべての照合には、数値を文字列としてソートする照合があります。 その照合の名前には末尾に「string」が含まれています。

USER>write ##class(%Collate).SetLocalName("Cache standard string")
1
USER>kill test

USER>set test(10) = "", test(2) = "", test("фф") = "", test("бб") = ""

USER>zwrite test
test(10)=""
test(2)=""
test("бб")=""
test("фф")=""

USER>write ##class(%Collate).SetLocalName("Cache standard")
1
USER>kill test

USER>set test(10) = "", test(2) = "", test("фф") = "", test("бб") = ""

USER>zwrite test
test(2)=""
test(10)=""
test("бб")=""
test("фф")=""

Caché標準とCyrillic3

Caché標準は、コードに従って文字をソートします。

 write ##class(%Library.Collate).SetLocalName("Cache standard"),!
 write ##class(%Library.Collate).GetLocalName(),!
 set letters = "абвгдеёжзийклмнопрстуфхцчщщьыъэюя"
 set letters = letters _ $zconvert(letters,"U")
 kill test

 //fill local array “test” with data
 for i=1:1:$Length(letters) {
     set test($Extract(letters,i)) = ""
 }

 //print test subscripts in sorted order
 set l = "", cnt = 0
 for  {
     set l = $Order(test(l))
     quit:l=""
     write l, " ", $Ascii(l),","
     set cnt = cnt + 1
     write:cnt#8=0 !
 }

USER>do ^testcol
1
Cache standard
Ё 1025,А 1040,Б 1041,В 1042,Г 1043,Д 1044,Е 1045,Ж 1046,
З 1047,И 1048,Й 1049,К 1050,Л 1051,М 1052,Н 1053,О 1054,
П 1055,Р 1056,С 1057,Т 1058,У 1059,Ф 1060,Х 1061,Ц 1062,
Ч 1063,Щ 1065,Ъ 1066,Ы 1067,Ь 1068,Э 1069,Ю 1070,Я 1071,
а 1072,б 1073,в 1074,г 1075,д 1076,е 1077,ж 1078,з 1079,
и 1080,й 1081,к 1082,л 1083,м 1084,н 1085,о 1086,п 1087,
р 1088,с 1089,т 1090,у 1091,ф 1092,х 1093,ц 1094,ч 1095,
щ 1097,ъ 1098,ы 1099,ь 1100,э 1101,ю 1102,я 1103,ё 1105,

キリル文字は、ロシア語のアルファベット順に出力されますが、「ё」と「Ё」は例外です。 これらのUnicode文字コードは順不同であり、 「Ё」は「Е」と「Д」の間、「ё」は「е」と「д」の間で照合されます。 そのため、ロシア語ロケールには独自の照合として、ロシア語のアルファベットと同じ順に並ぶ文字を使用するCyrillic3が必要となります。

USER>do ^testcol
1
Cyrillic3
А 1040,Б 1041,В 1042,Г 1043,Д 1044,Е 1045,Ё 1025,Ж 1046,
З 1047,И 1048,Й 1049,К 1050,Л 1051,М 1052,Н 1053,О 1054,
П 1055,Р 1056,С 1057,Т 1058,У 1059,Ф 1060,Х 1061,Ц 1062,
Ч 1063,Щ 1065,Ъ 1066,Ы 1067,Ь 1068,Э 1069,Ю 1070,Я 1071,
а 1072,б 1073,в 1074,г 1075,д 1076,е 1077,ё 1105,ж 1078,
з 1079,и 1080,й 1081,к 1082,л 1083,м 1084,н 1085,о 1086,
п 1087,р 1088,с 1089,т 1090,у 1091,ф 1092,х 1093,ц 1094,
ч 1095,щ 1097,ъ 1098,ы 1099,ь 1100,э 1101,ю 1102,я 1103,

Cachéのobjectscriptには、「後にソート」という特殊なバイナリ演算子「]]」があります。 最初のオペランドを持つサブスクリプトが2番目のオペランドを持つサブスクリプトの後にソートされる場合は1を返し、そうでない場合は0を返す演算子です。

USER>write ##class(%Library.Collate).SetLocalName("Cache standard"),!
1
USER>write "А" ]] "Ё"
1
USER>write ##class(%Library.Collate).SetLocalName("Cyrillic3"),!
1
USER>write "А" ]] "Ё"
0

グローバルと照合

同一のデータベースに含まれるグローバルには、さまざまな照合が使用される場合があります。 各データベースには構成オプションがあり、新しいグローバルにはデフォルト照合が使用されます。 インストール直後、USERを除くすべてのデータベースはCaché標準のデフォルト照合を使用します。 USERデータベースのデフォルト照合はインストールロケールによって決まるため、 ruswの場合はCyrillic3となります。

データベースに対しデフォルト以外の照合を使用してグローバルを作成するには、##class(%GlobalEdit).Createメソッドを使用します。

USER>kill ^a
USER>write ##class(%GlobalEdit).Create(,"a",##class(%Collate).DisplayToLogical("Cache standard"))

管理ポータル(システムエクスプローラ > Globals)のグローバルのリストに、各グローバルの照合列があります。

既存のグローバルの照合を変更することはできないため、 新しい照合でグローバルを作成して、Mergeコマンドを使ってデータをコピーする必要があります。 グローバルの一括変換を行うには##class(SYS.Database).Copy()を使用します。

Cyrillic4、Cyrillic3、およびウムラウト

文字列サブスクリプトを内部形式に変換するには、Cyrillic3照合には、Caché標準照合にかかる時間よりもはるかに長い時間がかかるため、Cyrillic3照合を使用したグローバル(またはローカル)配列の挿入とルックアップの処理が遅くなります。 Caché 2014.1には新しい照合であるCyrillic4が含まれており、Cyrillic3と同じ正しい文字順になりますが、パフォーマンスが改善されます。

for collation="Cache standard","Cyrillic3","Cyrillic4" {
     write ##class(%Library.Collate).SetLocalName(collation),!
     write ##class(%Library.Collate).GetLocalName(),!
     do test(100000)
 }
 quit
test(C)
 set letters = "абвгдеёжзийклмнопрстуфхцчщщьыъэюя"
 set letters = letters _ $zconvert(letters,"U")

 kill test
 write "test insert: "
 //fill local array “test” with data
 set z1=$zh
 for c=1:1:C {
     for i=1:1:$Length(letters) {
         set test($Extract(letters,i)_"плюс длинное русское слово" _ $Extract(letters,i)) = ""
     }
 }
 write $zh-z1,!

 //looping through test subscripts
 write "test $Order: "
 set z1=$zh
 for c=1:1:C {
     set l = ""
     for  {
         set l = $Order(test(l))
         quit:l=""
     }
 }
 write $zh-z1,!

USER>do ^testcol
1
Cache standard
test insert: 1.520673
test $Order: 2.062228
1
Cyrillic3
test insert: 3.541697
test $Order: 5.938042
1
Cyrillic4
test insert: 1.925205
test $Order: 2.834399

Cyrillic4は、まだruswロケールのデフォルトの照合ではありませんが、ruswに基づいて独自のロケールを定義し、Cyrillic4をローカル配列のデフォルトの照合として指定することができます。 または、データベース設定でグローバルの新しいデフォルト照合としてCyrillic4を設定することもできます。

Cyrillic3は、2つの文字列を各文字コードに基づいてソートするのに比べてさらに一般的なアルゴリズムに基づいているため、Caché標準とCyrillic4よりも低速になります。

ドイツ語の場合、ソート時には文字はssとして照合されます。 Cachéは次の通り、そのルールを尊重します。

USER>write ##class(%Collate).GetLocalName()
German3
USER>set test("Straßer")=1
USER>set test("Strasser")=1
USER>set test("Straster")=1
USER>zwrite test
test("Strasser")=1
test("Straßer")=1
test("Straster")=1

サブスクリプトの文字列のソート順に注目してください。 特に、最初の文字列の頭の4文字は「Stras」で、次に「Straß」、そしてもう一度「Stras」となっているところです。 照合が個別の文字のコードに基づいてソートされているだけであれば、この方法で文字列をソートすることは不可能です。

もう1つの例として、フィンランド語の場合、「v」と「w」は同一の文字として照合される必要があります。 ロシア語の照合ルールはより単純であり、各文字にある特定のコードを与え、これらのコードでソートするだけで十分です。 この方法で、照合Cyrillic4 のパフォーマンスはCyrillic3と比べて改善されています。

照合とSQL

配列の照合とSQL照合を混同しないようにしてください。 SQL照合は、比較の前に文字列に適用される変換、またはインデックスグローバルでサブスクリプトとして使用する変換です。 CachéのデフォルトのSQL照合はSQLUPPERで、 すべての文字を大文字に変換し、スペース文字を削除して、文字列の先頭にスペースを1つ追加します。 ほかのSQL照合(EXACT、SQLSTRING、TRUNCATE)については、こちらのドキュメントに説明されています。

同一のデータベース内の異なるグローバルにさまざまな照合が使用されており、ローカル配列にも別の照合が使用されている場合、たやすく混乱が生じます。 SQLは一時データにCACHETEMPデータベースを使用しますが、 CACHETEMPのグローバルのデフォルト照合は、Cachéのインストールロケールの照合とは異なる可能性があります。

主なルールが1つあります。期待される順序で行を返すSQLクエリのORDER BYについては、データと関連するテーブルのインデックスがソートされるグローバルの照合は、CACHETEMPデータベースのデフォルトの照合とローカル配列の照合と同一である必要があります。 詳細については、ドキュメントの「SQLとNLS照合」の段落を参照してください。

では、テストクラスを作成しましょう。

Class Collation.test Extends %Persistent
{

Property Name As %String;

Property Surname As %String;

Index SurInd On Surname;

ClassMethod populate()
{
    do ..%KillExtent()

    set t = ..%New()
    set t.Name = "Павел", t.Surname = "Ёлкин"
    write t.%Save()

    set t = ..%New()
    set t.Name = "Пётр", t.Surname = "Иванов"
    write t.%Save()

    set t = ..%New()
    set t.Name = "Прохор", t.Surname = "Александров"
    write t.%Save()
}

}

クラスにデータを入力します(後で、ドイツ語を使った前の例の単語を使用してください)。

USER>do ##class(Collation.test).populate()

クエリを実行します。

![](https://community.intersystems.com/sites/default/files/inline/images/0-order-by-name-wrong.png)

予想外の結果となりました。 主な疑問は、名前がなぜアルファベット順に並べられないのか (Павел、Пётр、Прохор)というところにあります。 クエリプランを見てみましょう。

![](https://community.intersystems.com/sites/default/files/inline/images/1-order-by-name-plan.png)

このプランのキーワードは、「一時ファイルを作成する」です。 SQLエンジンは、このクエリの実行に一時的な構造を使用することに決定しました。 「ファイル」と呼ばれてはいますが、実際にはプロセスプライベートのグローバルであり、場合によってはローカル配列です。 このグローバルのサブスクリプトは、この特定のケースでは個人名で順序付けする値です。 プロセスプライベートのグローバルは、CACHETEMPデータベースに格納され、CACHETEMPの新しいグローバルのデフォルト照合はCaché標準です。

また、「ё」がなぜ最後ではなく最初に返されているのかという別の合理的な疑問もあります(Caché標準では「ё」はロシア語のすべての文字と「Ё」の前ではなく、後にソートされます)。 一時グローバルのサブスクリプトはNameフィールドの実際の値ではなく、Nameを大文字化した値(SQLUPPERは文字列のデフォルトのSQL照合)であるため、「Ё」がほかの文字の前に返されているのです。

%Exact関数を使用してデフォルトの照合を変更すると、依然として誤りではありますが、少なくとも「ё」がほかの文字の後にソートされるという、期待された結果が得られます。

![](https://community.intersystems.com/sites/default/files/inline/images/2-order-by-exact-name.png)

ここでは、CACHETEMPのデフォルトの照合を変更せずに、Surname列のクエリを確認してみましょう。 この列のインデックスは、^Collation.testIグローバルに格納されます。 そのグローバルの照合はCyrillic3であるため、行の正しい順序を確認できるはずです。

![](https://community.intersystems.com/sites/default/files/inline/images/3-order-by-surname-wrong.png)

またしても、誤りです。「Ё」は 「А」と「И」の間である必要があります。 クエリプランを見てみましょう。

![](https://community.intersystems.com//sites/default/files/inline/images/4-order-by-surname-plan.png)

SQLUPPERがSurInd インデックスの値に適用されているため、Surnameフィールドの元の値を出力できる十分なインデックスデータがありません。 SQLエンジンは、Name列と同様に、テーブル自体の値を使用して一時ファイルの値を並べ替えるように決定しています。

クエリに、Surnameが大文字でも良いことを記述することができます。 行はインデックスグローバル ^Collation.testI から直接取得されるため、順序は正しくなります。

![](https://community.intersystems.com//sites/default/files/inline/images/5-order-by-surname-sqlupper.png)

クエリプランは期待通りです。

![](https://community.intersystems.com//sites/default/files/inline/images/6-order-by-surname-sqlupper-plan.png)

では、ずっと前に行っているはずの、CACHETEMPデータベースのデフォルト照合をCyrillic3(またはCyrillic4)に変更する作業を行いましょう。

一時ファイルを使用するクエリは、正しい順で行を出力します。

![](https://community.intersystems.com//sites/default/files/inline/images/7-order-by-name-ok.png)
![](https://community.intersystems.com//sites/default/files/inline/images/8-order-by-surname-ok.png)

要約

  • ローカルアルファベットのニュアンスを気にしない場合は、Caché標準の照合を使用します。
  • 一部の照合(Cyrillic4)は、ほかの照合(Cyrillic3)よりもパフォーマンスが優れています。
  • CACHETEMPの照合がメインデータベースとローカル配列の照合と同じであることを確認します。
0
0 569
記事 Toshihiko Minamoto · 10月 19, 2020 5m read

SQL パフォーマンスリソース

SQL のパフォーマンスについて語るとき、最も重要なトピックとして取り上げられるのが「Indices」、「TuneTable」、「Show Plan」の 3 つです。  添付の PDF にはこれらのトピックに関する過去のプレゼン資料が含まれていますので、それぞれの基礎を一度に確認していただけます。  当社のドキュメンテーションでは、これらのトピックの詳細に加え、SQL パフォーマンスの他のトピックについてもカバーしておりますので、下のリンクからお読みください。  eラーニングをご利用いただくと、これらのトピックをもっと深く理解していただけます。  また、開発者コミュニティによる記事の中にも SQL パフォーマンスについて書かれたものが複数ありますので、関連するリンクを下に記載しております。

下に記載する情報には同じ内容が多く含まれています。  SQL パフォーマンスにおける最も重要な要素を以下に紹介します。

  • 利用可能なインデックスの種類
  • あるインデックスの種類を他の種類の代わりに使用する
  • TuneTable がテーブルに集める情報とそれが Optimizer に対して意味する内容
  • Show Plan を読んでクエリの善し悪しを深く理解する方法
  • 以下は InterSystems IRIS SQL のパフォーマンスを詳しく理解するのに役立つリソースです

  • Worldwide Response Center (WRC)。電話番号: 617-621-0700
    WRC をご利用ください。  同センターはユーザーが理解しやすいレベルで SQL パフォーマンスを説明してくれます。  優秀な SQL サポートスタッフが数人おりますが、アドバイザーと直接お話しをされる場合は、お気軽にサポートマネージャーの Brendan Bannon までお問い合わせください。

    [訳注:日本ではカスタマサポートセンター(jpnsup@intersystems.com)にお問い合わせください。]

     
  • InterSystems オンラインドキュメンテーション
    1. 機能紹介:  InterSystems SQL:  https://docs.intersystems.com/irislatestj/csp/docbook/DocBook.UI.Page.cls?KEY=AFL_sqlbasics
    2. 機能紹介: InterSystems 製品での SQL パフォーマンスの最適化:  https://docs.intersystems.com/irislatestj/csp/docbook/DocBook.UI.Page.cls?KEY=AFL_sqlqueryopt
    3. InterSystems SQL 最適化ガイド:  https://docs.intersystems.com/irislatestj/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLOPT
       
  • InterSystems の eラーニング
    1. リソースガイド - Caché SQL を解説: パフォーマンス: https://learning.intersystems.com/enrol/index.php?id=255
    2. リソースガイド – Caché SQL を解説: デザインと実行: https://learning.intersystems.com/enrol/index.php?id=256
    3. SQL クエリの最適化:  https://learning.intersystems.com/course/view.php?id=707
    4. アカデミー:  SQL パフォーマンスの最適化:  https://learning.intersystems.com/course/view.php?id=80
    5. SQL クエリの最適化:  https://learning.intersystems.com/course/view.php?id=1013
       
  • Intersystems 開発者コミュニティ
    1. 知っておくと便利なクエリパフォーマンスのコツとは? ずばり Tune Table です! https://community.intersystems.com/post/one-query-performance-trick-you-need-know-tune-table
    2. InterSystems IRIS が提供する水平拡張性:  https://jp.community.intersystems.com/post/intersystems-irisによる水平スケーラビリティ
    3. 外れ値の選択性とは:  https://community.intersystems.com/post/introduction-outlier-selectivity
    4. 日付範囲クエリの SQL パフォーマンスを改善する:  https://jp.community.intersystems.com/post/日付範囲クエリのsqlパフォーマンスを改善する
    5. 日付範囲クエリの SQL パフォーマンスを改善する (vol2):  https://jp.community.intersystems.com/post/日付範囲クエリのsqlパフォーマンスを改善する-vol2
    6. Caché でカスタムインデックスタイプを作成する方法:  https://jp.community.intersystems.com/post/cachéでのカスタムインデックスタイプの作成
    7. グローバルはデータを保存するための魔法の剣です パート1:  https://jp.community.intersystems.com/post/グローバルはデータを保存するための魔法の剣です- パート1
    8. グローバルはデータを保存するための魔法の剣ですパート2 - ツリー:  https://jp.community.intersystems.com/post/グローバルはデータを保存するための魔法の剣ですパート2-ツリー
    9. Globals – グローバルはデータを保存するための魔法の剣です パート3 - 疎な配列 :  https://jp.community.intersystems.com/post/グローバルはデータを保存するための魔法の剣です-パート3-疎な配列
    10. フリーテキスト検索:SQL開発者が秘密にしているテキストフィールドの検索方法*: 
      https://jp.community.intersystems.com/post/フリーテキスト検索:sql開発者が秘密にしているテキストフィールドの検索方法
    11. ノンアトミック属性のインデックス作成:  https://community.intersystems.com/post/indexing-non-atomic-attributes
    12. 配列プロパティ要素の SQL インデックス:  https://community.intersystems.com/post/sql-index-array-property-elements
       
  • 過去の PowerPoint 資料
  • 付属のプレゼン資料は古いですが、充実した内容になっています。  情報の多くは Intersystems IRIS に該当するものですが、念のために最新のドキュメンテーションをご確認ください。  以下のすべてを PDF ファイルとして本記事に添付しています。

    1. インデックス作成とは
    2. SQL パフォーマンスの最適化 (2015 年版)
    3. %PARALLEL Query のヒント (2016 年版)
    4. Frozen Plans と Parallel Queries (2017 年版)
    5. 生成された COS に対する Show Plan (2013 年版)
    6. SQL プログラミング (2006 年版)
  • (本記事の著作者は Brendan Bannan、編集アシスタントは Cliff Mason と Kyle Baxter です) 

    0
    0 232
    記事 Mihoko Iijima · 10月 15, 2020 5m read

    これはInterSystems FAQ サイトの記事です。

    DATE 型は InterSystems 製品のデータ型の %Date に、TIME 型は %Time に対応しています。

    %Date は内部日付(特殊変数 $Horolog のカンマ区切り1番目)、%Time は内部時刻($Horolog のカンマ区切り2番目)を登録するタイプであるため、サーバ側ロジックでは表示モードを切り替えない限り、内部(論理)形式の値が使用されます。
    サーバ側ロジックで内部日付・時刻の表示形式を変更する方法は、操作方法により異なります。

    以降の実行例では、Sample.Person テーブルを使用して解説します。
    (コマンド実行例は SELECT 文で記載していますが、更新文に対しても同様に記述できます。)

    IRIS/IRIS for Health でお試しいただく場合は、ドキュメント(InterSystems IRIS で使用するサンプルのダウンロード)から、
    または 関連記事(サンプル(Sample.Person)のクラス定義ダウンロードとサンプルデータの作成について)から、
    Sample.Person クラスのインポートとサンプルデータの作成を行ってからお試しください。

    Caché/Ensembleでお試しいただく場合は、SAMPLESネームスペースのSample.Personをご利用ください。

    0
    0 869
    記事 Toshihiko Minamoto · 9月 30, 2020 14m read

    InterSystems IRIS のクラスクエリ

    InterSystems IRIS(および Cache、Ensemble、HealthShare)のクラスクエリは、SQL クエリを Object Script のコードから分離する便利なツールです。 このクエリの基本的な機能は、同じ SQL クエリを複数の場所で異なる引数で使用する場合にクエリの本文をクラスクエリとして宣言し、このクエリを名前で呼び出すことでコードの重複を回避できるというものです。 このアプローチは、次のレコードを取得するタスクを開発者が定義するカスタムクエリにも便利です。 興味が湧きましたか? それではこのまま読み進めてください!

    基本クラスクエリ

    簡単に言うと、基本クラスクエリは SQL の SELECT クエリを表現できるようにするものです。 基本クラスクエリは SQL オプティマイザとコンパイラによって通常の SQL クエリと同様に処理されますが、Caché Object Script のコンテキストから実行する際はより便利になります。 また、次のようにクラス定義(メソッドまたはプロパティと同様)でクエリ項目として宣言されます。

    • タイプ: %SQLQuery
    • SQL クエリのすべての引数を引数リストに含める必要があります。
    • クエリタイプ: SELECT
    • コロンを使用して各引数にアクセスします(静的SQLと同様)。
    • 出力結果の名前とデータタイプに関する情報を含む ROWSPEC パラメーターをフィールドの順序と共に定義します。
    • (任意)フィールドに ID が含まれている場合、 CONTAINID パラメーターにその列番号を定義します。 ID を返す必要がない場合は、CONTAINID に0を割り当てます。
    • (任意)静的 SQL の同様のパラメーターに対応し、SQL 式をコンパイルするタイミングを指定する COMPILEMODE パラメーターを定義します。 このパラメーターが IMMEDIATE(デフォルト)に設定されている場合、クエリはクラスと同時にコンパイルされます。 このパラメーターを DYNAMIC に設定すると、クエリは初回実行の前にコンパイルされます(動的 SQL と同様)。
    • (任意)クエリ結果の形式を指定する SELECTMODE パラメーターを定義します。
    • このクエリを SQL プロシージャとして呼び出す場合は、SqlProc プロパティを追加します。
    • クエリの名前を変更する場合は、SqlName プロパティを設定します。 SQL コンテキストでのクエリのデフォルト名は PackageName.ClassName_QueryName です。
    • Caché Studio には、クラスクエリを作成するためのウィザードが搭載されています。


    指定した文字で始まるすべてのユーザー名を返す ByName クエリを使った Sample.Person クラスのサンプル定義

    Class Sample.Person Extends %Persistent
    {
    Property Name As %String;
    Property DOB As %Date;
    Property SSN As %String;
    Query ByName(name As %String = "") As %SQLQuery
        (ROWSPEC="ID:%Integer,Name:%String,DOB:%Date,SSN:%String", 
         CONTAINID = 1, SELECTMODE = "RUNTIME", 
         COMPILEMODE = "IMMEDIATE") [ SqlName = SP_Sample_By_Name, SqlProc ]
    {
    SELECT ID, Name, DOB, SSN
    FROM Sample.Person
    WHERE (Name %STARTSWITH :name)
    ORDER BY Name
    }
    }

    このクエリを次のように Caché Object Script から呼び出すことができます。 

    Set statement=##class(%SQL.Statement).%New()   
    Set status=statement.%PrepareClassQuery("Sample.Person","ByName")   
    If $$$ISERR(status) {
        Do $system.OBJ.DisplayError(status) 
    }   
    Set resultset=statement.%Execute("A")   
    While resultset.%Next() {
        Write !, resultset.%Get("Name")   
    }

    または、自動生成されたメソッドである「クエリ名Func」を使用して結果セットを取得することもできます。

    Set resultset = ##class(Sample.Person).ByNameFunc("A")    
    While resultset.%Next() {
        Write !, resultset.%Get("Name")   
    }
    

    このクエリは、SQL コンテキストから次の 2 つの方法で呼び出すこともできます。

    Call Sample.SP_Sample_By_Name('A')
    Select * from Sample.SP_Sample_By_Name('A')

    このクラスは、SAMPLES のデフォルト Caché ネームスペースにあります。単純なクエリに関する説明は以上となります。 今度はカスタムクエリを説明します。

    カスタムクラスクエリ

    基本クラスクエリはほとんどの場合に正常に動作しますが、次のような場合にはアプリケーションでクエリの動作を完全に制御しなければならないことがあります。

    • 選択条件が複雑な場合。 カスタムクエリでは次のレコードを返す Caché Object Script メソッドが独自に実装されているため、選択条件が要件に応じて複雑化している可能性があります。
    • 使用したくない形式の API経由でしかデータにアクセスできない場合。
    • データが(クラスなしで)グローバルに保存されている場合。
    • データにアクセスするために権利を昇格する必要がある場合。
    • データにアクセスするために外部 API を呼び出す必要がある場合。
    • データにアクセスするためにファイルシステムにアクセスする必要がある場合。
    • クエリを実行する前に、追加の操作を実行する必要があります(接続の確立、アクセス許可の確認など)。

    では、どのようにカスタムクラスクエリを作成しますか? まず、クエリのワークフロー全体(初期化から破棄まで)を実装する 4 つのメソッドを定義する必要があります。

    • queryName — クエリに関する情報を提供します(基本クラスクエリと同様)
    • queryNameExecute — クエリを作成します
    • queryNameFetch — クエリの次のレコードの結果を取得します
    • queryNameClose — クエリを破棄します

    次に、これらのメソッドをさらに詳しく分析します。

    queryName メソッド

    queryName メソッドはクエリに関する情報を提供します。

    • タイプ: %Query
    • 本文を空白のままにします。
    • 出力結果の名前とデータタイプに関する情報を含む ROWSPEC パラメーターをフィールドの順序と共に定義します。
    • (任意)フィールドに ID が含まれている場合、番号順に対応する CONTAINID パラメーターを定義します。 ID を返さない場合は、CONTAINID に値を割り当てないでください。

    例えば、新しい永続クラス Utils.CustomQuery のすべてのインスタンスを 1 つずつ出力する AllRecords クエリ(queryName = AllRecords、メソッドは単に AllRecords と呼ばれる)を作成してみましょう。 まず、新しい永続クラス Utils.CustomQuery を作成します。

    Class Utils.CustomQuery Extends (%Persistent, %Populate){ 
    Property Prop1 As %String; 
    Property 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 %Status

    説明:

    • qHandle はクエリ実装の他のメソッドとの通信に使用されます。
    • このメソッドは、qHandle を後で queryNameFetch メソッドに渡す状態に設定する必要があります。
    • qHandle は OREF、変数、または多次元変数に設定できます。
    • args はクエリに渡される追加のパラメーターです。 必要な数の引数を追加できます(または引数をまったく使用しないでください)。
    • このメソッドはクエリの初期化ステータスを返します。

    では、再び例に戻りましょう。 複数の方法で範囲内を自由に反復処理できます(以下でカスタムクエリの基本的な処理方法を説明します)。ただし、この例では関数 $Order を使用してグローバルを反復処理します。 この場合、qHandle は現在の ID を格納します。追加の引数は必要ないため、arg 引数は必要ありません。 結果は次のようになります。

    ClassMethod AllRecordsExecute(ByRef qHandle As %Binary) As %Status {  
        Set qHandle = ""    Quit $$$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 コードでのメソッドの位置を識別します。 "Fetch" メソッドは "Execute" メソッドの後に配置する必要がありますが、これは 静的 SQL(つまり、クエリ内の カーソル)にのみ重要です。

    一般に、このメソッド内では次の処理が実行されます。

    1. データの終端に達したかどうかを確認します。
    2. まだデータが残っている場合は新しい %List を作成し、Row 変数に値を割り当てます。
    3. それ以外の場合は、AtEnd を 1 に設定します。
    4. 次の結果を取得するために qHandle を準備します。
    5. ステータスを返します。

    この例では次のようになります。

    ClassMethod AllRecordsFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status {
        #; ^Utils.CustomQueryD を反復する    
        #; qHandle に次の id を書き込み、新しい id を持つグローバルの値を val に書き込む
        Set qHandle = $Order(^Utils.CustomQueryD(qHandle),1,val)
        #; 残っているデータがあるかどうかを確認する
           If qHandle = "" {
            Set AtEnd = 1
            Set Row = ""
            Quit $$$OK    
        }
        #; 残っていなければ %List を作成する
        #; val = $Lb("", Prop1, Prop2) Storage の定義を参照
        #; Row =$lb(Id,Prop1, Prop2)  AllRecords リクエストについては ROWSPEC を参照
        Set Row = $Lb(qHandle, $Lg(val,2), $Lg(val,3))
        Quit $$$OK 
    }

    queryNameClose メソッド
    queryNameClose メソッドはすべてのデータが取得された時点でクエリを終了します。 このメソッドのシグネチャは次のとおりです。

    ClassMethod queryNameClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = queryNameFetch ]

    説明:

    • Caché は queryNameFetch メソッドの最後の呼び出しの後にこのメソッドを実行します。
    • つまり、クエリのデストラクタとも言い換えることができます。
    • したがって、その実装ではすべての SQL カーソル、クエリ、ローカル変数を破棄する必要があります。
    • このメソッドは現在のステータスを返します。

    この例では、ローカル変数 qHandle を削除する必要があります。

    ClassMethod AllRecordsClose(ByRef qHandle As %Binary) As %Status {
        Kill qHandle
        Quit $$$OK
      }

    以上です! クラスをコンパイルすると、基本クラスクエリと同様に %SQL.Statement から AllRecords クエリを使用できるようになります。

    カスタムクエリの反復ロジック手法

    では、カスタムクエリにはどのような手法を使用できるのでしょうか? 一般的には、次の 3 つの基本的な手法があります。

    グローバルによる反復
    この手法は、グローバルによる反復に $Order などの関数を使用することを基本にしています。 この手法は次の場合に使用できます。

    • データが(クラスなしで)グローバルに保存されている場合。
    • コード内の glorefs の数を減らしたい場合。
    • 結果をグローバルの添え字で並べ替える必要がある/並べ替え可能な場合。


    静的 SQL
    この手法は、カーソルと静的 SQL を基本にしています。 この手法は次の目的で使用されます。

    • int コードの可読性を高める。
    • カーソルの処理を簡単にする。
    • コンパイルプロセスの高速化(静的 SQL はクラスクエリに含まれるため、コンパイルは 1 回だけ実行されます)。

    注意事項:

    • %SQLQuery タイプのクエリから生成されたカーソルには、Q14 などのように自動的に名前が付けられます。
    • クラス内で使用されるすべてのカーソルは、異なる名前を持つ必要があります。
    • エラーメッセージは、名前の末尾に追加の文字があるカーソルの内部名が関係しています。 例えば、カーソル Q140 のエラーは実際にはカーソル Q14 によって引き起こされたものです。
    • PlaceAfter を使用し、カーソルが宣言箇所と同じ int ルーチンで使用されるようにしてください。
    • INTO は FETCH と組み合わせて使用する必要がありますが、DECLARE とは組み合わせないでください。


    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 %Status
    {
        &sql(DECLARE C CURSOR FOR
            SELECT Id, Prop1, Prop2
            FROM Utils.CustomQuery
         )
         &sql(OPEN C)
        Quit $$$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) {
            Set AtEnd = 1
            Set Row = ""
            Quit $$$OK
        }
        Set Row = $Lb(Id, Prop1, Prop2)
        Quit $$$OK
    }
    
    ClassMethod AllStaticClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = AllStaticFetch ]
    {
        &sql(CLOSE C)
        Quit $$$OK
    }

    動的 SQL
    この手法は、他のクラスクエリと動的 SQL を基本にしています。 この手法は、複数のネームスペースで SQL クエリを実行する、クエリを実行する前に権限を昇格するなどの SQL クエリ自体以外の追加処理を実行する必要がある場合に適切です。

    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 %Status
    {
        Set qHandle = ##class(%SQL.Statement).%ExecDirect(,"SELECT * FROM Utils.CustomQuery")
        Quit $$$OK
    }
    
    ClassMethod AllDynamicFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status
    {
        If qHandle.%Next()=0 {
            Set AtEnd = 1
            Set Row = ""
            Quit $$$OK
        } 
        Set Row = $Lb(qHandle.%Get("Id"), qHandle.%Get("Prop1"), qHandle.%Get("Prop2"))
        Quit $$$OK
    }
    
    ClassMethod AllDynamicClose(ByRef qHandle As %Binary) As %Status
    {
        Kill qHandle
        Quit $$$OK
    }

    代替手法:%SQL.CustomResultSet

    %SQL.CustomResultSet クラスからサブクラス化してクエリを作成することもできます。 この手法のメリットは次のとおりです。

    • 若干の速度改善
    • すべてのメタデータがクラス定義から取得されるため、ROWSPEC が不要であること
    • オブジェクト指向の設計原則に準拠できること

    %SQL.CustomResultSet クラスのサブクラスからクエリを作成するには、次の手順を実行してください。

    1. 結果のフィールドに対応するプロパティを定義します。
    2. クエリコンテキストが格納される private プロパティを定義します。
    3. コンテキストを開始する %OpenCursor メソッド(queryNameExecute と同様)をオーバーライドします。 エラーが発生時に %SQLCODE と %Message も設定します。
    4. 次の結果を取得する %Next メソッド(queryNameFetch と同様)をオーバーライドします。 プロパティを入力します。 このメソッドはすべてのデータが処理された場合に 0 を返し、一部のデータがまだ残っている場合に 1 を返します。
    5. 必要に応じて %CloseCursor メソッド(queryNameClose と同様)をオーバーライドします。


    Utils.CustomQuery の %SQL.CustomResultSet の例:

    Class Utils.CustomQueryRS Extends %SQL.CustomResultSet
    {
    Property Id As %String;
    Property Prop1 As %String;
    Property Prop2 As %Integer;
    Method %OpenCursor() As %Library.Status
    {
        Set ..Id = ""
        Quit $$$OK
    }
    
    Method %Next(ByRef sc As %Library.Status) As %Library.Integer [ PlaceAfter = %Execute ]
    {
        Set sc = $$$OK
        Set ..Id = $Order(^Utils.CustomQueryD(..Id),1,val)
        Quit:..Id="" 0
        Set ..Prop1 = $Lg(val,2)
        Set ..Prop2 = $Lg(val,3)
        Quit $$$OK
    }
    }

    これは、次のように Caché Object Script コードから呼び出すことができます。

    Set resultset= ##class(Utils.CustomQueryRS).%New()
           While resultset.%Next() {
            Write resultset.Id,!
     }

    また、SAMPLES ネームスペースでは、Samples.Person のクエリを実装する Sample.CustomResultSet クラスという別の例を使用することもできます。

    最後に

    カスタムクエリは、Caché Object Script コードから SQL 式を分離し、純粋な SQL では難しすぎる可能性がある高度な動作を実装するのに役立ちます。

    参考情報

    クラスクエリ

    グローバルによる反復

    静的 SQL

    動的 SQL

    %SQL.CustomResultSet

    Utils.CustomQuery クラス

    Utils.CustomQueryRS クラス

    この記事の執筆にご協力いただいた Alexander Koblov 氏に感謝します。

    0
    0 801
    記事 Tomoko Furuzono · 9月 17, 2020 17m read

    Caché 2017以降のSQLエンジンには新しい統計一式が含まれています。 これらの統計は、クエリの実行回数とその実行所要時間を記録します。

    これは、多くのSQLステートメントを含むアプリケーションのパフォーマンスを監視する人や最適化を試みる人にとっては宝物のような機能ですが、一部の人々が望むほどデータにアクセスするのは簡単ではありません。

    この記事と関連するサンプルコードでは、このような情報の使用方法と、日次統計の概要を定期的に抽出してアプリケーションのSQLパフォーマンス履歴記録を保持する方法について説明します。
    ※詳細については、下記ドキュメントページもご参考になさってください。

    https://docs.intersystems.com/iris20201/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLOPT_sqlstmts

    記録内容

    SQLステートメントが実行されるたびに、所要時間が記録されます。 この処理は非常に軽量であり、オフにすることはできません。 コストを最小限に抑えるため、統計はメモリに保持されてから定期的にディスクに書き込まれます。 このデータには当日にクエリが実行された回数と、その平均所要時間と合計所要時間が含まれます。

    0
    0 355