17 グラフ・サーバー(PGX)に対するPGQL問合せの実行

このセクションでは、グラフ・サーバー(PGX)でPGQL問合せを実行するために使用されるJava APIについて説明します。

17.1 PGQLスタート・ガイド

グラフ・サーバー(PGX)でPGQLを開始します。

このセクションでは、PGQLの開始方法の例を示します。事前に設定されているデータベース・レルムを想定しています(データベース認証のためのグラフ・サーバーの準備のステップに従ってください)。また、ユーザーがHRスキーマへのreadアクセス権を持っていることも想定しています。

最初に、CREATE PROPERTY GRAPH文を実行して、従業員、部門およびemployee works at departmentを含むグラフを作成します。

例17-1 グラフ・サーバー(PGX)でのグラフの作成

次の文は、グラフ・サーバー(PGX)にグラフを作成します

String statement =
      "CREATE PROPERTY GRAPH hr_simplified "
    + "  VERTEX TABLES ( "
    + "    hr.employees LABEL employee "
    + "      PROPERTIES ARE ALL COLUMNS EXCEPT ( job_id, manager_id, department_id ), "
    + "    hr.departments LABEL department "
    + "      PROPERTIES ( department_id, department_name ) "
    + "  ) "
    + "  EDGE TABLES ( "
    + "    hr.employees AS works_at "
    + "      SOURCE KEY ( employee_id ) REFERENCES employees (employee_id) "
    + "      DESTINATION departments "
    + "      PROPERTIES ( employee_id ) "
    + "  )";
session.executePgql(statement);

/**
 * To get a handle to the graph, execute:
 */
PgxGraph g = session.getGraph("HR_SIMPLIFIED");

/**
 * You can use this handle to run PGQL queries on this graph.
 * For example, to find the department that “Nandita Sarchand” works for, execute:
 */
String query =
    "SELECT dep.department_name "
  + "FROM MATCH (emp:Employee) -[:works_at]-> (dep:Department) "
  + "WHERE emp.first_name = 'Nandita' AND emp.last_name = 'Sarchand' "
  + "ORDER BY 1";
PgqlResultSet resultSet = g.queryPgql(query);
resultSet.print();
+-----------------+
| department_name |
+-----------------+
| Shipping        |
+-----------------+

/**
 * To get an overview of the types of vertices and their frequencies, execute:
 */
String query =
      "SELECT label(n), COUNT(*) "
    + "FROM MATCH (n) "
    + "GROUP BY label(n) "
    + "ORDER BY COUNT(*) DESC";
PgqlResultSet resultSet = g.queryPgql(query);
resultSet.print();

+-----------------------+
| label(n)   | COUNT(*) |
+-----------------------+
| EMPLOYEE   | 107      |
| DEPARTMENT | 27       |
+-----------------------+

/**
  *To get an overview of the types of edges and their frequencies, execute:
  */
 String query =
    "SELECT label(n) AS srcLbl, label(e) AS edgeLbl, label(m) AS dstLbl, COUNT(*) "
  + "FROM MATCH (n) -[e]-> (m) "
  + "GROUP BY srcLbl, edgeLbl, dstLbl "
  + "ORDER BY COUNT(*) DESC";
PgqlResultSet resultSet = g.queryPgql(query);
resultSet.print();

+---------------------------------------------+
| srcLbl   | edgeLbl  | dstLbl     | COUNT(*) |
+---------------------------------------------+
| EMPLOYEE | WORKS_AT | DEPARTMENT | 106      |
+---------------------------------------------+

17.2 オプションを使用したプロパティ・グラフの作成

グラフ最適化、および頂点が欠落しているエッジの処理の様々なオプションについて学習します。

CREATE PROPERTY GRAPH文でOPTIONS句を使用すると、次の各項で説明するオプションを指定できます。

グラフ最適化オプションの使用

問合せや分析、または更新操作を実行するためのグラフをロードできます。要件に応じて、CREATE PROPERTY GRAPH文のOPTIONS句を使用して、読取りまたは更新のパフォーマンスを最適化できます。

次の表に、OPTIONS句でサポートされる有効なオプションを示します。

表17-1 グラフ最適化オプション

オプション 説明
OPTIMIZED_FOR_READ これは、読取り集中型のシナリオに使用できます。
OPTIMIZED_FOR_UPDATES これはデフォルトのオプションで、高速更新に使用できます。
SYNCHRONIZABLE これにより、フラッシュバック・テクノロジを介してグラフを同期できるようになります。ただし、エッジ・キーのいずれかがコンポジット・キーであるか、または数値以外のキーである場合は、例外がスローされます。このような場合、通常はグラフのロードはできますが、PGXによって新しい(コンポジット以外および数値の)エッジ・キーが生成されます。そのため、このようなエッジをデータベースと同期することはできません。

たとえば、次のグラフは、OPTIMIZED_FOR_UPDATESおよびSYNCHRONIZABLEオプションを使用して設定されます。

CREATE PROPERTY GRAPH hr 
VERTEX TABLES ( 
employees LABEL employee, departments LABEL department 
) 
EDGE TABLES ( 
departments AS managed_by 
SOURCE KEY ( department_id ) REFERENCES departments (department_id)
DESTINATION employees 
NO PROPERTIES 
) OPTIONS (OPTIMIZED_FOR_UPDATES, SYNCHRONIZABLE)

ノート:

  • SYNCHRONIZABLEオプションは、OPTIMIZED_FOR_UPDATESおよびOPTIMIZED_FOR_READと組み合せて使用できます。ただし、OPTIMIZED_FOR_UPDATESOPTIMIZED_FOR_READは一緒には使用できず、一緒に使用すると例外がスローされます。
  • 同期可能なグラフを作成する場合は、頂点キーとエッジ・キーが数値であり、コンポジットではないことを確認します。

オプションを使用した頂点が欠落しているエッジの処理

エッジのソース頂点または宛先頂点のいずれか、あるいはその両方が欠落している場合は、CREATE PROPERTY GRAPH文のOPTIONS句で次のいずれかの値を構成できます。

  • IGNORE EDGE ON MISSING VERTEX: 欠落した頂点のエッジを無視する必要があることを指定します。
  • IGNORE EDGE AND LOG ON MISSING VERTEX: 欠落した頂点のエッジを無視し、無視されたすべてのエッジをログに記録する必要があることを指定します。
  • IGNORE EDGE AND LOG ONCE ON MISSING VERTEX: 欠落した頂点のエッジを無視し、最初の無視されたエッジのみをログに記録する必要があることを指定します。
  • ERROR ON MISSING VERTEX (デフォルト): 頂点が欠落しているエッジに対してエラーをスローする必要があることを指定します。

たとえば、次のグラフはERROR ON MISSING VERTEXオプションを使用して設定されます。

CREATE PROPERTY GRAPH region_graph 
VERTEX TABLES ( 
regions KEY (region_id), 
countries KEY (country_id)
) 
EDGE TABLES ( 
countries AS countries_regions 
SOURCE KEY ( country_id ) REFERENCES countries(country_id) 
DESTINATION KEY (region_id) REFERENCES regions(region_id) 
NO PROPERTIES 
) OPTIONS ( ERROR ON MISSING VERTEX)

実行すると、次のエラー・レスポンスが表示されます。

unknown vertex ID received in destination 4 of edge 5

IGNORE EDGE AND LOG ON MISSING VERTEXまたはIGNORE EDGE AND LOG ONCE ON MISSING VERTEXオプションを使用する場合は、/etc/oracle/graph/logback.xmlのデフォルトのLogback構成ファイルと、/etc/oracle/graph/logback-server.xmlのグラフ・サーバー(PGX)ロガー構成ファイルを更新して、DEBUGログを記録する必要があります。そのようにすることでのみ、無視されたエッジを/var/opt/log/pgx-server.logファイルで確認できます。

17.3 グラフ・サーバー(PGX)でサポートされているPGQL機能および制限事項

グラフ・サーバー(PGX)でサポート対象のPGQL機能、およびサポート対象外のPGQL機能について学習します。

表17-2 グラフ・サーバー(PGX)でサポートされているPGQL機能および制限事項

機能 グラフ・サーバー上のPGQL (PGX)
CREATE PROPERTY GRAPH サポート対象
制限事項:
  • 頂点のコンポジット・キーはありません
DROP PROPERTY GRAPH サポート対象外
固定長パターン一致 サポート対象
可変長パターン一致の目標 サポート対象:
  • 到達可能性
  • パス検索接頭辞:
    • ANY
    • ANY SHORTEST
    • SHORTEST k
    • ALL SHORTEST
    • ANY CHEAPEST
    • CHEAPEST k
    • ALL
  • パス・モード:
    • WALK
    • TRAIL
    • SIMPLE
    • ACYCLIC

制限事項:

  • パス・モードTRAILSIMPLE、およびACYCLICANY CHEAPESTおよびCHEAPEST kとの組合せではサポートされません
可変長パターン一致の数量詞 サポート対象:
  • *
  • +
  • ?
  • { n }
  • { n, }
  • { n, m }
  • { , m }

制限事項:

  • ?は到達可能性についてのみサポートされています
  • ANY CHEAPESTおよびTOP k CHEAPESTの場合は、*のみがサポートされています
可変長パスのネスト解除 サポート対象:
  • ONE ROW PER VERTEX
  • ONE ROW PER STEP

制限事項:

  • *数量詞はサポートされていません
GROUP BY サポート対象
HAVING サポート対象
集計 サポート対象:
  • COUNT
  • MINMAXAVGSUM
  • LISTAGG
  • ARRAY_AGG

制限事項:

  • ARRAY_AGGは、水平集計(可変長パスとの組合せ)としてのみサポートされており、垂直集計との組合せはサポートされていません
DISTINCT
  • SELECT DISTINCT
  • DISTINCTを使用した集計(COUNT(DISTINCT e.prop)など)
サポート対象
SELECT v.* サポート対象
ORDER BY (+ASC/DESC)、LIMIT、OFFSET サポート対象
データ型 サポート対象:
  • INTEGER (32ビット)
  • LONG (64ビット)
  • FLOAT (32ビット)
  • DOUBLE (64ビット)
  • STRING (最大長なし)
  • BOOLEAN
  • DATE
  • TIME
  • TIMEWITHTIMEZONE
  • TIMESTAMP
  • TIMESTAMPWITHTIMEZONE
JSON 組込みのJSONサポートはありません。ただし、JSON値はSTRINGとして格納でき、JavaまたはJavaScriptで記述されたユーザー定義関数(UDF)を使用して操作または問合せできます。
演算子 サポート対象:
  • 関係: +-*/%- (単項マイナス)
  • 算術: =<><><=>=
  • 論理: ANDORNOT
  • 文字列: || (concat)
関数および述語 サポート対象:
  • IS NULLIS NOT NULL
  • JAVA_REGEXP_LIKE (CONTAINSに基づいています)
  • LOWERUPPER
  • SUBSTRING
  • ABSCEIL/CEILINGFLOORROUND
  • EXTRACT
  • ID, VERTEX_ID, EDGE_ID
  • LABELLABELSIS [NOT] LABELED
  • ALL_DIFFERENT
  • IN_DEGREEOUT_DEGREE
  • CAST
  • CASE
  • INNOT IN
  • MATCHNUM
  • ELEMENT_NUMBER
  • IS [NOT] SOURCE [OF]IS [NOT] DESTINATION [OF]
  • VERTEX_EQUALEDGE_EQUAL
ユーザー定義関数 サポート対象:
  • Java UDF
  • JavaScript UDF
副問合せ:
  • スカラー副問合せ
  • EXISTSおよびNOT EXISTS副問合せ
  • LATERAL副問合せ
  • GRAPH_TABLE副問合せ
サポート対象

制限事項

  • LATERALまたはGRAPH_TABLE副問合せを含むFROM句には、他に何も含めることはできません
INSERT/UPDATE/DELETE サポート対象
INTERVALリテラルおよび操作

サポートされているリテラル:

  • SECOND
  • MINUTE
  • HOUR
  • DAY
  • MONTH
  • YEAR

サポートされている操作:

  • INTERVALを日時に追加します(+)
  • INTERVALを日時から減算します(-)

また、次では、特定のサポートされているPGQL機能とサポートされていないPGQL機能についても説明します:

17.3.1 すべてのプロパティの選択のサポート

SELECT v.*を使用して、変数vにバインドする頂点またはエッジのすべてのプロパティを選択できます。たとえば:

SELECT label(n), n.* FROM MATCH (n) ORDER BY "number", "name"

実行時に、問合せ出力は次のようになります。

+-----------------------------+
| label(n) | number | name    |
+-----------------------------+
| Account  | 1001   | <null>  |
| Account  | 2090   | <null>  |
| Account  | 8021   | <null>  |
| Account  | 10039  | <null>  |
| Person   | <null> | Camille |
| Person   | <null> | Liam    |
| Person   | <null> | Nikita  |
| Company  | <null> | Oracle  |
+-----------------------------+

ラベル式を使用して、指定した頂点ラベルまたはエッジ・ラベルに属するプロパティを選択できます。たとえば:

SELECT label(n), n.* FROM MATCH (n:Person) ORDER BY "name"

前述の問合せは、指定されたPersonラベルのすべてのプロパティを取得します。

+--------------------+
| label(n) | name    |
+--------------------+
| Person   | Camille |
| Person   | Liam    |
| Person   | Nikita  |
+--------------------+

複数の変数を使用してすべてのプロパティを選択した場合、列名が重複しないようにPREFIXを指定することもできます。たとえば:

SELECT n.* PREFIX 'n_', e.* PREFIX 'e_', m.* PREFIX 'm_'
FROM MATCH (n:Account) -[e:transaction]-> (m:Account)
ORDER BY "e_amount"

問合せの出力は次のようになります。

+--------------------------------+
| n_number | e_amount | m_number |
+--------------------------------+
| 10039    | 1000.0   | 8021     |
| 8021     | 1500.3   | 1001     |
| 8021     | 3000.7   | 1001     |
| 2090     | 9900.0   | 10039    |
| 1001     | 9999.5   | 2090     |
+--------------------------------+

17.3.2 可変長パス問合せのネスト解除

可変長パス問合せ(SHORTESTパスやCHEAPESTパスなど)のネストを解除して、パスに沿った頂点またはエッジごとに個別の行を取得できます。

次のオプションのいずれかを使用して、パス集計をネスト解除できます。
  • ONE ROW PER MATCH (デフォルト・オプション)
  • ONE ROW PER VERTEX(vertex_variable)
  • ONE ROW PER STEP(edge_source_variable,edge_variable,edge_destination_variable)

たとえば、次のPGQL問合せではONE ROW PER STEPオプションを使用します。

SELECT  v1.ACCT_ID AS src_no, k.TXN_AMOUNT, v2.ACCT_ID AS dest_no 
FROM MATCH ALL SHORTEST (a:Accounts) -[e:transfers]->+ (b:Accounts)
ONE ROW PER STEP( v1,k,v2 )
WHERE a.ACCT_ID = 284 AND b.ACCT_ID = 616

ONE ROW PER STEPオプションは、最小ホップが0より大きいパスのみをサポートするため、*修飾子はこのオプションでサポートされていないことに注意してください。

実行すると、前述の問合せでは、対応するソース頂点および宛先頂点によってバインドされているパス上のエッジごとに1行を取得します。

+-------------------------------+
| src_no | TXN_AMOUNT | dest_no |
+-------------------------------+
| 744    | 1000.0     | 616     |
| 772    | 1000.0     | 744     |
| 284    | 1000.0     | 772     |
| 744    | 1000.0     | 616     |
| 772    | 1500.0     | 744     |
| 284    | 1000.0     | 772     |
+-------------------------------+

グラフ・ビジュアライゼーション・ツールを使用して、パスに沿ってONE ROW PER STEPを使用してエッジを視覚化することもできます。

図17-1 可変長パス問合せのネスト解除の視覚化

図17-1の説明が続きます
「図17-1 可変長パス問合せのネスト解除の視覚化」の説明

ONE ROW PER VERTEXオプションを指定した問合せの例を次に示します。

SELECT k.acct_id AS id, k.acct_name AS name
FROM MATCH ANY SHORTEST (a:Accounts) ((src:Accounts)-[e:transfers]->){1,3}(b:Accounts)
ONE ROW PER VERTEX(k)
WHERE a.acct_id=284 AND b.acct_id=616

実行時には、前述の問合せはパスに沿って頂点ごとに1行を取得します。

+----------------+
| id  | name     |
+----------------+
| 616 | Account4 |
| 744 | Account3 |
| 772 | Account2 |
| 284 | Account1 |
+---------------+

再帰的なパスのネスト解除問合せ用の組込み関数のサポート

PGQLは、次の2つの組込み関数をサポートしており、これらはパスのネスト解除オプション(ONE ROW PER VERTEXONE ROW PER STEPまたはONE ROW PER MATCH)のいずれかと組み合せて使用できます。

  • MATCH_NUMBER(k): ネスト解除されたパスごとに一意のパス単位の識別子を戻します(つまり、2つの行の元が同じパスである場合、それらのMATCH_NUMBER(k)は同じです)。
  • ELEMENT_NUMBER(k): パスに沿って頂点またはエッジの要素番号を戻します。左端の頂点には1、2番目には3、その次には5のように、頂点には奇数の番号が付けられます。左端のエッジには2、次のエッジには4のように、エッジには偶数の番号が割り当てられます。

たとえば、次のPGQL問合せでは、ONE ROW PER VERTEXオプションを指定してMATCH_NUMBER(k)およびELEMENT_NUMBER(k)関数を使用します。

SELECT k.*, match_number(k), element_number(k)
FROM MATCH ANY SHORTEST (a:Accounts) -[e:transfers]->* (b:Accounts) ONE ROW PER VERTEX ( k )
WHERE a.acct_id = 284 AND b.acct_id = 616

前述の問合せを実行すると、次の出力が生成されます。頂点に対して戻されたelement_number(k)は奇数の値であることに注意してください。前述の問合せではANYパス・パターンを使用しているため、出力には任意のパスが1つのみ表示されます。このため、match_number(k)はパス内のすべての行で同じです。

+-----------------------------------------------------------+
| ACCT_ID | ACCT_NAME | match_number(k) | element_number(k) |
+-----------------------------------------------------------+
| 616     | Account   | 0               | 7                 |
| 744     | Account   | 0               | 5                 |
| 772     | Account   | 0               | 3                 |
| 284     | Account   | 0               | 1                 |
+-----------------------------------------------------------+

次の例に、ONE ROW PER STEPオプションを指定してMATCH_NUMBER(k)およびELEMENT_NUMBER(k)関数を使用したPGQL問合せを示します。

SELECT v1.acct_id AS src_no,k.txn_amount,v2.acct_id AS dest_no, match_number(k), element_number(k)
FROM MATCH ALL SHORTEST (a:Accounts) -[e:transfers]->+ (b:Accounts)
ONE ROW PER STEP( v1,k,v2 )
WHERE a.acct_id = 284 AND b.acct_id = 616

前述の問合せ出力は次のようになります。match_number(k)で識別されたパスが2つあり、エッジは偶数のelement_number(k)値とともに表示されています。

+---------------------------------------------------------------------+
| src_no | txn_amount | dest_no | match_number(k) | element_number(k) |
+---------------------------------------------------------------------+
| 744    | 1000.0     | 616     | 0               | 6                 |
| 772    | 1000.0     | 744     | 0               | 4                 |
| 284    | 1000.0     | 772     | 0               | 2                 |
| 744    | 1000.0     | 616     | 1               | 6                 |
| 772    | 1500.0     | 744     | 1               | 4                 |
| 284    | 1000.0     | 772     | 1               | 2                 |
+---------------------------------------------------------------------+

17.3.3 PGQL問合せでのINTERVALリテラルの使用

PGQL問合せでINTERVALリテラルを使用すると、PGQL Temporalデータ型に対する間隔の加算または減算をそれぞれ実行できます。

サポートされるTemporalデータ型については、PGQL 1.5 Specificationを参照してください。

INTERVAL型は期間で、キーワード"INTERVAL"とそれに続く数値および時間単位で構成されます。たとえば、INTERVAL '1' DAYとなります。

次の表に、INTERVAL値でサポートされる有効な時間単位を示します。

表17-3 INTERVAL値のフィールドの有効な値

キーワード サポートされる有効な値
YEAR <間隔先行フィールド精度>以外は制約なし
MONTH 月(年単位) (0-11)
DAY <間隔先行フィールド精度>以外は制約なし
HOUR 時間(日単位) (0-23)
MINUTE 分(時間単位) (0-59)
SECOND 秒(分単位) (0-59.999...)

次のINTERVAL操作がTemporalデータ型でサポートされています。

  • TEMPORAL TYPE + INTERVAL
  • INTERVAL + TEMPORAL TYPE
  • TEMPORAL TYPE - INTERVAL

たとえば、次のPGQL問合せでは、n.birthdate + INTERVAL '20' YEAR > TIMESTAMP '2000-01-01 00:00:00'である個人を取得します。

opg4j> graph.queryPgql("SELECT n.name, n.birthdate FROM MATCH (n:Person) WHERE n.birthdate + INTERVAL '20' YEAR > TIMESTAMP '2000-01-01 00:00:00'").print()
graph.queryPgql("SELECT n.name, n.birthdate FROM MATCH (n:Person) WHERE n.birthdate + INTERVAL '20' YEAR > TIMESTAMP '2000-01-01 00:00:00'").print();
graph.query_pgql("SELECT n.name, n.birthdate FROM MATCH (n:Person) WHERE n.birthdate + INTERVAL '20' YEAR > TIMESTAMP '2000-01-01 00:00:00'").print()

実行時に、問合せ出力は次のようになります。

+--------------------------+
| name  | birthdate        |
+--------------------------+
| Mary  | 1982-09-25T00:00 |
| Alice | 1987-02-01T00:00 |
+--------------------------+

17.3.4 PGQLでのパス・モードの使用

次のパス・モードは、ANYALLANY SHORTESTSHORTEST k、およびALL SHORTESTと組み合せて使用できます。

  • WALK (デフォルト・パス・モード):ウォークは、一連の頂点とエッジを介してグラフをトラバースします。ウォークでアクセスした頂点とエッジを繰り返すことができます。したがって、このデフォルトのパス・モードではパスのフィルタリングは行われません。
  • TRAIL:トレイルはエッジを繰り返すことなくグラフをトラバースしています。したがって、エッジを繰り返すパス・バインディングは返されません。
    SELECT CAST(a.number AS STRING) || ' -> ' || LISTAGG(x.number, ' -> ') AS accounts_along_path
    FROM MATCH ALL TRAIL PATHS (a IS account) (-[IS transaction]-> (x)){2,} (b IS Account)
    WHERE a.number = 8021 AND b.number = 1001
    
    +-----------------------------------------------+
    | accounts_along_path                           |
    +-----------------------------------------------+
    | 8021 -> 1001 -> 2090 -> 10039 -> 8021 -> 1001 |
    | 8021 -> 1001 -> 2090 -> 10039 -> 8021 -> 1001 |
    +-----------------------------------------------+
    前述の出力では、両方のパスに頂点8021と1001が2回含まれていますが、エッジが繰り返されないかぎり、まだトレイルは有効です。
  • ACYCLIC:グラフ・トラバースの開始頂点と終了頂点が異なる場合、これはパスにサイクルがないことを意味します。この場合、頂点が繰り返されるパス・バインディングは返されません。
    SELECT CAST(a.number AS STRING) || ' -> ' || LISTAGG(x.number, ' -> ') AS accounts_along_path
    FROM MATCH SHORTEST 10 ACYCLIC PATHS (a IS account) (-[IS transaction]-> (x))+ (b)
    WHERE a.number = 10039 AND b.number = 1001
    
    +-----------------------+
    | accounts_along_path   |
    +-----------------------+
    | 10039 -> 8021 -> 1001 |
    | 10039 -> 8021 -> 1001 |
    +-----------------------+
    前述の問合せで、10の最短パスがリクエストされました。ただし、他のすべてのパスが循環するため、2つのみが返されます。
  • SIMPLE: 単純なウォークは、頂点を繰り返さずにグラフをトラバースします。したがって、頂点を繰り返すパス・バインディングは返されません。唯一の例外は、繰り返される頂点がパスの最初の頂点と最後の頂点であることです。
    SELECT CAST(a.number AS STRING) || ' -> ' || LISTAGG(x.number, ' -> ') AS accounts_along_path
    FROM MATCH ANY SIMPLE PATH (a IS account) (-[IS transaction]-> (x))+ (a)
    WHERE a.number = 10039
    
    +----------------------------------------+
    | accounts_along_path                    |
    +----------------------------------------+
    | 10039 -> 8021 -> 1001 -> 2090 -> 10039 |
    +----------------------------------------+
    前述の問合せは循環パスを返します。このパスは、同じ頂点で開始および終了し、パスに他のサイクルがないため、有効かつ単純なパスです。

パス・モードは、構文的にはANYALLANY SHORTESTSHORTEST kおよびALL SHORTESTの後に配置されます。パス・モードの後には、PATHまたはPATHSキーワードが続くこともあります。

すべての無限量数量詞に対してTRAILACYCLICまたはSIMPLEの一致パス・モードを使用すると、グラフ・パターン一致の結果セットが有限であることが保証されます。

17.3.5 PGQL Lateral副問合せのサポート

LATERAL副問合せを使用して、ある問合せの出力行を別の問合せに渡すことができます。サポートされているのは単一のLATERAL副問合せのみです。

たとえば、別のORDER BY句またはGROUP BY句の上にORDER BY句またはGROUP BY句を使用できます。

/* Find the top-5 largest transactions and return the account number
   that received the highest number of such large transactions */
SELECT recipient, COUNT(*) AS num_large_transactions
FROM LATERAL ( SELECT m.number AS recipient
               FROM MATCH (n:account) -[e:transaction]-> (m:account)
               ORDER BY e.amount DESC
               LIMIT 5 )
GROUP BY recipient
ORDER BY num_large_transactions DESC
LIMIT 1

17.3.6 PGQL GRAPH_TABLE副問合せのサポート

PGQLのGRAPH_TABLE副問合せによって、グラフ・サーバー(PGX)にロードされるグラフとデータベース上のグラフの間の相互運用性が向上します。

ただし、SQL標準に準拠するには、PGQL問合せ構文が次のように整理されている必要があります。
  • グラフ・パターンMATCH問合せのラベル述語では、ISキーワードを使用する必要があります。
  • 出力行数を制限するには、LIMIT x句のかわりにFETCH [FIRST/NEXT] x [ROW/ROWS]句を使用します。
  • エッジの方向を確認するには、[NOT] is_source_of(e, v) / [NOT] is_destination_of(e, v)のかわりにv IS [NOT] SOURCE [OF] e/v IS [NOT] DESTINATION [OF] eを標準フォームとして使用します。
  • 頂点またはエッジに指定したラベルがあるかどうかを確認するには、has_label(x, <label_string>)の代替としてx IS [NOT] LABELED <label_string>述語を使用します。
  • kの最短パスと一致させるには、MATCH TOP k SHORTEST (n) –[e]->* (m)の標準形式としてMATCH SHORTEST k (n) –[e]->* (m)を使用します。
  • 固定長パス・パターンの前にはALLキーワード(オプション)があります。

    MATCH ALL (n) –[e]->{1,4} (m)の代替としてのMATCH (n) –[e]->{1,4} (m)

たとえば:

SELECT *
FROM GRAPH_TABLE ( financial_transactions
       MATCH ALL TRAIL (a IS account) -[e IS transaction]->* (b IS account)
       /* optional ONE ROW PER VERTEX/STEP clause here */
       WHERE a.number = 8021 AND b.number = 1001
       COLUMNS ( LISTAGG(e.amount, ', ') AS amounts )
     )ORDER BY amounts
この問合せの出力は次のようになります。
+----------------------------------------+
| amounts                                |
+----------------------------------------+
| 1500.3                                 |
| 1500.3, 9999.5, 9900.0, 1000.0, 3000.7 |
| 3000.7                                 |
| 3000.7, 9999.5, 9900.0, 1000.0, 1500.3 |
+----------------------------------------+

17.3.7 数量詞の制限

到達可能性パターンと最短パス・パターンでは、*+{1,4}などのすべての数量詞がサポートされていますが、最小コスト・パス・パターンでサポートされている数量詞は* (ゼロ以上)のみです。

17.3.8 定量化されたパターンでのWHERE句およびCOST句の制限事項

到達可能性パターンや最短パス・パターンおよび最小コスト・パス・パターンなどの定量化されたパターンのWHERE句およびCOST句は、単一の変数の参照のみに制限されます。

次に、WHERE句またはCOST句が、zeroまたはoneのかわりに2つの変数eおよびxを参照しているためにサポートされていない問合せの例を示します。

... PATH p AS (n) –[e]-> (m) WHERE e.prop > m.prop ...
... SHORTEST ( (n) (-[e]-> (x) WHERE e.prop + x.prop > 10)* (m) ) ...
... CHEAPEST ( (n) (-[e]-> (x) COST e.prop + x.prop )* (m) ) ...

次の問合せがサポートされているのは、副問合せは外部スコープから単一の変数aのみを参照し、変数cは副問合せで新たに導入されたためにカウントされないためです。

... PATH p AS (a) -> (b)
      WHERE EXISTS ( SELECT * FROM MATCH (a) -> (c) ) ...

17.4 CREATE PROPERTY GRAPH文を実行するためのJava API

CREATE PROPERTY GRAPH文を実行する最も簡単な方法は、PgxSession.executePgql(String statement)メソッドを使用することです。

例17-2 CREATE PROPERTY GRAPH文の実行

String statement =
      "CREATE PROPERTY GRAPH hr_simplified "
    + "  VERTEX TABLES ( "
    + "    hr.employees LABEL employee "
    + "      PROPERTIES ARE ALL COLUMNS EXCEPT ( job_id, manager_id, department_id ), "
    + "    hr.departments LABEL department "
    + "      PROPERTIES ( department_id, department_name ) "
    + "  ) "
    + "  EDGE TABLES ( "
    + "    hr.employees AS works_at "
    + "      SOURCE KEY ( employee_id ) REFERENCES employees (employee_id) "
    + "      DESTINATION departments "
    + "      PROPERTIES ( employee_id ) "
    + "  )";
session.executePgql(statement);
PgxGraph g = session.getGraph("HR_SIMPLIFIED");

/**
 * Alternatively, one can use the prepared statement API, for example:
 */

PgxPreparedStatement stmnt = session.preparePgql(statement);
stmnt.execute();
stmnt.close();
PgxGraph g = session.getGraph("HR_SIMPLIFIED");

17.5 CREATE PROPERTY GRAPH文を実行するためのPython API

Python APIを介してCREATE PROPERTY GRAPH文を実行してプロパティ・グラフを作成できます。

Pythonクライアントを使用したプロパティ・グラフの作成

  • Pythonクライアントを起動します。
    ./bin/opg4py --base_url https://localhost:7007 --user customer_360
  • 次のようにCREATE PROPERTY GRAPH文を定義して実行します。
    statement = (
           "CREATE PROPERTY GRAPH "+ "<graph_name>" + " " +
           "VERTEX TABLES ( " +
           "bank_accounts " +
           "KEY(acct_id) " +
           "LABEL Account PROPERTIES (acct_id) " +
           ")" +
           "EDGE TABLES ( " +
           "bank_txns " +
           "KEY (txn_id) " +
           "SOURCE KEY (from_acct_id) REFERENCES bank_accounts (acct_id) " +
           "DESTINATION KEY (to_acct_id) REFERENCES bank_accounts (acct_id) " +
           "LABEL Transfer PROPERTIES(amount) " +
           ")")
    >>> session.prepare_pgql(statement).execute()

    <graph_name>は、グラフの名前です。

    グラフが作成され、get_graphメソッドで確認できます。
    >>> graph = session.get_graph("<graph_name>")
    >>> graph
    PgxGraph(name:<graph_variable>, v: 1000, e: 5001, directed: True, memory(Mb): 0)

17.6 SELECT問合せを実行するためのJava API

このセクションでは、グラフ・サーバー(PGX)でSELECT問合せを実行するためのAPIについて説明します。

17.6.1 グラフ・サーバー(PGX)のグラフに対するSELECT問合せの実行

PgxGraph.queryPgql(String query)メソッドは、現在のセッションで問合せを実行します。このメソッドは、PgqlResultSetを返します。

問合せはPGXグラフに対して直接実行されるため、MATCH句内のON句は省略できます。同じ理由で、INSERT句内のINTO句は省略できます。ただし、ON句およびINTO句でグラフ名を明示的に指定する場合、これらのグラフ名がグラフの実際の名前(PgxGraph.getName())と一致している必要があります。

17.6.2 PGXセッションに対するSELECT問合せの実行

PgxSession.queryPgql(String query)メソッドは、セッションで指定された問合せを実行し、PgqlResultSetを返します。

MATCH句内のON句、およびINSERT句内のINTO句は、指定する必要があり、省略できません。この時点では、単一の問合せで複数のグラフのデータを結合することはまだサポートされていないため、問合せのすべてのON句およびINTO句で同じグラフを参照する必要があります。

17.6.3 結果セットの反復処理

結果セットを反復処理するには、JDBCに似た方法またはJava Iteratorインタフェースを使用する方法があります。

JDBCのような反復の場合、PgqlResultSet (パッケージoracle.pgx.api)のメソッドはjava.sql.ResultSetのメソッドと似ています。主な違いは、PGQLの結果セット・インタフェースはJava 8で導入された新しい日時ライブラリに基づいているのに対し、java.sql.ResultSetはレガシーjava.util.Dateに基づいていることです。ギャップを埋めるために、PGQLの結果セットは、java.util.Dateをまだ使用しているアプリケーションにgetLegacyDate(..)を提供します。

PgqlResultSetには、最初の行の前に初期設定されるcursorがあります。その後、次のメソッドを使用してカーソルを再配置できます。
  • next() : boolean
  • previous() : boolean
  • beforeFirst()
  • afterLast()
  • first() : boolean
  • last() : boolean
  • absolute(long row) : boolean
  • relative(long rows) : boolean
カーソルが目的の行に置かれると、次のgetterを使用して値が取得されます。
  • getObject(int columnIdx) : Object
  • getObject(String columnName) : Object
  • getString(int columnIdx) : String
  • getString(String columnName) : String
  • getInteger(int columnIdx) : Integer
  • getInteger(String columnName) : Integer
  • getLong(int columnIdx) : Long
  • getLong(String columnName) : Long
  • getFloat(int columnIdx) : Float
  • getFloat(String columnName) : Float
  • getDouble(int columnIdx) : Double
  • getDouble(String columnName) : Double
  • getBoolean(int columnIdx) : Boolean
  • getBoolean(String columnName) : Boolean
  • getVertexLabels(int columnIdx) : Set<String>
  • getVertexLabels(String columnName) : Set<String>
  • getDate(int columnIdx) : LocalDate
  • getDate(String columnName) : LocalDate
  • getTime(int columnIdx) : LocalTime
  • getTime(String columnName) : LocalTime
  • getTimestamp(int columnIdx) : LocalDateTime
  • getTimestamp(String columnName) : LocalDateTime
  • getTimeWithTimezone(int columnIdx) : OffsetTime
  • getTimeWithTimezone(String columnName) : OffsetTime
  • getTimestampWithTimezone(int columnIdx) : OffsetDateTime
  • getTimestampWithTimezone(String columnName) : OffsetDateTime
  • getLegacyDate(int columnIdx) : java.util.Date
  • getLegacyDate(String columnName) : java.util.Date
  • getVertex(int columnIdx) : PgxVertex<ID>
  • getVertex(String columnName) : PgxVertex<ID>
  • getEdge(int columnIdx) : PgxEdge
  • getEdge(String columnName) : PgxEdge

詳細は、Java ドキュメントを参照してください。

最後に、結果セットのリソースを解放するPgqlResultSet.close()があり、PgqlResultSet.getMetaData()を使用して列名と列数を取得できます。

結果セットの反復の例は次のとおりです。

PgqlResultSet resultSet = g.queryPgql(
    "   SELECT owner.name AS account_holder, SUM(t.amount) AS total_transacted_with_Nikita "
  + "     FROM MATCH (p:Person) -[:ownerOf]-> (account1:Account) " 
  + "        , MATCH (account1) -[t:transaction]- (account2) "
  + "        , MATCH (account2:Account) <-[:ownerOf]- (owner:Person|Company) "
  + "    WHERE p.name = 'Nikita' "
  + " GROUP BY owner");

while (resultSet.next()) {
  String accountHolder = resultSet.getString(1);
  long totalTransacted = resultSet.getLong(2);
  System.out.println(accountHolder + ": " + totalTransacted);
}

resultSet.close();

前述の例の出力は次のようになります。

Oracle: 4501
Camille: 1000

さらに、PgqlResultSetもJava Iteratorインタフェースを介して反復可能です。結果セットに対する"for each loop"の例は、次のとおりです。

for (PgxResult result : resultSet) {
  String accountHolder = result.getString(1);
  long totalTransacted = result.getLong(2);
  System.out.println(accountHolder + ": " + totalTransacted);
}

前述の例の出力は次のようになります。

Oracle: 4501
Camille: 1000

PgqlResultSetで使用可能なものと同じgetterをPgxResultでも使用できることに注意してください。

17.6.4 結果セットの出力

PgqlResultSet (パッケージoracle.pgx.api)の次のメソッドは、結果セットを出力するために使用されます。

  • print() : PgqlResultSet
  • print(long numResults) : PgqlResultSet
  • print(long numResults, int from) : PgqlResultSet
  • print(PrintStream printStream, long numResults, int from) : PgqlResultSet

たとえば:

g.queryPgql("SELECT COUNT(*) AS numPersons FROM MATCH (n:Person)").print().close()
+------------+
| numPersons |
+------------+
| 3          |
+------------+

もう1つ例を示します:

PgqlResultSet resultSet = g.queryPgql(
    "   SELECT owner.name AS account_holder, SUM(t.amount) AS total_transacted_with_Nikita "
  + "     FROM MATCH (p:Person) -[:ownerOf]-> (account1:Account) " 
  + "        , MATCH (account1) -[t:transaction]- (account2) "
  + "        , MATCH (account2:Account) <-[:ownerOf]- (owner:Person|Company) "
  + "    WHERE p.name = 'Nikita' "
  + " GROUP BY owner")

resultSet.print().close()
+-----------------------------------------------+
| account_holder | total_transacted_with_Nikita |
+-----------------------------------------------+
| Camille        | 1000.0                       |
| Oracle         | 4501.0                       |
+-----------------------------------------------+

17.7 UPDATE問合せを実行するためのJava API

UPDATE問合せは、PGQL 1.3仕様のグラフの変更のセクションで説明されているように、INSERTUPDATEおよびDELETE操作を使用して既存のグラフに変更を加えます。

INSERTを使用するとグラフに新しい頂点およびエッジを挿入でき、UPDATEを使用するとプロパティを新しい値に設定して既存の頂点およびエッジを更新でき、DELETEを使用するとグラフから頂点およびエッジを削除できます。

17.7.1 PGQLによるグラフの更新可能性

Oracle RDBMSまたはCSVファイルからPGXにロードされたグラフ・データは、PGQLを介してすぐに更新することはできません。

最初に、PgxGraph.clone()メソッドを使用してデータのコピーを作成する必要があります。結果のグラフは完全に更新可能です。

次のケースについて検討します。

// load a graph from the RDBMS or from CSV
PgxGraph g1 = session.readGraphWithProperties("path/to/graph_config.json");

// create an updatable copy of the graph
PgxGraph g2 = g1.clone("new_graph_name");

// insert an additional vertex into the graph
g2.executePgql("INSERT VERTEX v " +
               "         LABELS ( Person ) " +
               "         PROPERTIES ( v.firstName = 'Camille', " +
               "                      v.lastName = ' Mullins')"); 

さらに、前述の例の最後の2つのステップを組み合せて単一のステップにするPgxGraph.cloneAndExecutePgql(String query, String graphName)メソッドもあります。

// create an updatable copy of the graph while inserting a new vertex
PgxGraph g2_copy = g1.cloneAndExecutePgql(
                     "INSERT VERTEX v " +
                     "         LABELS ( Person ) " +
                     "         PROPERTIES ( v.firstName = 'Camille', " +
                     "                      v.lastName = ' Mullins') "
                   , "new_graph_name");

PgxGraph.clone()を介して作成されるグラフは、セッションに対してローカルであることに注意してください。ただし、PgxGraph.publish(..)メソッドを介して他のセッションと共有することはできますが、PGQLを使用して更新することはできなくなりました。セッションローカル・グラフのみが更新可能ですが、永続グラフは更新できません。

17.7.2 グラフ・サーバー(PGX)のグラフに対するUPDATE問合せの実行

グラフに対してUPDATE問合せを実行するには、PgxGraph.executePgql(String query)メソッドを使用します。

次に、INSERT問合せの例を示します。

g.executePgql("INSERT VERTEX v " +
              "         LABELS ( Person ) " +
              "         PROPERTIES ( v.firstName = 'Camille', " +
              "                      v.lastName = ' Mullins' ) "); 

INSERTINTO句は省略できることに注意してください。INTO句を使用する場合、INTO句のグラフ名は、問合せが実行されるPGXグラフ(PgxGraph.getName())の名前に対応している必要があります。

次に、UPDATE問合せの例を示します。

// set the date of birth of Camille to 2014-11-15
g.executePgql("UPDATE v SET ( v.dob = DATE '2014-11-14' ) " +
              "FROM MATCH (v:Person) " +
              "WHERE v.firstName = 'Camille' AND v.lastName = ' Mullins' "); 

次に、DELETE問合せの例を示します。

// delete Camille from the graph
g.executePgql("DELETE v " +
              "FROM MATCH (v:Person) " +
              "WHERE v.firstName = 'Camille' AND v.lastName = 'Mullins' "); 

17.7.3 PGXセッションに対するUPDATE問合せの実行

現時点では、PgxSessionに対するUPDATE問合せの実行はサポートされていないため、更新は常にPgxGraphに対して実行する必要があります。セッションからグラフを取得するには、PgxSession.getGraph(String graphName)メソッドを使用します。

17.7.4 グラフの基礎となるスキーマの変更

INSERT操作では、既知のラベルとプロパティを持つ頂点とエッジのみを挿入できます。同様に、UPDATE操作では、既知のプロパティの値のみを設定できます。したがって、新しいデータは常にグラフの既存のスキーマに準拠する必要があります。

ただし、グラフのスキーマを更新するためのPGX APIはいくつか存在します。新しいラベルを追加するためのAPIは存在しませんが、PgxGraph.createVertexProperty(PropertyType type, String name)およびPgxGraph.createEdgeProperty(PropertyType type, String name)メソッドを介して新しいプロパティを追加できます。新しいプロパティは、ラベルに関係なく、グラフ内の各頂点/エッジにアタッチされます。最初はプロパティにデフォルト値が割り当てられますが、その後UPDATE文を介して値を更新できます。

次のケースについて検討します。

// load a graph from the RDBMS or from CSV
PgxGraph g = session.readGraphWithProperties("path/to/graph_config.json");

// add a new property to the graph
g.createVertexProperty(PropertyType.LOCAL_DATE, "dob");

// set the date of birth of Camille to 2014-11-15
g.executePgql("UPDATE v SET ( v.dob = DATE '2014-11-14' ) " +
              "FROM MATCH (v:Person) " +
              "WHERE v.firstName = 'Camille' AND v.lastName = ' Mullins' ");

17.8 パーティション化されたIDを含むPGQL問合せ

PGQLのid()関数を使用して、パーティション化されたIDを取得できます。

PGQLのSELECT問合せ

次に、パーティション化されたIDをPGQLのSELECT問合せを使用して取得する例をいくつか示します。

g.queryPgql("SELECT id(n) FROM MATCH(n)").print().close()

次のように出力されます。

+-------------+
| id(n)       |
+-------------+
| Accounts(2) |
| Accounts(4) |
| Accounts(6) |
+-------------+
g.queryPgql("SELECT n.name FROM MATCH(n) WHERE id(n) = 'Accounts(1)'").print().close()

次のように出力されます。

+-------+
| name  |
+-------+
| User1 |
+-------+
g.queryPgql("SELECT LABEL(n), n.name from MATCH(n) WHERE n.id = 1").print().close()

次のように出力されます。

+------------------+
| label(n) | name  |
+------------------+
| Accounts | User1 |
+------------------+

PGXでは、一定時間で頂点を取得することでWHERE id(n) = 'Accounts(1)'WHERE n.id = 1などの述語を含む問合せを効率的に処理できるように、キーの一意索引が自動的に作成されます。

バインド変数の使用

パーティション化されたIDは、バインド値としてPgxPreparedStatementに渡すこともできます。

たとえば:

PgxPreparedStatement statement = g.preparePgql("SELECT n.name FROM MATCH (n) WHERE id(n)= ?")
statement.setString(1, "Accounts(1)")
statement.executeQuery().print().close()

次のように出力されます。

+-------+
| name  |
+-------+
| User1 |
+-------+

PGQLのINSERT問合せ

INSERT問合せでは、キー・プロパティが存在する場合はキー・プロパティの値を指定する必要があります。この値は、その後、頂点キーまたはエッジ・キーに使用されます。

たとえば、次のようにINSERTを実行できます。

g.executePgql("INSERT VERTEX v LABELS (Accounts) PROPERTIES (v.id = 1001, v.name = 'User1001')")

挿入された値は、次のように確認できます。

g.queryPgql("SELECT id(n), n.name FROM MATCH(n) WHERE n.id = 1001").print().close()

次のように出力されます。

+---------------------------+
| id(n)          | name     |
+---------------------------+
| Accounts(1001) | User1001 |
+---------------------------+

17.9 PGQL問合せを実行するためのセキュリティ・ツール

問合せインジェクションから保護するために、リテラルのかわりにバインド変数を使用し、グラフ名、ラベル、プロパティ名などの識別子のかわりにprintIdentifier(String identifier)を使用できます。

17.9.1 バインド変数の使用

バインド変数を使用する理由は2つあります。

  • 問合せインジェクションから保護します。
  • 問合せの再コンパイルを必要とせずに同じバインド変数を複数回設定できるため、問合せの実行が高速化されます。

プリペアド文を作成するには、次の2つの方法のいずれかを使用します。

  • PgxGraph.preparePgql(String query) : PgxPreparedStatement
  • PgxSession.preparePgql(String query) : PgxPreparedStatement

これらのメソッドから返されるPgxPreparedStatement (package oracle.pgx.api)には、バインド変数を指定されたデータ型の値にバインドするためのsetterメソッドがあります。

次のケースについて検討します。
PreparedStatement stmnt = g.preparePgql(
  "SELECT v.id, v.dob " +
  "FROM MATCH (v) " +
  "WHERE v.firstName = ? AND v.lastName = ?");
stmnt.setString(1, "Camille");
stmnt.setString(2, "Mullins");
ResultSet rs = stmnt.executeQuery();

問合せ内の各バインド変数は、PgxPreparedStatementの次のセッターのいずれかを使用して値に設定する必要があります。

  • setBoolean(int parameterIndex, boolean x)
  • setDouble(int parameterIndex, double x)
  • setFloat(int parameterIndex, float x)
  • setInt(int parameterIndex, int x)
  • setLong(int parameterIndex, long x)
  • setDate(int parameterIndex, LocalDate x)
  • setTime(int parameterIndex, LocalTime x)
  • setTimestamp(int parameterIndex, LocalDateTime x)
  • setTimeWithTimezone(int parameterIndex, OffsetTime x)
  • setTimestampWithTimezone(int parameterIndex, OffsetDateTime x)
  • setArray(int parameterIndex, List<?> x)

すべてのバインド変数が設定されると、文は次の方法で実行できます。

  • PgxPreparedStatement.executeQuery()
    • SELECT問合せの場合のみ
    • ResultSetを返します
  • PgxPreparedStatement.execute()
    • あらゆるタイプの文の場合
    • 結果の形式を示すブール値を返します。SELECT問合せの場合はtrue、それ以外の場合はfalseです。
    • SELECTの場合、ResultSetには後でPgxPreparedStatement.getResultSet()を使用してアクセスできます。

PGQLでは、配列リテラルを含む任意のデータ型のリテラルのかわりにバインド変数を使用できます。文字列配列のインスタンスにバインド変数が設定されている問合せの例は次のとおりです。

List<String> countryNames = new ArrayList<String>();
countryNames.add("Scotland");
countryNames.add("Tanzania");
countryNames.add("Serbia");

PreparedStatement stmnt = g.preparePgql(
  "SELECT n.name, n.population " +
  "FROM MATCH (c:Country) " +
  "WHERE c.name IN ?");

ResultSet rs = stmnt.executeQuery();

最後に、プリペアド文が不要になった場合は、PgxPreparedStatement.close()を介してクローズされ、リソースが解放されます。

17.9.2 安全な方法での識別子の使用

文字列の連結を介して問合せを作成すると、問合せのリテラルのみでなく、グラフ名、ラベル、プロパティ名などの識別子でもセキュリティ上のリスクが生じます。唯一の問題は、バインド変数がそのような識別子でサポートされていないことです。したがって、これらの識別子がアプリケーションの観点から可変である場合は、oracle.pgql.lang.ir.PgqlUtils.printIdentifier(String identifier)メソッドを介して識別子を渡すことにより、問合せインジェクションから保護することをお薦めします。

識別子文字列を指定すると、メソッドは識別子の先頭と末尾に二重引用符を自動的に追加し、識別子内の文字を適切にエスケープします。

次のケースについて検討します。

String graphNamePrinted = printIdentifier("my graph name with \" special % characters ");
PreparedStatement stmnt = g.preparePgql(
  "SELECT COUNT(*) AS numVertices FROM MATCH (v) ON " + graphNamePrinted);

17.10 PGQL問合せをチューニングするためのベスト・プラクティス

このセクションでは、メモリー割当て、並列度および問合せ計画に関するベスト・プラクティスについて説明します。

17.10.1 メモリー割当て

グラフ・サーバー(PGX)にはon-heapおよびoff-heapメモリーがあり、前者は標準JVMヒープですが、後者はPGXによって管理される別のヒープです。グラフ・データと同様に、PGQL問合せの中間結果および最終結果は、一部がオンヒープおよび一部がオフヒープで格納されます。したがって、両方のヒープが必要です。

オンヒープ・メモリーの場合、JVMの起動時にデフォルトの最大値が選択されますが、-Xmxオプションを介して上書きできます。

オフヒープの場合、デフォルトでは最大値は設定されず、オフヒープのメモリー使用量はシステム・リソースを使い果たすまで自動的に増加し続けます。システム・リソースがなくなると、操作が取り消され、メモリーが解放され、適切な例外がユーザーに渡されます。必要に応じて、グラフ・サーバー(PGX)のmax_off_heap_sizeオプションを介して最大オフヒープ・サイズを構成できます。

可能なかぎり最大のグラフをロードして問合せを実行できるようにするための適切な開始点として、オンヒープ・とオフヒープの比率を1対1にすることをお薦めします。オンヒープ・メモリー・サイズを構成するステップは、「オンヒープ制限の構成」を参照してください。

17.10.2 パラレル化

デフォルトでは、使用可能なすべてのプロセッサ・スレッドがPGQL問合せの処理に使用されます。ただし、必要に応じてグラフ・サーバー(PGX)のparallelismオプションを設定することにより、スレッドの数を制限できます。

グラフ・サーバーの構成パラメータの詳細は、グラフ・サーバー(PGX)エンジンの構成パラメータを参照してください。

17.10.3 問合せ計画の説明

PgxGraph.explainPgql(String query)メソッドは、問合せの問合せ計画を把握するために使用されます。このメソッドは、次のメソッドを持つOperation (package oracle.pgx.api)のインスタンスを返します。

  • print(): 操作とその子操作を出力するため
  • getOperationType(): 操作のタイプを取得するため
  • getPatternInfo(): 操作の文字列表現を取得するため
  • getCostEstimate(): 操作のコストを取得するため
  • getTotalCostEstimate(): 操作とその子操作のコストを取得するため
  • getCardinatlityEstimate(): 予想される結果行数を取得するため
  • getChildren(): 子操作にアクセスするため

次のケースについて検討します。

g.explainPgql("SELECT COUNT(*) FROM MATCH (n) -[e1]-> (m) -[e2]-> (o)").print()
\--- GROUP BY  GroupBy {"cardinality":"42", "cost":"42", "accumulatedCost":"58.1"}
     \--- (m) -[e2]-> (o) NeighborMatch {"cardinality":"3.12", "cost":"3.12", "accumulatedCost":"16.1"}
          \--- (n) -[e1]-> (m) NeighborMatch {"cardinality":"5", "cost":"5", "accumulatedCost":"13"}
               \--- (n) RootVertexMatch {"cardinality":"8", "cost":"8", "accumulatedCost":"8"}

前述の例では、print()メソッドを使用して問合せ計画を出力しています。

問合せ計画が最適でない場合、パフォーマンスを向上させるために問合せを書き直せることがよくあります。たとえば、合計実行時間を改善する方法として、SELECT問合せをUPDATEおよびSELECT問合せに分割できます。

グラフ・サーバー(PGX)はヒント・メカニズムを提供していないことに注意してください。

また、問合せ計画を出力すると、問合せで使用されているフィルタが表示されます。たとえば:

g.explainPgql("SELECT id(n) FROM MATCH (n)-[e]->(m) WHERE " +
...> "id(n) > 500 " +
...> "AND id(n) < 510 " +
...> "AND id(n) <> 509 " +
...> "AND id(n) <> 507 ").print()
\--- Projection {"cardinality":"146", "cost":"0", "accumulatedCost":"175"}
     \--- (n) -[e]-> (m) NeighborMatch {"cardinality":"146", "cost":"146", "accumulatedCost":"175"}
          \--- (n) RootVertexMatch {"cardinality":"29.2", "cost":"29.2", "accumulatedCost":"29.2"}
                WHERE $filter1
filter1: (id(n) <> 509) AND
         (id(n) <> 507) AND
         (id(n) > 500) AND
         (id(n) < 510)

前述の例では、問合せに3行を超えるフィルタがあるため、フィルタは問合せ計画の下に表示されます。フィルタが3行以下の場合、次のようにフィルタは問合せ計画ツリー内に直接表示されます。

g.explainPgql("SELECT id(n) FROM MATCH (n)-[e]->(m) WHERE " +
...> "id(n) > 500 " +
...> "AND id(n) < 510 ").print()
\--- Projection {"cardinality":"162", "cost":"0", "accumulatedCost":"194"}
     \--- (n) -[e]-> (m) NeighborMatch {"cardinality":"162", "cost":"162", "accumulatedCost":"194"}
          \--- (n) RootVertexMatch {"cardinality":"32.4", "cost":"32.4", "accumulatedCost":"32.4"}
                WHERE (id(n) > 500) AND
                      (id(n) < 510)