永続オブジェクトと InterSystems IRIS SQL
InterSystems IRIS® の主な特徴は、オブジェクト・テクノロジと SQL との結合にあります。どのようなシナリオに対しても、最も便利なアクセス・モードを使用できます。このページでは、InterSystems IRIS でこの機能がどのように提供されているのかを説明し、格納されているデータを操作するためのオプションの概要について説明します。
このページで示されている ObjectScript のサンプルは、Samples-Data サンプル (https://github.com/intersystems/Samples-DataOpens in a new tab) のものです。(例えば) SAMPLES という名前の専用ネームスペースを作成し、そのネームスペースにサンプルをロードすることをお勧めします。一般的な手順は、"InterSystems IRIS で使用するサンプルのダウンロード" を参照してください。
はじめに
InterSystems IRIS は、オブジェクト指向のプログラミング言語が組み込まれたマルチモデル・データ・プラットフォームです。その結果、以下をすべて実行できる柔軟なコードを作成できます。
-
SQL を介してデータの一括挿入を実行します。
-
オブジェクトを開き、変更し、保存します。したがって、SQL を使用しないで 1 つ以上のテーブルのデータを変更します。
-
新しいオブジェクトを作成、保存し、SQL を使用しないで 1 つ以上のテーブルに行を追加します。
-
多数のオブジェクトに繰り返し処理を実行せずに、SQL を使用して、レコードから、指定された条件に一致する値を取得します。
-
オブジェクトを削除し、SQL を使用せずに 1 つ以上のテーブルからレコードを削除します。
つまり、いつでもニーズにあったアクセス・モードを選択できます。
内部では、すべてのアクセスは、直接グローバル・アクセスを介して実行され、必要に応じてユーザもその方法で自身のデータにアクセスできます。(クラス定義がある場合、直接グローバル・アクセスを使用してデータに変更を行うことはお勧めしません。)
InterSystems SQL
InterSystems IRIS は、InterSystems SQL と呼ばれる SQL の実装を提供します。InterSystems SQL は、メソッド内およびルーチン内で使用できます。
また、InterSystems SQL は、SQL シェル内 (ターミナル) および管理ポータルで直接実行することもできます。これらには、それぞれ、クエリ・プランを表示するためのオプションがあり、クエリをさらに効率的にする方法を特定する際に役立ちます。
InterSystems SQL は、いくつかの例外を除き、すべての初級 SQL-92 標準をサポートし、いくつかの拡張もあります。InterSystems SQL は、インデックス、トリガ、BLOB、およびストアド・プロシージャもサポートしています (これらは典型的な RDBMS 機能であり、SQL-92 標準には含まれません)。完全なリストについては、"InterSystems SQL の使用法" を参照してください。
ObjectScript からの SQL の使用
以下のいずれかまたは両方の方法を使用して、ObjectScript から SQL を実行できます。
-
ダイナミック SQL (%SQL.StatementOpens in a new tab クラスおよび %SQL.StatementResultOpens in a new tab クラス)。以下に例を示します。
SET myquery = "SELECT TOP 5 Name, DOB FROM Sample.Person"
SET tStatement = ##class(%SQL.Statement).%New()
SET tStatus = tStatement.%Prepare(myquery)
SET rset = tStatement.%Execute()
DO rset.%Display()
WRITE !,"End of data"
ダイナミック SQL は、ObjectScript のメソッドおよびルーチンで使用できます。
-
埋め込み SQL。以下に例を示します。
&sql(SELECT COUNT(*) INTO :myvar FROM Sample.Person)
IF SQLCODE<0 {WRITE "SQLCODE error ",SQLCODE," ",%msg QUIT}
ELSEIF SQLCODE=100 {WRITE "Query returns no results" QUIT}
WRITE myvar
埋め込み SQL は、ObjectScript のメソッドおよびルーチンで使用できます。
Python からの SQL の使用
以下のいずれかまたは両方の方法を使用して、Python から SQL を実行できます。
Python ターミナルまたは Python メソッドでこれらのいずれかの方法を使用して、SQL クエリを実行できます。
SQL へのオブジェクト拡張
オブジェクト・アプリケーションで SQL を使用しやすくするために、InterSystems IRIS には SQL へのオブジェクト拡張が多数あります。
大変興味深い拡張として、"矢印構文" とも呼ばれる暗黙結合演算子 (“–>”) を使用して、オブジェクト参照を実行する機能があります。例えば、2 つのクラス、Contact と Region を参照する Vendor クラスがあるとします。暗黙結合演算子を使用すると、関連するクラスのプロパティを参照できます。
SELECT ID,Name,ContactInfo->Name
FROM Vendor
WHERE Vendor->Region->Name = 'Antarctica'
また、SQL JOIN 構文を使用しても、同様のクエリ式を記述できます。暗黙結合演算子構文の利点は、簡潔で理解しやすいことです。
永続クラスに対する特別なオプション
InterSystems IRIS では、すべての永続クラスは %Library.PersistentOpens in a new tab (%PersistentOpens in a new tab とも呼ぶ) を拡張します。このクラスは、InterSystems IRIS でのオブジェクト SQL 対応のためのフレームワークの大部分を提供します。永続クラス内では、以下のようなオプションがあります。
-
メソッドを使用してオブジェクトを開く、保存する、および削除する機能。
永続オブジェクトを開く場合、永続オブジェクトは、複数のユーザまたは複数のプロセスによって使用される可能性があるため、同時処理ロックの程度を指定します。
オブジェクト・インスタンスを開き、オブジェクト値プロパティを参照すると、システムによってそのオブジェクトも自動的に開かれます。このプロセスをスウィズリングといいます (また、遅延ロードということもあります)。それにより、そのオブジェクトも操作できます。以下の例では、Sample.Person オブジェクトを開くと、対応する Sample.Address オブジェクトがスウィズルされます。
Set person=##class(Sample.Person).%OpenId(10)
Set person.Name="Andrew Park"
Set person.Address.City="Birmingham"
Do person.%Save()
import iris
person=iris.cls("Sample.Person")._OpenId(10)
person.Name="Andrew Park"
person.Address.City="Birmingham"
person._Save()
同様に、オブジェクトを保存すると、システムによって、そのすべてのオブジェクト値プロパティも自動的に保存されます。これをディープ・セーブといいます。代わりに、シャロウ・セーブを実行するオプションもあります。
-
既定のクエリ (エクステント・クエリ) を使用する機能。既定のクエリは、このクラスのオブジェクトのデータが含まれる SQL 結果セットです。既定では、エクステント・クエリはエクステント内の既存の ID を返します。エクステント・クエリを変更して、返す列を増やすこともできます。
このクラス (または他のクラス) では、追加のクエリを定義できます。
-
外部キーとして SQL に投影されるクラス間のリレーションシップを定義する機能。
リレーションシップは、2 つ以上のオブジェクト・インスタンスの相互の関連性を定義する、オブジェクト値プロパティの特別なタイプです。すべてのリレーションシップは 2 つで 1 組みです。リレーションシップのすべての定義には、対応する逆のリレーションシップがあり、それが反対側を定義します。InterSystems IRIS ではデータの参照整合性が自動的に適用され、一方での操作を、他方ですぐに見ることができます。リレーションシップは、メモリ内の振る舞いやディスク上の振る舞いを自動的に管理します。それらは、オブジェクト・コレクションを使用した優れたスケーリングと同時処理も提供します ("コレクション・クラス" を参照してください)。
-
外部キーを定義する機能。実際は、外部キーを追加して、既存のアプリケーションに参照整合性制約を追加します。新しいアプリケーションの場合、代わりにリレーションシップを定義した方が簡単です。
-
これらのクラスにインデックスを定義する機能。
インデックスは、永続クラスのインスタンス全体にわたる検索を最適化するためのメカニズムを提供します。インデックスは、クラスに関連する、要求されること多いデータをソートした特定のサブセットを定義します。これらは、パフォーマンス・クリティカルな検索のオーバーヘッドを削減するのに非常に役立ちます。
インデックスは、そのクラスに属する 1 つまたは複数のプロパティでソートできます。これは、返される結果の順序を制御するのに大変便利です。
またインデックスは、ソートされたプロパティに基づいて、クエリで頻繁に要求される他のデータを格納できます。インデックスの一部として他のデータを含めることによって、インデックスを使用するクエリの性能が大きく向上します。メイン・データ・ストレージにアクセスしなくても、クエリがインデックスを使用して結果セットを生成できます。
-
行が挿入、変更、または削除されるときに何が行われるのかを制御するために、これらのクラスにトリガを定義する機能。
-
メソッドおよびクラス・クエリを SQL ストアド・プロシージャとして投影する機能。
-
SQL へのプロジェクションを細かく調整する (例えば、SQL クエリのようにテーブルと列名を指定する) 機能。
-
オブジェクトのデータを格納するグローバルの構造を細かく調整する機能。
Note:
Python では、リレーションシップ、外部キー、およびインデックスを定義することはできません。
永続クラスの SQL プロジェクション
永続クラスの場合、クラスの各インスタンスは、SQL を使用してクエリおよび操作できる、テーブルの行として使用できます。この例を示すために、このセクションでは管理ポータルとターミナルを使用します。これらについては、このドキュメントで後で説明します。
オブジェクト SQL プロジェクションのデモ
SAMPLES の Sample.Person クラスを考えてみます。管理ポータルを使用して、このクラスに対応するテーブルのコンテンツを表示すると、以下に類似したものが表示されます。
(このサンプルはリリースごとに再構築されるため、これは実際に表示されるものと同じデータではありません。)以下の点に注意してください。
-
ここに表示される値は、表示値であり、ディスクに格納されている論理値ではありません。
-
最初の列 (#) は、表示されているこのページ内の行番号です。
-
2 番目の列 ([ID]) は、このテーブルの行に対する一意の識別子であり、このクラスのオブジェクトを開くときは、この識別子を使用します (このクラスではこれらの識別子は整数ですが、整数ではないこともあります)。
このテーブルは、SAMPLES データベースが構築されるたびに新しく生成されるため、この場合、これらの番号は同一になっています。実際のアプリケーションでは、いくつかのレコードが削除されている可能性があるため、[ID] の値には相違があり、それらの値は行番号と一致しません。
ターミナルで、一連のコマンドを使用して、最初の人を調べることができます。
SAMPLES>set person=##class(Sample.Person).%OpenId(1)
SAMPLES>write person.Name
Newton,Dave R.
SAMPLES>write person.FavoriteColors.Count()
1
SAMPLES>write person.FavoriteColors.GetAt(1)
Red
SAMPLES>write person.SSN
384-10-6538
>>> person=iris.cls("Sample.Person")._OpenId(1)
>>> print(person.Name)
Newton,Dave R.
>>> print(person.FavoriteColors.Count())
1
>>> print(person.FavoriteColors.GetAt(1))
Red
>>> print(person.SSN)
384-10-6538
これらは、SQL を使用した場合と同じ値です。
オブジェクト SQL プロジェクションの基本事項
継承はリレーショナル・モデルの一部ではないので、クラス・コンパイラは、永続クラスを “平坦化” したものをリレーショナル・テーブルとして投影します。以下の表は、さまざまなオブジェクト要素のいくつかが SQL にどのように投影されるのかを示しています。
オブジェクト・コンセプト |
SQL コンセプト |
パッケージ |
スキーマ |
クラス |
テーブル |
プロパティ |
フィールド |
埋め込みオブジェクト |
一連のフィールド |
リスト・プロパティ |
リスト・フィールド |
配列プロパティ |
子テーブル |
ストリーム・プロパティ |
BLOB または CLOB |
インデックス |
インデックス |
ストアド・プロシージャとしてマークされるクラス・メソッド |
ストアド・プロシージャ |
投影されたテーブルには、継承されたフィールドを含む、そのクラスに適切なすべてのフィールドが含まれます。
クラスとエクステント
InterSystems IRIS は、従来にない強力な解釈によるオブジェクトテーブル・マッピングを使用します。
永続クラスのすべての格納されているインスタンスによって、クラスのエクステントと呼ばれるものが構成され、インスタンスは、それがインスタンスとなっているクラスそれぞれのエクステントに属します。したがって、以下のようになります。
インデックスは、インデックスが定義されるクラスの全範囲を自動的にカバーします。Person に定義されているインデックスには、Person インスタンスと Student インスタンスの両方が含まれます。Student エクステントに定義されているインデックスには、Student インスタンスだけが含まれます。
サブクラスは、そのスーパークラスで定義されていない追加のプロパティも定義できます。これらは、サブクラスのエクステントでは使用できますが、スーパークラスのエクステントでは使用できません。例えば、Student エクステントには FacultyAdvisor フィールドが含まれますが、これは Person エクステントには含まれません。
前述の点は、InterSystems IRIS では、同じタイプのレコードをすべて取得するクエリを比較的簡単に作成できることを意味します。例えば、すべてのタイプの人々をカウントする場合、Person テーブルに対してクエリを実行できます。学生のみをカウントする場合、同じクエリを Student テーブルに対して実行します。これとは対照的に、他のオブジェクト・データベースでは、すべてのタイプの人々をカウントするには、テーブルを結合する複雑なクエリを作成する必要があり、別のサブクラスが追加されるたびにこのクエリを更新する必要があります。
オブジェクト ID
各オブジェクトは、それが属する各エクステント内で一意の ID を持っています。多くの場合、この ID を使用してそのオブジェクトを操作します。この ID は、%PersistentOpens in a new tab クラスの、以下のよく使用されるメソッドの引数です。
-
%DeleteId()
-
%ExistsId()
-
%OpenId()
このクラスには、ID を使用する他のメソッドもあります。
ID はどのように決まるか
最初にオブジェクトを保存するときに、InterSystems IRIS によって ID 値が割り当てられます。この割り当ては永続的であり、オブジェクトの ID は変更できません。他のオブジェクトが削除されたり、変更された場合でも、オブジェクトに新しい ID が割り当てられることはありません。
どの ID も、そのエクステント内で一意です。
オブジェクトの ID は、以下のように決定されます。
-
大部分のクラスでは、ID は、既定ではそのクラスのオブジェクトが保存されるときに順番に割り当てられる整数です。
-
親子リレーションシップの子として使用されるクラスでは、ID は以下のようにフォーマットされます。
parentID||childID
parentID は親オブジェクトの ID であり、childID は、子オブジェクトが親子リレーションシップの子として使用されていない場合に受け取る ID です。例 :
104||3
この ID は 3 番目に保存された子であり、その親はそれ自体のエクステントに ID 104 を持っています。
-
このクラスが、タイプ IdKey のインデックスを持っていて、そのインデックスが特定のプロパティに対するものである場合、そのプロパティ値が ID として使用されます。
SKU-447
また、そのプロパティ値も変更できません。
-
このクラスが、タイプ IdKey のインデックスを持っていて、そのインデックスが複数のプロパティに対するものである場合、それらのプロパティ値が連結されて ID を形成します。以下に例を示します。
CATEGORY12||SUBCATEGORYA
また、これらのプロパティ値も変更できません。
ID へのアクセス
オブジェクトの ID 値にアクセスするには、そのオブジェクトが %PersistentOpens in a new tab から継承する %Id() インスタンス・メソッドを使用します。
SAMPLES>set person=##class(Sample.Person).%OpenId(2)
SAMPLES>write person.%Id()
2
>>> person = iris.cls("Sample.Person")._OpenId(2)
>>> print(person._Id())
2
SQL では、オブジェクトの ID 値は、[%Id] という擬似フィールドとして使用できます。管理ポータルでテーブルを参照する場合には、[%Id] 擬似フィールドは ID というキャプションで表示されます。
キャプションはこのようになっていても、擬似フィールドの名前は [%Id] です。
ストレージ
各永続クラス定義には、そのクラス・プロパティが、それが実際に格納されているグローバルにどのようにマップされるかを示す情報が含まれています。この情報は、クラス・コンパイラによってクラスに対して生成され、開発者が変更してリコンパイルしたときに更新されます。
ストレージ定義の確認
この情報を確認すると役立つ場合があり、まれに、その詳細のいくつかを (注意深く) 変更することが必要になります。永続クラスの場合、統合開発環境 (IDE) はクラス定義の一部として、次に示すような定義を表示します。
<Storage name="Default">
<Data name="PersonDefaultData"><Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Name</Value>
</Value>
<Value name="3">
<Value>SSN</Value>
</Value>
<Value name="4">
<Value>DOB</Value>
</Value>
...
</Storage>
永続クラスによって使用されるグローバル
ストレージ定義には、データが格納されるグローバルを指定するいくつかの要素が含まれています。
<DataLocation>^Sample.PersonD</DataLocation>
<IdLocation>^Sample.PersonD</IdLocation>
<IndexLocation>^Sample.PersonI</IndexLocation>
...
<StreamLocation>^Sample.PersonS</StreamLocation>
既定では、既定のストレージで以下のようになります。
-
クラス・データは、そのクラスのデータ・グローバルに格納されます。その名前は、完全なクラス名 (パッケージ名を含む) で始まります。その名前に “D” が追加されます。例えば、Sample.PersonD のようになります。
-
インデックス・データは、そのクラスのインデックス・グローバルに格納されます。その名前は、クラス名で始まり、“I” で終わります。例えば、Sample.PersonI のようになります。
-
保存されるストリーム・プロパティは、そのクラスのストリーム・グローバルに格納されます。その名前は、クラス名で始まり、“S” で終わります。例えば、Sample.PersonS のようになります。
Important:
完全なクラス名が長い場合、システムでは、代わりにクラス名のハッシュした形式が自動的に使用されます。したがって、ストレージ定義を表示したときに、^package1.pC347.VeryLongCla4F4AD のようなグローバル名が表示されることがあります。何らかの理由で、クラスのデータ・グローバルを使用して直接作業する場合は、ストレージ定義を調べて、グローバルの実際の名前を確認する必要があります。
グローバル名がどのように決まるかは、"クラスの定義と使用" の "グローバル" を参照してください。
格納されるオブジェクトの既定の構造
一般的なクラスの場合、大部分のデータはデータ・グローバルに格納され、それには以下のノードが含まれています。
ノード |
ノードのコンテンツ |
^full_class_nameD(id)
full_class_name は、パッケージを含む完全なクラス名であり、31 文字までの長さにする必要がある場合はハッシュされます。また、id はオブジェクト ID であり、これについては、"オブジェクト ID" で説明しています。 |
$ListBuild によって返されるフォーマットのリスト。
このリストでは、格納されるプロパティは、ストレージ定義の <Value> 要素の name 属性によって指定される順序でリストされます。
当然ながら、一時プロパティは格納されません。ストリーム・プロパティは、そのクラスのストリーム・グローバルに格納されます。 |
使用例は、"格納されているデータの確認" を参照してください。
メモ
以下の点に注意してください。
-
格納されたデータを持つクラスのストレージを再定義したり、削除しないでください。その場合、ストレージを手動で再作成しなければならなくなります。それは、次にそのクラスをコンパイルしたときに作成される新しい既定のストレージは、そのクラスに必要なストレージと合わない可能性があるためです。
-
開発中に、クラスのストレージ定義のリセットが必要になる場合があります。それは、データも削除して、後でそれを再ロードまたは再生成する場合に実行できます。
-
既定では、開発中にプロパティを追加および削除すると、システムによってスキーマ進化というプロセスが使用されて自動的にストレージ定義が更新されます。
例外は、<Type> 要素に対して既定以外のストレージ・クラスを使用する場合です。既定は %Storage.Persistent です。このストレージ・クラスを使用しない場合、InterSystems IRIS によってストレージ定義が更新されることはありません。
永続クラスおよびテーブルを作成するためのオプション
永続クラスとそれに対応する SQL テーブルを作成するには、次のいずれかを実行します。
データへのアクセス
永続クラスに関連付けられたデータにアクセス、そのデータを変更および削除するには、以下のいずれかまたはすべてを実行するコードを作成できます。
-
永続クラスのインスタンスを開き、それらを変更および保存します。
-
永続クラスのインスタンスを削除します。
-
埋め込み SQL を使用します。
-
ダイナミック SQL (SQL 文と結果セット・インタフェース) を使用します。
-
Python から SQL を使用します。
-
直接グローバル・アクセスのための低レベルのコマンドおよび関数を使用します。この手法は、格納されている値を取得する場合以外は、お勧めしません。それは、この手法では、オブジェクトおよび SQL インタフェースによって定義されているロジックがバイパスされるためです。
InterSystems SQL は、以下のような場合に適しています。
-
最初の時点では開くインスタンスの ID が不明であり、代わりに、入力条件に基づいて 1 つ以上のインスタンスを選択する場合。
-
一括ロードまたは一括変更を実行する場合。
-
オブジェクト・インスタンスを開かずにそのデータを表示する場合
(ただし、オブジェクト・アクセスを使用する場合、同時処理ロックの程度を制御できます。データを変更する予定がない場合は、最小限の同時処理ロックを使用できます)。
-
SQL に精通している場合。
オブジェクト・アクセスは、以下のような場合に適しています。
格納されているデータの確認
このセクションでは、任意の永続オブジェクトに対して、オブジェクト・アクセス、SQL アクセス、および直接グローバル・アクセスを使用して同じ値が見えることを例示します。
IDE では、Sample.Person クラスを表示すると、次のプロパティ定義が表示されます。
/// Person's name.
Property Name As %String(POPSPEC = "Name()") [ Required ];
...
/// Person's age.<br>
/// This is a calculated field whose value is derived from <property>DOB</property>.
Property Age As %Integer [ details removed for this example ];
/// Person's Date of Birth.
Property DOB As %Date(POPSPEC = "Date()");
ターミナルでは、格納されているオブジェクトを開き、そのプロパティ値を書き込めます。
SAMPLES>set person=##class(Sample.Person).%OpenId(1)
SAMPLES>w person.Name
Newton,Dave R.
SAMPLES>w person.Age
21
SAMPLES>w person.DOB
58153
>>> person=iris.cls("Sample.Person")._OpenId(1)
>>> print(person.Name)
Newton, Dave R.
>>> print(person.Age)
21
>>> print(person.DOB)
58153
ここでは、DOB プロパティのリテラルの格納されている値 (リテラル) が表示されます。代わりに、このプロパティの表示値を返すメソッドを呼び出すこともできます。
SAMPLES>write person.DOBLogicalToDisplay(person.DOB)
03/20/2000
>>> print(iris.cls("%Date").LogicalToDisplay(person.DOB))
03/20/2000
管理ポータルでは、このクラスの格納されているデータを参照できます。以下のように表示されます。
この場合、DOB プロパティの表示値を表示できます。(ポータルでは、クエリを実行するもう 1 つのオプションがあり、そのオプションを使用すると、その結果に対して論理モードと表示モードのどちらを使用するのか制御できます。)
ポータルでは、このクラスのデータが格納されているグローバルを参照することもできます。
また、ターミナルでは、このインスタンスを含むグローバル・ノードの値を ObjectScript で書き込むことができます。
zwrite ^Sample.PersonD("1")
^Sample.PersonD(1)=$lb("","Newton,Dave R.","384-10-6538",58153,$lb("6977 First Street","Pueblo","AK",63163),
$lb("9984 Second Blvd","Washington","MN",42829),"",$lb("Red"))
スペースの関係で、最後の例には追加の改行が含まれています。
InterSystems SQL に対して生成されたコードのストレージ
InterSystems SQL の場合、データにアクセスするための再使用可能なコードがシステムによって生成されます。
最初に SQL 文を実行するときに、InterSystems IRIS によってクエリが最適化され、データを取得するコードが生成され、格納されます。コードは、最適化されたクエリ・テキストと共にクエリ・キャッシュに格納されます。このキャッシュは、コードのキャッシュであり、データのキャッシュではありません。
後で SQL 文を実行すると、InterSystems IRIS によって最適化され、そのクエリのテキストとクエリ・キャッシュの項目が比較されます。InterSystems IRIS によって、格納されているクエリが、指定されたクエリと一致していると判断された場合 (空白などのわずかな相違を除く)、そのクエリに対して格納されているコードが使用されます。
クエリ・キャッシュを表示し、その中に含まれている任意の項目を削除できます。
詳細
このページで説明したトピックの詳細は、以下のリソースを参照してください。
-
"クラスの定義と使用" では、InterSystems IRIS でのクラスおよびクラス・メンバの定義方法について説明しています。
-
"クラス定義リファレンス" には、クラス定義で使用するコンパイラ・キーワードのリファレンス情報があります。
-
"InterSystems SQL の使用法" では、InterSystems SQL の使用方法、およびこの言語を使用できる場所について説明しています。
-
"InterSystems SQL リファレンス" には、InterSystems SQL のリファレンス情報があります。
-
"グローバルの使用法" には、InterSystems IRIS でグローバルに永続オブジェクトを格納する方法が記載されています。
-
"インターシステムズ・クラス・リファレンス" には、InterSystems IRIS で提供されるすべての非内部クラスに関する情報があります。