Skip to main content

This is documentation for Caché & Ensemble. See the InterSystems IRIS version of this content.Opens in a new tab

For information on migrating to InterSystems IRISOpens in a new tab, see Why Migrate to InterSystems IRIS?

多次元ストレージの SQL およびオブジェクトの使用法

この章では、Caché オブジェクトおよび SQL エンジンが、どのように多次元ストレージ (グローバル) を利用して永続オブジェクト、リレーショナル・テーブル、インデックスを格納するかについて説明します。

データ・ストレージ構造は、Caché オブジェクトおよび SQL エンジンで自動的に提供および管理されますが、これらエンジンの動作を詳しく理解しておくことは無駄ではありません。

データのオブジェクト・ビューとリレーショナル・ビューで使用されるストレージ構造は同じものです。簡潔にするため、ここではオブジェクトの見地からのストレージのみ説明します。

データ

%CacheStorageOpens in a new tab ストレージ・クラス (既定) を使用する各永続クラスは、それ自体のインスタンスを、多次元ストレージ (グローバル) の 1 つ以上のノードを使用して Caché データベースに格納できます。

各永続クラスには、プロパティのグローバル・ノードへの格納法を指定したストレージ定義があります。このストレージ定義 (“既定構造” と呼ばれます) は、クラス・コンパイラによって自動的に管理されます。このストレージ定義は変更でき、必要に応じて代替バージョンを提供できます。これについてはこのドキュメントでは説明しません。

既定構造

永続オブジェクトの格納に使用される既定構造は、非常に単純です。

  • データは、グローバル名が完全なクラス名 (パッケージ名など) で始まるグローバル内に格納されます。データ・グローバル名には “D” を、インデックス・グローバルには “I” を付けて、それぞれの名前を作成します。

  • 各インスタンスのデータは、$List 構造内に置かれた、すべての一時的でないプロパティと併せて、データ・グローバルのシングル・ノード内に格納されます。

  • データ・グローバル内の各ノードは、オブジェクト ID 値で添え字が付けられています。既定では、オブジェクト ID 値は、データ・グローバルのルート (添え字なし) で格納されているカウンタ・ノードの $Increment 機能を呼び出すことにより提供される整数です。

例えば、2 つのリテラル・プロパティを持つ、単純な永続クラス MyApp.Person を定義するとします。

Class MyApp.Person Extends %Persistent
{
Property Name As %String;
Property Age As %Integer;
}

このクラスの 2 つのインスタンスを生成して保存すると、結果のグローバルは以下のようになります。

 ^MyApp.PersonD = 2  // counter node
 ^MyApp.PersonD(1) = $LB("",530,"Abraham")
 ^MyApp.PersonD(2) = $LB("",680,"Philip")

各ノードに格納されている $List 構造の最初の部分は空で、クラス名用に確保されます。この Person クラスのサブクラスのいずれかを定めると、このスロットはサブクラス名を含みます。(%PersistentOpens in a new tab クラスが提供する) %OpenId メソッドは、複数のオブジェクトが同じエクステント内に保存されている場合、この情報を使用して多様な形態で正しいタイプのオブジェクトを開きます。このスロットはクラス・ストレージ定義に、プロパティ名 “%%CLASSNAME” として表示されます。

詳細は、以下の "サブクラス" セクションを参照してください。

IDKEY

IDKEY 機能によって、オブジェクト ID として使用する値を明示的に定義できます。 これを行うには、IDKEY インデックス定義をクラスに追加し、ID 値を提供するプロパティを指定します。保存したオブジェクトのオブジェクト ID 値は変更できません。つまり、IDKEY 機能を使用したオブジェクトを保存した後は、オブジェクト ID が基としているプロパティを変更することはできません。

例えば、IDKEY インデックスを使用するために上記の例で使用した Person クラスは変更できます。

Class MyApp.Person Extends %Persistent
{
Index IDKEY On Name [ Idkey ];

Property Name As %String;
Property Age As %Integer;
}

Person クラスの 2 つのインスタンスを生成し保存すると、結果のグローバルは以下のようになります。

 ^MyApp.PersonD("Abraham") = $LB("",530,"Abraham")
 ^MyApp.PersonD("Philip") = $LB("",680,"Philip")

定義されたカウンタ・ノードは、既に存在していないことに注意してください。また、Name プロパティに基づいてオブジェクト ID を設定することで、Name の値はオブジェクトごとに一意とすることも必要になります。

IDKEY インデックスが複数のプロパティに基づく場合、メイン・データ・ノードは複数の添え字を持ちます。例えば以下のようになります。

Class MyApp.Person Extends %Persistent
{
Index IDKEY On (Name,Age) [ Idkey ];

Property Name As %String;
Property Age As %Integer;
}

この場合、結果のグローバルは以下のようになります。

 ^MyApp.PersonD("Abraham",530) = $LB("",530,"Abraham")
 ^MyApp.PersonD("Philip",680) = $LB("",680,"Philip")
Important:

プロパティが永続クラスのインスタンスに対する有効な参照ではない場合、IDKEY インデックスで使用するプロパティの値の中に、垂直バーの連続ペア (||) を入れることはできません。この制約は、Caché SQL メカニズムの機能上の方式により課せられるものです。IDKey プロパティ で || を使用すると、予測できない動作を起こす場合があります。

サブクラス

既定では、永続オブジェクトのサブクラスにより発生したフィールドは、追加ノードに格納されます。サブクラス名は、追加の添え字値として使用されます。

例えば、2 つのリテラル・プロパティを持つ、単純な永続クラス MyApp.Person を定義するとします。

Class MyApp.Person Extends %Persistent
{
Property Name As %String;
Property Age As %Integer;
}

ここで、2 つの追加リテラル・プロパティを取り込む、永続サブクラス MyApp.Student を定義します。

Class MyApp.Student Extends Person
{
Property Major As %String;
Property GPA As %Float;
}

この MyApp.Student クラスの 2 つのインスタンスを生成し保存すると、結果のグローバルは以下のようになります。

^MyApp.PersonD = 2  // counter node
^MyApp.PersonD(1) = $LB("Student",19,"Jack")
^MyApp.PersonD(1,"Student") = $LB(3.2,"Physics")

^MyApp.PersonD(2) = $LB("Student",20,"Jill")
^MyApp.PersonD(2,"Student") = $LB(3.8,"Chemistry")

Person クラスから派生したプロパティはメイン・ノードに格納され、Student クラスから取得したプロパティは追加サブノードに格納されます。この構造により、Student データと Person データを入れ替えて使用できるようになります。例えば、すべての Person オブジェクト名を扱っている SQL クエリは、Person データと Student データの両方を正確に取得します。また、この構造により、プロパティはスーパークラスかサブクラスのいずれかに追加されるため、クラス・コンパイラは、容易にデータ互換性を保持することができるようになります。

メイン・ノードの最初の部分は文字列 “Student” を含みますが、これが、Student データが含まれるノードを識別します。

親子リレーションシップ

親子リレーションシップ内で、子オブジェクトのインスタンスは、それが属する親オブジェクトのサブノードとして格納されます。この構造により、子インスタンス・データが親データと物理的にクラスタ化するようになります。

例えば、以下は 2 つの関連したクラスの 1 つ、Invoice の定義です。

/// An Invoice class
Class MyApp.Invoice Extends %Persistent
{
Property CustomerName As %String;

/// an Invoice has CHILDREN that are LineItems
Relationship Items As LineItem  [inverse = TheInvoice, cardinality = CHILDREN];
}

次に、LineItem は以下のようになります。

/// A LineItem class
Class MyApp.LineItem Extends %Persistent
{
Property Product As %String;
Property Quantity As %Integer;

/// a LineItem has a PARENT that is an Invoice
Relationship TheInvoice As Invoice [inverse = Items, cardinality = PARENT];
}

Invoice オブジェクトの複数のインスタンスを、関連付けた LineItem オブジェクトと共に格納すると、結果のグローバルは以下のようになります。

^MyApp.InvoiceD = 2  // invoice counter node
^MyApp.InvoiceD(1) = $LB("","Wiley Coyote")
^MyApp.InvoiceD(1,"Items",1) = $LB("","Rocket Roller Skates",2)
^MyApp.InvoiceD(1,"Items",2) = $LB("","Acme Magnet",1)

^MyApp.InvoiceD(2) = $LB("","Road Runner")
^MyApp.InvoiceD(2,"Items",1) = $LB("","Birdseed",30)

リレーションシップについての詳細は、"Caché オブジェクトの使用法" の "リレーションシップ" の章を参照してください。

埋め込みオブジェクト

埋め込みオブジェクトは、まずシリアル化された状態に変換され (既定では $List で、オブジェクトのプロパティを含む構造です)、その後他のプロパティと同様に、そのシリアル状態で格納されます。

例えば、2 つのリテラル・プロパティを持つ、単純な連続した (埋め込み) クラスを定義するとします。

Class MyApp.MyAddress Extends %SerialObject
{
Property City As %String;
Property State As %String;
}

前述の例を変更して、埋め込みの Home アドレス・プロパティを追加します。

Class MyApp.MyClass Extends %Persistent
{
Property Name As %String;
Property Age As %Integer;
Property Home As MyAddress;
}

このクラスの 2 つのインスタンスを生成して保存すると、結果のグローバルは以下のようになります。

 ^MyApp.MyClassD = 2  // counter node
 ^MyApp.MyClassD(1) = $LB(530,"Abraham",$LB("UR","Mesopotamia"))
 ^MyApp.MyClassD(2) = $LB(680,"Philip",$LB("Bethsaida","Israel"))

ストリーム

グローバル・ストリームは、32,000 バイト未満のデータの塊に分けられ、その塊の集合とされます。それがシーケンシャル・ノードに書き込まれ、グローバルに格納されます。ファイル・ストリームは外部ファイルに格納されます。

インデックス

永続クラスは、1 つ以上のインデックスを定義できます。インデックスは、処理 (ソートや条件付き検索など) をさらに効率的にするために使用される追加のデータ構造です。Caché SQL は、クエリを実行するときに、このようなインデックスを使用します。Caché オブジェクト、および SQL は、挿入、更新、削除の処理が実行されるときに、インデックス内に自動的に正しい値を保持します。

標準インデックスのストレージ構造

標準インデックスは、順序付けられた 1 つ以上のプロパティ値を、プロパティを含むオブジェクトのオブジェクト ID 値に関連付けます。

例えば、2 つのリテラル・プロパティ、および Name プロパティにインデックスを持つ、単純な永続クラスの MyApp.Person を定義するとします。

Class MyApp.Person Extends %Persistent
{
Index NameIdx On Name;

Property Name As %String;
Property Age As %Integer;
}

この Person クラスの複数のインスタンスを生成し保存すると、結果のデータおよびインデックス・グローバルは以下のようになります。

 // data global
 ^MyApp.PersonD = 3  // counter node
 ^MyApp.PersonD(1) = $LB("",34,"Jones")
 ^MyApp.PersonD(2) = $LB("",22,"Smith")
 ^MyApp.PersonD(3) = $LB("",45,"Jones")


 // index global
 ^MyApp.PersonI("NameIdx"," JONES",1) = ""
 ^MyApp.PersonI("NameIdx"," JONES",3) = ""
 ^MyApp.PersonI("NameIdx"," SMITH",2) = ""

インデックス・グローバルについては、以下の点に注意してください。

  1. 既定では、“I” (インデックス) が付いたクラス名をグローバル名として持つグローバルに配置します。

  2. 既定では、最初の添え字文字がインデックス名です。これにより、衝突することなく、同じグローバルへ複数のインデックスを格納することができます。

  3. 2 つ目の添え字は、照合されたデータ値を含みます。この場合、データは既定の SQLUPPER 照合機能を使用して照合されます。これによって、(大文字小文字の区別なしにソートするために) すべての文字を大文字に変換し、(強制的にすべてのデータを文字列として照合するために) 空白文字を付加します。

  4. 3 つ目の添え字は、インデックス付きのデータ値を含むオブジェクトのオブジェクト ID 値を含みます。

  5. ノード自体は空の状態です。 必要なデータはすべて添え字内にあります。データをインデックスと共に格納することを指定しているインデックス定義は、インデックス・グローバルのノードに配置されます。

すべての Person クラスを Name の順番でリストするクエリを初めとして、多数のクエリを満たすうえで十分な情報が、このインデックスに収められています。

ビットマップ・インデックス

ビットマップ・インデックスは標準インデックスと類似していますが、唯一異なる点は、インデックスが付いた値に対応するオブジェクト ID 値のセットを、一連のビット文字列を使用して格納することです。

ビットマップ・インデックスの論理処理

ビット文字列は、一連のビット (0 と 1 の値) を専用の圧縮形式で保持する文字列です。Caché には、ビット文字列を効率的に生成して使用するための関数が用意されています。以下はその説明です。

ビット文字列の処理
関数 説明
$Bit ビット文字列内のビットを設定または取得します。
$BitCount ビット文字列内のビット数を数えます。
$BitFind ビット文字列内で、あるビットが次に置かれている位置を見つけます。
$BitLogic 2 つ以上のビット文字列に対し、論理演算 (AND、OR) を実行します。

ビットマップ・インデックスでは、ビット文字列内の並び位置が、インデックス付きテーブル内の行 (オブジェクト ID 番号) に対応しています。ビットマップ・インデックスでは、指定された値が存在する行に対応する位置に 1 を持ち、その値が存在しない行に対応する位置に 0 を持つビット文字列が保持されます。ビットマップ・インデックスは、システムが割り当てた数値のオブジェクト ID を持つ、既定のストレージ構造を使用しているオブジェクトに対してのみ作用します。

例えば、以下のようなテーブルがあるとします。

ID 製品
1 MA Hat
2 NY Hat
3 NY Chair
4 MA Chair
5 MA Hat

State 列および Product 列にビットマップ・インデックスがある場合、そのインデックスは次の値で構成されます。

State 列のビットマップ・インデックスは以下のビット文字列値を含みます。

MA 1 0 0 1 1
NY 0 1 1 0 0

State が “MA” である行に対応した場所 (1、4、5) の値は 1 です。

同様に、Product 列のビットマップ・インデックスは、以下のビット文字列値を含みます (インデックス内では値が大文字に置き換えて照合されています)。

CHAIR 0 0 1 1 0
HAT 1 1 0 0 1

Caché SQL エンジンは、これらのインデックスで保持されているビット文字列に対して、繰り返し、文字列内部のビット数のカウント、または論理的な組み合わせ (AND、OR) などによるさまざまな演算を実行できます。例えば、State が “MA” で、Product が “HAT” である行を見つけるには、SQL エンジンでは、該当する複数のビット文字列を論理 AND で結合するだけです。

システムは、これらのインデックスに加え、“エクステント・インデックス” と呼ばれる別のインデックスを保持します。これは、存在する行に対応するすべての位置に 1 を持ち、存在しない行 (削除された行など) に対応するすべての位置に 0 を持つインデックスです。これは否定演算子など、特定の演算に対して使用します。

ビットマップ・インデックスのストレージ構造

ビットマップ・インデックスでは、順序付けられた 1 つ以上のプロパティ値の集合を、そのプロパティ値に対応したオブジェクト ID 値を持つ 1 つ以上のビット文字列と関連付けます。

例えば、2 つのリテラル・プロパティを持つ単純な永続クラスの MyApp.Person を定義し、クラスの Age プロパティにビットマップ・インデックスを定義するとします。

Class MyApp.Person Extends %Persistent
{
Index AgeIdx On Age [Type = bitmap];

Property Name As %String;
Property Age As %Integer;
}

この Person クラスの複数のインスタンスを生成し保存すると、結果のデータおよびインデックス・グローバルは以下のようになります。

 // data global
 ^MyApp.PersonD = 3  // counter node
 ^MyApp.PersonD(1) = $LB("",34,"Jones")
 ^MyApp.PersonD(2) = $LB("",34,"Smith")
 ^MyApp.PersonD(3) = $LB("",45,"Jones")

 // index global
 ^MyApp.PersonI("AgeIdx",34,1) = 110...
 ^MyApp.PersonI("AgeIdx",45,1) = 001...

 // extent index global
 ^MyApp.PersonI("$Person",1) = 111...
 ^MyApp.PersonI("$Person",2) = 111...

インデックス・グローバルについては、以下の点に注意してください。

  1. 既定では、“I” (インデックス) が付いたクラス名をグローバル名として持つグローバルに配置します。

  2. 既定では、最初の添え字文字がインデックス名です。これにより、衝突することなく、同じグローバルへ複数のインデックスを格納することができます。

  3. 2 つ目の添え字は、照合されたデータ値を含みます。これは数値データのインデックスのため、照合関数は適用されません。

  4. 3 つ目の添え字は、チャンク番号を含みます。効率を上げるため、ビットマップ・インデックスはそれぞれがテーブルのおよそ 64,000 行の情報を含む一連のビット文字列に分けられます。これら各ビット文字列がチャンクと呼ばれます。

  5. ノードには、このようなビット文字列が含まれます。

また、このテーブルにはビットマップ・インデックスが含まれるため、エクステント・インデックスが自動的に保持されます。このエクステント・インデックスは、インデックス・グローバル内に格納され、最初の添え字同様 “$” 文字の付いたクラス名を使用します。

ビットマップ・インデックスの直接アクセス

以下の例はクラス・エクステント・インデックスを使用して、保存されたオブジェクト・インスタンス (行) の合計数を計算します。$Order を使用して、エクステント・インデックスのチャンクを順番に処理します (各チャンクはおよそ 64,000 行の情報を含みます)。

/// Return the number of objects for this class.<BR>
/// Equivalent to SELECT COUNT(*) FROM Person
ClassMethod Count() As %Integer
{
    New total,chunk,data
    Set total = 0
    
    Set chunk = $Order(^MyApp.PersonI("$Person",""),1,data)
    While (chunk '= "") {
        Set total = total + $bitcount(data,1)
        Set chunk = $Order(^MyApp.PersonI("$Person",chunk),1,data)
    }

    Quit total
}
FeedbackOpens in a new tab