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?

リレーションシップの定義と使用

この章では、リレーションシップについて説明します。リレーションシップは永続クラスでのみ定義可能な、特殊なプロパティです。以下のトピックについて説明します。

このドキュメントをオンラインで表示している場合は、このドキュメントの "序文" を使用すると、関連のあるトピックをすばやく見つけることができます。

リレーションシップの概要

リレーションシップとは、2 つの特定のタイプの永続オブジェクトの関連性です。2 つのオブジェクト間にリレーションシップを作成するには、それぞれのオブジェクトがリレーションシップの半分を定義するリレーションシップ・プロパティを持っている必要があります。Caché は一対多と親対子の、2 種類のリレーションシップを直接サポートしています。

Caché のリレーションシップは、以下の特性を持ちます。

  • リレーションシップは二項です。つまり、リレーションシップは 2 つのみのクラス間で定義されるか、あるいは 1 つのクラスとそれ自体との間で定義されます。

  • リレーションシップは、永続クラス間でのみ定義できます。

  • リレーションシップは双方向です。リレーションシップは両側で定義する必要があります。

  • リレーションシップは、参照整合性を自動的に提供します。これらは、SQL から外部キーとして見られます。このトピックの詳細は、“リレーションシップの SQL プロジェクション” を参照してください。

  • リレーションシップは、メモリ内の振る舞いやディスク上の振る舞いを自動的に管理します。

  • リレーションシップは、オブジェクト・コレクションでの優れたスケーリングや並行処理を提供します。

    一方、オブジェクト・コレクションには、オブジェクトの固有の順序があります。リレーションシップには同じことは当てはまりません。オブジェクト A、B、および C をこの順序でオブジェクトのリストに挿入すると、その順序が保持されます。オブジェクト A、B、および C をこの順序でリレーションシップ・プロパティに挿入しても、その順序は保持されません。

Note:

リレーションシップを追加するのではなく、永続クラス間に外部キーを定義することも可能です。外部キーにより、1 つのクラス内のオブジェクトが追加、更新、または削除されたときの動作内容を詳細に制御できるようになります。"Caché SQL の使用法" の “トリガの使用法” を参照してください。

一対多リレーションシップ

クラス A とクラス B の間の一対多リレーションシップでは、クラス A の 1 つのインスタンスが、クラス B のゼロ個以上のインスタンスに関連付けられます。

例えば、company (会社) クラスは、employee (従業員) クラスと一対多のリレーションシップを定義することがあります。この場合、それぞれの company オブジェクトに関連付けられた employee オブジェクトが 0 個以上あることを意味します。

これらのクラスは、以下に示すように相互に独立しています。

  • どちらかのクラスのインスタンスを作成したときに、そのインスタンスは、もう一方のクラスのインスタンスに関連付けても、関連付けなくてもかまいません。

  • クラス B のインスタンスが、クラス A の特定のインスタンスに関連付けられている場合、この関連付けは、削除することも変更することも可能です。クラス B のインスタンスは、クラス A の別のインスタンスに関連付けることができます。クラス B のインスタンスは、クラス A のインスタンスとの関連付けが必要なわけではありません (その逆も同じです)。

1 つのクラス内での一対多リレーションシップもあり得ます。そのクラスの 1 つのインスタンスを、そのクラスのゼロ個以上の別のインスタンスに関連付けることができます。例えば、Employee クラスでは、ある従業員と、その従業員に直接報告を行う別の従業員とのリレーションシップを定義することもできます。

親子リレーションシップ

クラス A とクラス B との間の親子リレーションシップの場合は、クラス A の 1 つのインスタンスが、クラス B のゼロ個以上のインスタンスに関連付けられます。また、以下に示すように、子テーブルは親テーブルに依存します。

  • クラス B のインスタンスを保存するとき、そのインスタンスは、クラス A のインスタンスと必ず関連付けられます。インスタンスを保存しようとしたときに、その関連付けが定義されていないと、保存アクションは失敗します。

  • この関連付けは変更できません。そのため、クラス B のインスタンスをクラス A の別のインスタンスに関連付けることはできません。

  • クラス A のインスタンスが削除されると、それに関連付けられたクラス B のすべてのインスタンスも削除されます。

  • クラス B のインスタンスは削除できます。クラス A は、クラス B のインスタンスとの関連付けを必要としません。

例えば、invoice (送り状) クラスでは、line item (明細) クラスとの親子リレーションシップを定義できます。この場合、送り状はゼロ個以上の明細で構成されます。このような明細は、別の送り状に移動することはできません。それらが独自に意味を持つこともありません。

Important:

また、子テーブル (クラス B) 内の ID は、純粋な数字ではありません。結果として、このクラスのリレーションシップ・プロパティにビットマップ・インデックスを追加することは不可能になります。ただし、別の形式のインデックスは追加できます (インデックスの追加は、この章で後述するように役立ちます)。

親子リレーションシップとストレージ

クラスのコンパイル前に親子リレーションシップを定義すると、両方のクラスのデータは同一のグローバルに保存されます。以下に示すような構造で、子のデータは親のデータの下位に配置されます。

^Inv(1)
^Inv(1, "invoice", 1)
^Inv(1, "invoice", 2)
^Inv(1, "invoice", 3)
...

これにより、Caché は、このような関連オブジェクトをより高速に読み書きできるようになります。

一般的なリレーションシップ用語

このセクションでは、リレーションシップに関する説明を円滑に進めるために、一般に使用されている語句について例を挙げて説明します。

会社と従業員との間の一対多リレーションシップについて考えてみます。つまり、1 つの会社は複数の従業員を抱えているということです。このシナリオでは、会社は一側と呼ばれ、従業員は多側と呼ばれます。

同様に、会社と製品との間の親子リレーションシップについて考えてみます。つまり、会社は親であり、製品が子であるということです。このシナリオでは、会社は親側と呼ばれ、従業員は子側と呼ばれます。

リレーションシップの定義

2 つのクラスのレコード間にリレーションシップを作成するには、補完的なリレーションシップ・プロパティを 1 組 (それぞれのクラスに 1 つずつ) 作成します。同一クラスのレコード間にリレーションシップを作成するには、そのクラスに補完的なリレーションシップ・プロパティを 1 組作成します。

スタジオには、このタスクを簡単にする、便利なウィザード (新規プロパティ・ウィザード) が用意されています。詳細は、"スタジオの使用法" の “リレーションシップ” のセクションを参照してください。

以下のサブセクションでは、一般的な構文について説明してから、一対多リレーションシップ親子リレーションシップを定義する方法について説明します。

一般的な構文

リレーションシップ・プロパティの構文は、以下のとおりです。

Relationship Name As classname [ Cardinality = cardinality_type, Inverse = inverseProp ];

以下はその説明です。

  • classname は、このリレーションシップが参照するクラスです。これは、永続クラスである必要があります。

  • cardinality_type (必須) は、こちら側からリレーションシップがどのように “見える” かを定義すると同時に、独立リレーションシップ (一対多) か、依存リレーションシップ (親子) かを定義します。cardinality_type は、one (一)、many (多)、parent (親)、または children (子) のいずれかです。

  • inverseProp (必須) は、もう一方のクラスで定義される、補完的なリレーションシップ・プロパティの名前です。

補完的なリレーションシップ・プロパティでは、cardinality_type キーワードは、ここの cardinality_type キーワードを補完するものにする必要があります。値 onemany は、相互に補完するものです。同様に、値 parentchildren は、相互に補完します。

リレーションシップはプロパティの一種であるため、その他のプロパティ・キーワード (Final、Required、SqlFieldName、Private などを含む) も使用できます。MultiDimensional など、一部のプロパティのキーワードは、適用されません。詳細は、"クラス定義リファレンス" を参照してください。

一対多リレーションシップの定義

このセクションでは、classAclassB との間に、一対多リレーションシップを定義する方法について説明します。ここでは、classA の 1 つのインスタンスが classB のゼロ個以上のインスタンスに関連付けられます。

Note:

1 つのクラスに含まれるレコード間で一対多リレーションシップを持つことができます。そのため、以下の説明の classAclassB は、同じクラスでもかまいません。

classA には、以下の形式のリレーションシップ・プロパティを含める必要があります。

Relationship manyProp As classB [ Cardinality = many, Inverse = oneProp ];

oneProp は、補完的なリレーションシップ・プロパティの名前です。このプロパティは、classB で定義されます。

classB には、以下の形式のリレーションシップ・プロパティを含める必要があります。

Relationship oneProp As classA [ Cardinality = one, Inverse = manyProp ];

manyProp は、補完的なリレーションシップ・プロパティの名前です。このプロパティは、classA で定義されます。

Important:

一側 (classA) では、リレーションシップはクエリを使用してリレーションシップ・オブジェクトを生成します。ほとんどの場合、このクエリのパフォーマンスは、補完的なリレーションシップ・プロパティにインデックスを追加する (つまり、多側の class B にインデックスを追加する) ことで向上できます。

スタジオの新規プロパティ・ウィザードでは、そのようなインデックスの作成を促すプロンプトが表示されます。詳細は、"スタジオの使用法" の “リレーションシップ” のセクションを参照してください。

親子リレーションシップの定義

このセクションでは、classAclassB との間に、親子リレーションシップを定義する方法について説明します。ここでは、classA の 1 つのインスタンスが、classB のゼロ個以上のインスタンスの親になります。これらは、同一のクラスにすることはできません。

classA には、以下の形式のリレーションシップ・プロパティを含める必要があります。

Relationship childProp As classB [ Cardinality = children, Inverse = parentProp ];

parentProp は、補完的なリレーションシップ・プロパティの名前です。このプロパティは、classB で定義されます。

classB には、以下の形式のリレーションシップ・プロパティを含める必要があります。

Relationship parentProp As classA [ Cardinality = parent, Inverse = childProp ];

childProp は、補完的なリレーションシップ・プロパティの名前です。このプロパティは、classA で定義されます。

Important:

親側 (classA) では、リレーションシップはクエリを使用してリレーションシップ・オブジェクトを生成します。ほとんどの場合、このクエリのパフォーマンスは、補完的なリレーションシップ・プロパティにインデックスを追加する (つまり、子側の class B にインデックスを追加する) ことで向上できます。

スタジオの新規プロパティ・ウィザードでは、そのようなインデックスの作成を促すプロンプトが表示されます。詳細は、"スタジオの使用法" の “リレーションシップ” のセクションを参照してください。

親子リレーションシップとコンパイル

親子リレーションシップの場合、Caché では、前述のとおり、親オブジェクトと子オブジェクトのデータを 1 つのグローバルに保存するストレージ定義を生成できます。こうしたストレージ定義により、これらの関連オブジェクトにアクセスする速度を向上できます。

クラスのコンパイル後にリレーションシップを追加すると、Caché は、この最適化されたストレージ定義を生成しません。そのような場合は、すべてのテスト・データを削除し、2 つのクラスのストレージ定義を削除してからリコンパイルします。

このセクションでは、一対多リレーションシップと、親子リレーションシップの例を示します。

一対多リレーションシップの例

この例は、会社 (company) と従業員 (employee) との間の一対多リレーションシップを表しています。会社クラスは、次のようになります。

Class MyApp.Company Extends %Persistent
{

Property Name As %String;

Property Location As %String;

Relationship Employees As MyApp.Employee [ Cardinality = many, Inverse = Employer ];

}

また、従業員クラスは、次のようになります。

Class MyApp.Employee Extends (%Persistent, %Populate)
{

Property FirstName As %String;

Property LastName As %String;

Relationship Employer As MyApp.Company [ Cardinality = one, Inverse = Employees ];

Index EmployerIndex On Employer;

}

親子リレーションシップの例

この例は、送り状 (invoice) と明細 (line item) との間の一対多リレーションシップを示しています。送り状クラスは以下のようになります。

Class MyApp.Invoice Extends %Persistent
{

Property Buyer As %String;

Property InvoiceDate As %TimeStamp;

Relationship LineItems As MyApp.LineItem [ Cardinality = children, Inverse = Invoice ];

}

明細クラスは以下のようになります。

Class MyApp.LineItem Extends %Persistent
{

Property ProductSKU As %String;

Property UnitPrice As %Numeric;

Relationship Invoice As MyApp.Invoice [ Cardinality = parent, Inverse = LineItems ];

Index InvoiceIndex On Invoice;

}

オブジェクトの接続

リレーションシップは双方向です。具体的には、一方のオブジェクトのリレーションシップ・プロパティの値を更新すると、その影響は、関連するオブジェクトの対応するリレーションシップ・プロパティの値に即座に現れます。したがって、一方のオブジェクトでリレーションシップ・プロパティの値を指定して、両方のオブジェクトに反映させることができます。

リレーションシップ・プロパティの性質は、2 つのクラスで異なります。そのため、リレーションシップの更新には、一般的なシナリオが 2 つ存在します。

  • シナリオ 1 : リレーションシップ・プロパティは単純な参照プロパティです。プロパティを、該当するオブジェクトと等しくなるように設定します。

  • シナリオ 2 : リレーションシップ・プロパティは %RelationshipObjectOpens in a new tab のインスタンスです。このインスタンスには配列のようなインタフェースがあります。このインタフェースのメソッドを使用して、リレーションシップにオブジェクトを挿入します。リレーションシップ内のオブジェクトは、順序付けされないことに注意してください。リレーションシップでは、それにオブジェクトを挿入した際の順序は保持されません。

次のサブセクションで詳細を説明します。3 番目のサブセクションでは、シナリオ 1 のバリエーションについて説明します。このバリエーションは、リレーションシップに多数のオブジェクトがある場合に特に適しています。

ここでは、オブジェクトをリレーションシップに追加する方法について説明します。オブジェクトの変更プロセスはほぼ同じですが、親子リレーションシップの場合には、重要な (設計上の) 例外があります。特定の親オブジェクトに関連付けられた (その後で保存された) 子オブジェクトは、別の親に関連付けられなくなります。

シナリオ 1 : 多または子側の更新

多側または子側 (ObjA) では、リレーションシップ・プロパティは、ObjB を指す単純な参照プロパティです。こちら側からオブジェクトに接続するには、以下の手順を実行します。

  1. もう一方のクラスのインタンスに向けた OREF (ObjB) を取得します。(適宜、新しいオブジェクトを作成するか、既存のオブジェクトを開きます。)

  2. ObjA のリレーションシップ・プロパティを ObjB と等しくなるように設定します。

例えば、前述した親子クラスの例について考えてみます。以下の手順では、リレーションシップを MyApp.LineItem 側から更新します。

  //obtain an OREF to the invoice class
 set invoice=##class(MyApp.Invoice).%New()
 //...specify invoice date and so on

 set item=##class(MyApp.LineItem).%New()
 //...set some properties of this object such as the product name and sale price...

 //connect the objects
 set item.Invoice=invoice

item オブジェクトの %Save() メソッドを呼び出すと、システムにより、両方のオブジェクト (item および invoice) が保存されます。

この手法のバリエーションについては、最後のサブセクションも参照してください。

シナリオ 2 : 一または親側の更新

一側または親側では、リレーションシップ・プロパティは、%RelationshipObjectOpens in a new tab のインスタンスです。こちら側では、オブジェクトに接続するために、以下の手順を実行できます。

  1. もう一方のオブジェクトのインタンスに向けた OREF を取得します。(適宜、新しいオブジェクトを作成するか、既存のオブジェクトを開きます。)

  2. こちら側のリレーションシップ・プロパティの Insert() メソッドを呼び出して、引数として OREF を渡します。

前述した親子クラスの例について考えてみます。それらのクラスの場合は、以下の手順により、リレーションシップを MyApp.Invoice 側から更新することになります。

 set invoice=##class(MyApp.Invoice).%OpenId(100034)
 //set some properties such as the customer name and invoice date

 set item=##class(MyApp.LineItem).%New()
 //...set some properties of this object such as the product name and sale price...

 //connect the objects
 do invoice.LineItems.Insert(item)

invoice オブジェクトの %Save() メソッドを呼び出すと、システムにより、両方のオブジェクト (item および invoice) が保存されます。

Important:

Caché は、オブジェクトがリレーションシップに追加される順序に関する情報を維持しません。そのため、以前に保存したオブジェクトを開いて、GetNext() などのメソッドを使用して、リレーションシップに繰り返し処理を実行すると、そのリレーションシップ内のオブジェクトの順序は、オブジェクトを作成したときの順序とは異なります。

オブジェクト接続の近道

比較的多数のオブジェクトをリレーションシップに追加する必要がある場合は、シナリオ 1 で示した手法のバリエーションを使用します。このバリエーションの内容は、以下のようになります。

  1. クラス A の OREF (ObjA) を取得します。

  2. クラス B のインスタンスの ID を取得します。

  3. ObjA のリレーションシップ・プロパティのプロパティ・セッター・メソッドを使用します。このメソッドには、引数として ID を渡します。

    リレーションシップ・プロパティの名前が MyRel の場合、プロパティ・セッター・メソッドの名前は MyRelSetObjectId() になります。

    プロパティ・セッター・メソッドの詳細は、“プロパティ・メソッドの使用とオーバーライド” の章を参照してください。

シナリオ 1 で説明した例のクラスを考えてみます。それらのクラスの場合は、以下の手順により、多数の送り状項目を 1 つの送り状に挿入することになります (当該のセクションで説明した手法よりもすばやく実行できます)。

 set invoice=##class(MyApp.Invoice).%New()
 //set some properties such as the customer name and invoice date
 do invoice.%Save()
 set id=invoice.%Id()
 kill invoice  //OREF is no longer needed
 
 for index = 1:1:(1000)
  {
    set Item=##class(MyApp.LineItem).%New()
    //set properties of the invoice item
    
    //connect to the invoice
    do Item.InvoiceSetObjectId(id)
    do Item.%Save()
  } 

リレーションシップの削除

一対多リレーションシップでは、2 つのオブジェクト間のリレーションシップを削除できます。これを行う方法の 1 つを以下に示します。

  1. 子オブジェクト (または多側のオブジェクト) のインスタンスを開きます。

  2. このオブジェクトの該当するプロパティを null に設定します。

例えば、SAMPLES ネームスペースには、Sample.CompanyOpens in a new tabSample.EmployeeOpens in a new tab との間に一対多リレーションシップが存在します。ID 101 の従業員 (employee ) が、ID 5 の会社 (company) に勤めている場合の例を以下に示します。この会社には、4 人の従業員がいることに注目してください。

SAMPLES>set e=##class(Sample.Employee).%OpenId(101)
 
SAMPLES>w e.Company.%Id()
5
SAMPLES>set c=##class(Sample.Company).%OpenId(5)
 
SAMPLES>w c.Employees.Count()
4

次に、この従業員について、Company プロパティを null に設定します。この会社の従業員が 3 人になったことに注目してください。

SAMPLES>set e.Company=""
 
SAMPLES>w c.Employees.Count()
3

別のオブジェクトに変更を加えることで、リレーションシップを削除することもできます。ここでは、コレクション・プロパティの RemoveAt() メソッドを使用します。例えば、会社の ID は 17、最初の従業員の従業員 ID は 102 の場合の例を以下に示します。

SAMPLES>set e=##class(Sample.Employee).%OpenId(102)
 
SAMPLES>w e.Company.%Id()
17
SAMPLES>set c=##class(Sample.Company).%OpenId(17)
 
SAMPLES>w c.Employees.Count()
4
SAMPLES>w c.Employees.GetAt(1).%Id()
102

この会社とこの従業員との間のリレーションシップを削除するには、RemoveAt() メソッドを使用し、引数として値 1 を渡して、最初のコレクション項目を削除します。これを実行すると、この会社の従業員は 3 人になることに注目してください。

SAMPLES>do c.Employees.RemoveAt(1)
 
SAMPLES>w c.Employees.Count()
3

親子リレーションシップの場合、2 つのオブジェクト間のリレーションシップを削除することはできません。ただし、子オブジェクトを削除することは可能です。

リレーションシップ内のオブジェクトの削除

一対多リレーションシップの場合は、以下の規則により、オブジェクトを削除しようとしたときの動作が制御されます。

  • 一側のオブジェクトは、そのオブジェクトを参照する多側のオブジェクトが存在していると、リレーションシップによって削除が妨げられます。例えば、会社を削除しようとしたときに、従業員テーブルにその会社を指しているレコードが存在していると、削除操作が失敗します。

    そのため、多側のレコードを先に削除しておく必要があります。

  • 多側 (従業員テーブル) のオブジェクトの削除が、リレーションシップによって妨げられることはありません。

親子リレーションシップの場合は、規則が異なります。

  • リレーションシップにより、親側の削除が子側に影響します。具体的には、親側のオブジェクトを削除すると、それに関連付けられた子側のオブジェクトが自動的に削除されます。

    例えば、送り状と明細との間の親子リレーションシップがある場合は、送り状を削除すると、その明細が削除されます。

  • 子側 (明細テーブル) のオブジェクトの削除が、リレーションシップによって妨げられることはありません。

リレーションシップを使用した作業

リレーションシップは、プロパティです。一または親のカーディナリティを持つリレーションシップは、アトミック (非コレクション) の参照プロパティのように動作します。多または子のカーディナリティを持つリレーションシップは、配列のようなインタフェースを持つ %RelationshipObjectOpens in a new tab クラスのインスタンスです。

例えば、以下の方法で、上記で定義された Company および Employee オブジェクトを使用できます。

 // create a new instance of Company
 Set company = ##class(MyApp.Company).%New()
 Set company.Name = "Chiaroscuro LLC"

 // create a new instance of Employee
 Set emp = ##class(MyApp.Employee).%New()
 Set emp.LastName = "Weiss"
 Set emp.FirstName = "Melanie"

 // Now associate Employee with Company
 Set emp.Employer = company

 // Save the Company (this will save emp as well)
 Do company.%Save()

 // Close the newly created objects 
 Set company = ""
 Set emp = ""

リレーションシップは、メモリ内で完全に双方向です。一方での操作を、他方ですぐに見ることができます。したがって、上記のコードは以下の company に対して操作するコードと同じです。

 Do company.Employees.Insert(emp)

 Write emp.Employer.Name 
 // this will print out "Chiaroscuro LLC"

リレーションシップは、ディスクからロードすることができ、他の多様なプロパティを使用するように使用できます。関連オブジェクトを一側から参照すると、その関連オブジェクトは自動的に参照 (オブジェクト値) プロパティと同じ方法でメモリにスウィズルされます。関連オブジェクトを多側から参照すると、その関連オブジェクトはすぐにはスウィズルされません。代わりに、一時的な %RelationshipObjectOpens in a new tab コレクション・オブジェクトが作成されます。メソッドがこのコレクションに呼び出されるとすぐに、リレーションシップ内のオブジェクト ID 値を持っているリストを構築します。これは、実際に関連するオブジェクトが、メモリにスウィズルされるコレクション内のオブジェクトの 1 つを参照するときに適用されます。

以下は、特定の Company に関連するすべての Employee オブジェクトを表示する例です。

 // open an instance of Company
 Set company = ##class(Company).%OpenId(id)

 // iterate over the employees; print their names
 Set key = ""

 Do {
    Set employee = company.Employees.GetNext(.key)
    If (employee '= "") {
        Write employee.Name,!
    }
 } While (key '= "")

この例では、company を閉じることで、メモリから Company オブジェクトや、それに関連するすべての Employee オブジェクトを削除します。しかし、ループが終了するまでにリレーションシップに含まれるすべての Employee オブジェクトが (同時に) メモリ内にロードされることに注意してください。このオペレーションが使用する (多くの Employee オブジェクトが存在する) メモリの容量を削減するために、名前を表示した後に %UnSwizzleAt() メソッドを呼び出すことによって、Employee オブジェクトを “アンスウィズル” するようにループを変更します。

 Do {
    Set employee = company.Employees.GetNext(.key)
    If (employee '= "") {
        Write employee.Name,!
        // remove employee from memory
        Do company.Employees.%UnSwizzleAt(key)
    }
 } While (key '= "")
Important:

リレーションシップは、リスト・インタフェースをサポートしません。これは、関連するオブジェクト数をカウントしたりオブジェクトの繰り返し処理を行うのに、ポインタを 1 から 1 ずつインクリメントする方法は使用できないことを意味します。代わりに、配列コレクション・スタイルの繰り返しを使用する必要があります。リレーションシップ内のオブジェクトの繰り返し処理の詳細は、%Library.RelationshipObjectOpens in a new tab の参照ページを参照してください。

リレーションシップの SQL プロジェクション

このドキュメントで前述したように、永続クラスは SQL テーブルとして投影されます。このセクションでは、それらのクラスのリレーションシップがどのように SQL に投影されるかについて説明します。

Note:

関連するクラスの他のプロパティのプロジェクションは変更できますが、リレーションシップの SQL プロジェクション自体は変更できません。例えば、リレーションシップの CLASSNAME プロパティ・パラメータを指定することはサポートされていません。このパラメータについては、このドキュメントで前述の、“オブジェクト値プロパティの定義” で説明されています。

一対多リレーションシップの SQL プロジェクション

このセクションでは、一対多リレーションシップの SQL プロジェクションについて説明します。一例として、前述した一対多クラスの例について考えてみます。この場合、クラスは以下のように投影されます。

  • 一側 (会社クラス内) には、リレーションシップを表すフィールドがありません。会社テーブルには、その他のプロパティ用のフィールドはありますが、従業員を保持するフィールドはありません。

  • 多側 (従業員クラス内) では、リレーションシップは単純な参照プロパティであり、そのプロパティは他の参照プロパティと同じ方法で SQL に投影されます。従業員テーブルには、Employer (雇用主) という名前のフィールドがあり、このフィールドが会社テーブルを指しています。

これらのテーブルに対して同時にクエリを実行するには、以下の例に示すように、従業員テーブルに対してクエリを実行し、矢印構文を使用します。

SELECT Employer->Name, LastName,FirstName FROM MyApp.Employee

または、以下の例に示すように、明示的な結合を実行します。

SELECT c.Name, e.LastName, e.FirstName FROM MyApp.Company c, MyApp.Employee e WHERE e.Employer = c.ID 

また、このリレーションシップ・プロパティのペアは、暗黙的に外部キーを従業員テーブルに追加しています。この外部キーには、UPDATE および DELETE が含まれていて、どちらも NOACTION が指定されます。

親子リレーションシップの SQL プロジェクション

同様に、前述したように親子クラスの例について考えてみます。この例には、送り状と、その明細との間に親子リレーションシップがあります。この場合、クラスは以下のように投影されます。

  • 親側 (送り状クラス内) には、リレーションシップを表すフィールドがありません。送り状テーブルには、その他のプロパティ用のフィールドはありますが、明細を保持するフィールドはありません。

  • 子側 (明細クラス内) では、リレーションシップは単純な参照プロパティであり、そのプロパティは他の参照プロパティと同じ方法で SQL に投影されます。明細テーブルには、Invoice (送り状) という名前のフィールドがあり、このフィールドが送り状テーブルを指しています。

  • さらに、子側の ID には、常に親レコードの ID が含まれます。その子にのみ基づいて IDKey を作成しようとしても同じことになります。また、子クラスの IDKey の定義に明示的に親のリレーションシップを設定しておくと、これがコンパイラで認識されるので、親のリレーションシップが重複して追加されることがありません。これにより、生成したグローバル参照内の添え字として親の参照が使用される順序を変更できます。

    その結果として、このプロパティにはビットマップ・インデックスを追加できなくなります。ただし、別の形式のインデックスは追加できます。

これらのテーブルに対して同時にクエリを実行するには、以下の例に示すように、送り状テーブルに対してクエリを実行し、矢印構文を使用します。

SELECT 
Invoice->Buyer, Invoice->InvoiceDate, ID, ProductSKU, UnitPrice
FROM MyApp.LineItem

または、以下の例に示すように、明示的な結合を実行します。

SELECT 
i.Buyer, i.InvoiceDate, l.ProductSKU,l.UnitPrice 
FROM MyApp.Invoice i, MyApp.LineItem l 
WHERE i.ID = l.Invoice 

また、子側のクラスの場合、投影されたテーブルは、他方のテーブルの子テーブルとして “採用” されます。

多対多リレーションシップの作成

Caché では、多対多リレーションシップを直接はサポートしていませんが、このセクションでは、そのようなリレーションシップを間接的にモデル化する方法について説明します。

クラス A とクラス B との間に多対多リレーションシップを確立するには、以下の手順を実行します。

  1. それぞれのリレーションシップを定義する中間クラスを作成します。

  2. そのクラスとクラス A との間に、一対多リレーションシップを定義します。

  3. そのクラスとクラス B との間に、一対多リレーションシップを定義します。

その後、クラス A のインスタンスとクラス B のインスタンスとの間のリレーションシップごとに、中間クラスにレコードを作成します。

例えば、クラス A で doctor (医者) を定義し、このクラスでプロパティ Name (名前) と Specialty (診療科目) を定義するとします。クラス B で patient (患者) を定義し、このクラスでプロパティ Name (名前) と Address (住所) を定義します。医者と患者との間の多対多リレーションシップをモデル化するため、以下に示すように中間クラスを定義できます。

/// Bridge class between MN.Doctor and MN.Patient
Class MN.DoctorPatient Extends %Persistent
{

Relationship Doctor As MN.Doctor [ Cardinality = one, Inverse = Bridge ];

Index DoctorIndex On Doctor;

Relationship Patient As MN.Patient [ Cardinality = one, Inverse = Bridge ];

Index PatientIndex On Patient;
}

次に、医者クラスは以下のようになります。

Class MN.Doctor Extends %Persistent
{

Property Name;

Property Specialty;

Relationship Bridge As MN.DoctorPatient [ Cardinality = many, Inverse = Doctor ];

}

さらに、患者クラスは以下のようになります。

Class MN.Patient Extends %Persistent
{

Property Name;

Property Address;

Relationship Bridge As MN.DoctorPatient [ Cardinality = many, Inverse = Patient ];

}

医者と患者の両方に対してクエリを実行する最も簡単な方法は、中間テーブルに対してクエリを実行することです。以下に例を示します。

SELECT top 20 Doctor->Name as Doctor, Doctor->Specialty, Patient->Name as Patient 
FROM MN.DoctorPatient order by doctor
 
Doctor  Specialty       Patient
Davis,Joshua M. Dermatologist   Wilson,Josephine J.
Davis,Joshua M. Dermatologist   LaRocca,William O.
Davis,Joshua M. Dermatologist   Dunlap,Joe K.
Davis,Joshua M. Dermatologist   Rotterman,Edward T.
Davis,Joshua M. Dermatologist   Gibbs,Keith W.
Davis,Joshua M. Dermatologist   Black,Charlotte P.
Davis,Joshua M. Dermatologist   Dunlap,Joe K.
Davis,Joshua M. Dermatologist   Rotterman,Edward T.
Li,Umberto R.   Internist       Smith,Wolfgang J.
Li,Umberto R.   Internist       Ulman,Mo O.
Li,Umberto R.   Internist       Gibbs,Keith W.
Li,Umberto R.   Internist       Dunlap,Joe K.
Quixote,William Q.      Surgeon Black,Charlotte P.
Quixote,William Q.      Surgeon LaRocca,William O.
Quixote,William Q.      Surgeon Black,Charlotte P.
Quixote,William Q.      Surgeon Smith,Wolfgang J.
Quixote,William Q.      Surgeon LaRocca,William O.
Quixote,William Q.      Surgeon LaRocca,William O.
Quixote,William Q.      Surgeon Black,Charlotte P.
Salm,Jocelyn Q. Allergist       Tsatsulin,Mark S.

バリエーションとして、一対多リレーションシップのうちの 1 つの代わりに親子リレーションシップを使用します。これにより、この章の前半で説明したように、データの物理クラスタリングを実現できます。ただし、これは、そのリレーションシップにはビットマップ・インデックスが使用できないことを意味します。

外部キーによるバリエーション

中間クラスとクラス A および B との間にリレーションシップを定義するのではなく、参照プロパティと外部キーを使用すると、中間クラス MN.DoctorPatient は前述のバージョンの代わりに次のようになります。

Class MN.DoctorPatient Extends %Persistent
{

Property Doctor As MN.Doctor;

ForeignKey DoctorFK(Doctor) References MN.Doctor();

Property Patient As MN.Patient;

ForeignKey PatientFK(Patient) References MN.Patient();

}

外部キーの詳細は、"Caché SQL の使用法" の “外部キーの使用法” を参照してください。"クラス定義リファレンス" のリファレンス “クラス定義” の “外部キー定義” も参照してください。

単純な外部キー・モデルを使用する 1 つの利点は、不注意による大量のオブジェクトのスウィズリングが発生しなくなることです。1 つの欠点は、自動的なスウィズリングが使用できないことです。

FeedbackOpens in a new tab