記事
· 2021年4月19日 8m read

配列プロパティ要素のための SQL インデックス

クラスの中で配列プロパティを使い、その要素 (キーと値の両方) によってスピーディに検索を実行できると非常に便利な場合があります (EAV モデルの場合は特に重宝します)。

それでは、簡単な例を見てみましょう。

Class User.eav Extends %Persistent
{

Index idx1 On attributes(ELEMENTS) [ Data = entity ];

Index idx2 On (attributes(KEYS), attributes(ELEMENTS)) [ Data = entity ];

Property entity;

Property attributes As array Of %String(SQLTABLENAME "attributes") [ SqlFieldName attr ];

/// d ##class(User.eav).RepopulateAll()
ClassMethod RepopulateAll()
{
  ..%DeleteExtent()
  
  name=$TR("Sibe^rian pi^ne ce^dar","^",$c(769))
  
  obj=..%New()
  obj.entity="Human"
  obj.attributes.SetAt(22,"Age")
  obj.attributes.SetAt(186,"Height")
  obj.attributes.SetAt("Jack","Name")
  obj.%Save()

  obj=..%New()
  obj.entity="Tree"
  obj.attributes.SetAt(186,"Age")
  obj.attributes.SetAt(22,"Height")
  obj.attributes.SetAt("Pines","Family")
  obj.attributes.SetAt(name,"Name")
  obj.%Save()
  
  /*
  
  ;or
  
  &sql(insert into eav(entity) select 'Human' union select 'Tree')
  &sql(insert into attributes(eav,element_key,attr)
  select 1,'Age',22 union
  select 1,'Height',186 union
  select 1,'Name','Jack' union
  select 2,'Age',186 union
  select 2,'Height',22 union
  select 2,'Family','Pines' union
  select 2,'Name',:name)
  */
  
  ..Reindex()
}

/// d ##class(User.eav).Reindex()
ClassMethod Reindex()
{
  ..%BuildIndices(,1)

  d $system.SQL.TuneTable("SQLUser.eav",1)
  d $system.SQL.TuneTable("SQLUser.attributes",1)
  d $system.OBJ.Compile($classname(),"cu/multicompile=1")
}

}

データを読み込んだ後に、以下を実行します。

USER>##class(User.eav).RepopulateAll()

すると、テーブルに以下のデータが表示されます。

ID entity
eav テーブル
1 Human
2 Tree

eav ID attr element_key
attributes テーブル
1 1||Age 22 Age
1 1||Name Jack Name
1 1||Height 186 Height
2 2||Age 186 Age
2 2||Height 22 Height
2 2||Name Sibérian píne cédar Name
2 2||Family Pines Family

データを持つグローバル

USER>zw ^User.eavD
^User.eavD=2
^User.eavD(1)=$lb("","Human")
^User.eavD(1,"attributes","Age")=22
^User.eavD(1,"attributes","Height")=186
^User.eavD(1,"attributes","Name")="Jack"
^User.eavD(2)=$lb("","Tree")
^User.eavD(2,"attributes","Age")=186
^User.eavD(2,"attributes","Family")="Pines"
^User.eavD(2,"attributes","Height")=22
^User.eavD(2,"attributes","Name")="Sibérian píne cédar"

インデックスを持つグローバル

USER>zw ^User.eavI
^User.eavI("idx1"," 186",1)=$lb("","Human")
^User.eavI("idx1"," 186",2)=$lb("","Tree")
^User.eavI("idx1"," 22",1)=$lb("","Human")
^User.eavI("idx1"," 22",2)=$lb("","Tree")
^User.eavI("idx1"," JACK",1)=$lb("","Human")
^User.eavI("idx1"," PINES",2)=$lb("","Tree")
^User.eavI("idx1"," SIBÉRIAN PÍNE CÉDAR",2)=$lb("","Tree")
^User.eavI("idx2","Age"," 186",2)=$lb("","Tree")
^User.eavI("idx2","Age"," 22",1)=$lb("","Human")
^User.eavI("idx2","Family"," PINES",2)=$lb("","Tree")
^User.eavI("idx2","Height"," 186",1)=$lb("","Human")
^User.eavI("idx2","Height"," 22",2)=$lb("","Tree")
^User.eavI("idx2","Name"," JACK",1)=$lb("","Human")
^User.eavI("idx2","Name"," SIBÉRIAN PÍNE CÉDAR",2)=$lb("","Tree")

それでは、以下のクエリを実行しましょう。

entity
select entity from eav where attributes->attr = 22
Human
Tree

クエリは実行されるものの、インデックスではなくフルスキャンが使われています。 SMP (System Management Portal) のテーブルを見ると、そこには生成されたはずの idx1 と idx2 がありません。

これが起るのは、SQL エンジンには、サブテーブルの配列のフィールドだけを基に作成された配列プロパティで、キー (すなわち propArray(KEY)) を持つもののインデックスしか「見えない」ためです。 両方のインデックスに「entity」フィールドがあるにもかかわらず、「attribute」サブテーブルにはそれが見当たりません。

また、attributes(KEYS) を持たない Index idx3 On attributes(ELEMENTS); も表示されません。但し、こちらのインデックス

  • Index idx4 On (attributes(KEYS), attributes(ELEMENTS));
  • Index idx5 On (attributes(ELEMENTS), attributes(KEYS));

は表示され、クエリに考慮されます。 しかし、これらはあらゆる種類のクエリに最適という訳ではありません。

では、SQL エンジンが配列プロパティ要素のインデックスを見えるようにするには、どのメソッドを使うのが一番簡単なのでしょうか?

Caché 2015.1 では、SetCollectionProjection/GetCollectionProjection メソッドを使ってサブテーブルに表示できるコレクションであれば、それをテーブルフィールドとして表示することができます。

ですが、この機能はデフォルトで無効になっています。

これらのメソッドは、Caché の過去のバージョンにはありませんが、手動で有効化できるかを試すことはできます。

%SYS>^%SYS("sql","sys","collection projection")=1

この変更を実行した後は、必ずクラスをもう一度コンパイルしてください。

それでは、このパラメーターをオン (1) にして、その動作を見てみましょう。

SMP にインデックスが表示されるようになりました。また、「eav」テーブルには「attr」という隠れたコレクションフィールドがあります。 しかし、それでも私たちが実行したクエリはインデックス idx1/idx2 を見つけられません。

それでは、お馴染みの述語 FOR SOME %ELEMENT を使って、この状況を解決しましょう。

entity
select entity from eav where for some %element(attr) (%value = 22)
Human
Tree

インデックス idx1 がクエリで使用されています。 これを少しだけ変更します。

entity
select entity from eav where for some %element(attr) (%value = 22 and %key'Age')
Human

entity
select entity from eav where for some %element(attr) (%value = 22 and %key'Height')
Tree

最後の 2 つの例では、インデックス idx1 の代わりにインデックス idx2 が使用されています。

UPD: SQLPROJECTION を使えば、これと同じことができます。つまり、以下を実行します。

Property attributes As array Of %String(SQLPROJECTION "table/column"SQLTABLENAME "attributes") [ SqlFieldName attr ];

この記事は、こちらの記事を翻訳したものです。 [@Evgeny Shvarov]、翻訳作業にご協力いただきありがとうございました。

この記事は、Habrahabr でもお読みいただけます。

本記事を書くきっかけとなったサイト: 17383689

WRC のフレームワークについてヒントをくださった [@Alexander Koblov] に深く感謝します。

ディスカッション (0)2
続けるにはログインするか新規登録を行ってください