3.2 JSONの使用による頂点プロパティおよびエッジ・プロパティの格納

頂点プロパティおよびエッジ・プロパティを単一のJSON値としてエンコードすることで、プロパティ・グラフ・ビューで柔軟なスキーマ・アプローチを採用できます。その後、これをプロパティ・グラフ・ビューでプロパティ値にマップできます。

プロパティ・グラフ・スキーマ・グラフでは、必然的にスキーマの柔軟性が提供されます。明示的なスキーマ更新(たとえば、ALTER TABLE文によるもの)を発行する必要なく、任意のラベルおよびプロパティをグラフに追加できます。一方、プロパティ・グラフ・ビューでは、もともとそのような柔軟性は提供されておらず、新しいラベルを追加するには新しい頂点またはエッジ表を追加する必要があり、新しいプロパティを追加するには新しい列を追加する必要があります(両方ともスキーマ更新操作です)。ただし、JSONを使用することで、プロパティ・グラフ・ビューの上にスキーマの柔軟性をモデル化できます。

たとえば、2つのAccount頂点の間の財務トランザクションを表す、次のグラフを考えてみます。Accountは、PersonまたはCompanyのどちらかが所有できます。

図3-1 財務トランザクションのグラフ

図3-1の説明が続きます
「図3-1 財務トランザクションのグラフ」の説明

次に示すように、すべての頂点を格納するための単一の表と、すべてのエッジを格納するための別の単一の表を作成できます。

CREATE TABLE fin_vertex_table (
  id NUMBER PRIMARY KEY,
  properties VARCHAR2(2000)
);
 
INSERT INTO fin_vertex_table VALUES ( 1, '{"type":"Person","name":"Nikita"}');
INSERT INTO fin_vertex_table VALUES ( 2, '{"type":"Person","name":"Camille"}');
INSERT INTO fin_vertex_table VALUES ( 3, '{"type":"Person","name":"Liam"}');
INSERT INTO fin_vertex_table VALUES ( 4, '{"type":"Company","name":"Oracle"}');
INSERT INTO fin_vertex_table VALUES ( 5, '{"type":"Account","number":10039}');
INSERT INTO fin_vertex_table VALUES ( 6, '{"type":"Account","number":2090}');
INSERT INTO fin_vertex_table VALUES ( 7, '{"type":"Account","number":8021}');
INSERT INTO fin_vertex_table VALUES ( 8, '{"type":"Account","number":1001}');
 
CREATE TABLE fin_edge_table (
  id NUMBER PRIMARY KEY,
  src NUMBER REFERENCES fin_vertex_table ( id ),
  dst NUMBER REFERENCES fin_vertex_table ( id ),
  properties VARCHAR2(2000)
);
 
INSERT INTO fin_edge_table VALUES ( 1, 7, 1, '{"type":"owner"}');
INSERT INTO fin_edge_table VALUES ( 2, 5, 2, '{"type":"owner"}');
INSERT INTO fin_edge_table VALUES ( 3, 6, 3, '{"type":"owner"}');
INSERT INTO fin_edge_table VALUES ( 4, 8, 4, '{"type":"owner"}');
INSERT INTO fin_edge_table VALUES ( 5, 2, 4, '{"type":"worksFor"}');
INSERT INTO fin_edge_table VALUES ( 6, 5, 7, '{"type":"transaction","amount":1000.00}');
INSERT INTO fin_edge_table VALUES ( 7, 7, 8, '{"type":"transaction","amount":1500.30}');
INSERT INTO fin_edge_table VALUES ( 8, 7, 8, '{"type":"transaction","amount":3000.70}');
INSERT INTO fin_edge_table VALUES ( 9, 8, 6, '{"type":"transaction","amount":9999.50}');
INSERT INTO fin_edge_table VALUES ( 10, 6, 5, '{"type":"transaction","amount":9900.00}');

前述のコードで示されているように、各頂点と各エッジは、それぞれの表内の単一の行で表されます。最初の列は、頂点またはエッジの一意キーです。エッジ表の2番目と3番目の列は、それぞれソース・キーと宛先キーです。頂点表およびエッジ表の最後の列では、すべてのプロパティ、およびラベルが1つのJSONオブジェクトにエンコードされます。JSONは、順序付けされていない、名前と値のペアのセットです。ここでは、このようなペアを使用して、プロパティ名とそれらの値、およびラベルの値をエンコードできます。ラベルの場合は、typeやlabelなどの任意の名前を選択できます。この例では、typeを使用します。

頂点またはエッジのすべてのラベルおよびプロパティは単一のJSON値としてエンコードされるため、新しいラベルまたはプロパティがグラフに追加されたときにスキーマを更新する必要はありません。かわりに、頂点およびエッジを追加挿入するか、SQLを介して基礎となる表内のJSON値を更新することで、新しいラベルおよびプロパティを追加できます。

次の2つの例では、RDBMS上のPGQLおよびPGX上のPGQLのJSONオブジェクトからラベルおよびプロパティの値を抽出する方法をそれぞれ示します。

例3-2 JSON_VALUEを使用したJSONプロパティの抽出(RDBMS上のPGQL)

次のコードでは、fin_vertex_table表およびfin_edge_table表を使用してプロパティ・グラフ・ビューを作成し、PGQLのSELECT問合せを実行します。

PgqlStatement pgqlStmnt = pgqlConn.createStatement();
 
/* Create the property graph */
pgqlStmnt.execute(
  "CREATE PROPERTY GRAPH financial_transactions " +
  "  VERTEX TABLES ( " +
  "    fin_vertex_table PROPERTIES ( properties ) ) " +
  "  EDGE TABLES ( " +
  "    fin_edge_table " +
  "      SOURCE KEY ( src ) REFERENCES fin_vertex_table " +
  "      DESTINATION KEY ( dst ) REFERENCES fin_vertex_table " +
  "      PROPERTIES ( properties ) ) " +
  "  OPTIONS ( PG_VIEW )");
 
/* Set the name of the graph so that we can omit the ON clause from queries */
pgqlConn.setGraph("FINANCIAL_TRANSACTIONS");
 
/* PGQL query: find all outgoing transactions from account 8021. Output the
   transaction amount and the destination account number. */
PgqlResultSet rs = pgqlStmnt.executeQuery(
  "SELECT JSON_VALUE(trans.properties, '$.amount') AS transaction_amount, " +
  "       JSON_VALUE(account2.properties, '$.number') AS account_number " +
  "FROM MATCH (account1) -[trans]-> (account2) " +
  "WHERE JSON_VALUE(account1.properties, '$.number') = 8021 " +
  "  AND JSON_VALUE(trans.properties, '$.type') = 'transaction'");
 
rs.print();
rs.close();
pgqlStmnt.close();

前述のコードでは、CREATE PROPERTY GRAPH文で、JSON列をpropertiesという名前のプロパティにマップしています。したがって、このプロパティには、頂点またはエッジのすべてのラベルとプロパティが含まれています。PGQLのSELECT問合せにより、JSON_VALUEを使用してこれらのラベルとプロパティを抽出します。

たとえば、account1.number = 8021のかわりに、JSON_VALUE(account1.properties, '$.number') = 8021を使用する必要があります。これにより、問合せは少し長くなります。

Javaコードの出力は次のとおりです。

+-------------------------+
| AMOUNT | ACCOUNT_NUMBER |
+-------------------------+
| 1500.3 | 1001           |
| 3000.7 | 1001           |
+-------------------------+

例3-3 UDFの使用によるJSONプロパティ値の抽出(PGX上のPGQL)

この例は、2つの部分で構成されています。最初の部分ではUDFの作成を示し、2番目の部分では、グラフ・サーバー(PGX)へのグラフのロード、続いて、そのUDFを使用したPGQL問合せの実行を示します。

グラフ・サーバー(PGX)にはRDBMS上のPGQLのような組込みJSON_VALUE関数がないため、かわりにJavaのUDFを作成できます。

次のように、UDFを実装するJavaクラス(MyJsonUtils.java)を作成します。

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
 
public class MyJsonUtils {
 
  private final static ObjectMapper mapper = new ObjectMapper();
 
  public static String get_prop(String json_string, String prop_name) throws JsonProcessingException {
    JsonNode node =  mapper.readTree(json_string);
    return node.path(prop_name).asText();
  }
}

クラス・パスに追加された/opt/oracle/graph/pgx/server/lib/*にあるJARを使用してクラスをコンパイルします。これは、ライブラリ・フォルダに、JSONの解析に必要なJacksonライブラリが含まれているためです。

mkdir ./target
javac -classpath .:/opt/oracle/graph/pgx/server/lib/* -d ./target *.java
cd target
jar cvf MyJsonUtils.jar *

次のUDF JSON構成ファイル(my_udfs.json)を使用して、PGXでのユーザー定義関数(UDF)で示されている手順3から手順6に従って、グラフ・サーバー(PGX)にJavaのUDFを登録できるようになりました。

{
  "user_defined_functions": [
    {
      "namespace": "my",
      "function_name": "get_prop",
      "language": "java",
      "implementation_reference": "MyJsonUtils",
      "return_type": "string",
      "arguments": [
        {
          "name": "json_string",
          "type": "string"
        },
        {
          "name": "prop_name",
          "type": "string"
        }
      ]
    }
  ]
}

JSONからプロパティ値を抽出するためのUDFの実装時に、グラフをグラフ・サーバー(PGX)にロードしてPGQL問合せを発行できるようになりました。

/* Load the graph into the Graph Server (PGX) */
ServerInstance instance = GraphServer.getInstance("http://localhost:7007", username, password.toCharArray());
session = instance.createSession("my-session");
PgxGraph g = session.readGraphByName("FINANCIAL_TRANSACTIONS", GraphSource.PG_VIEW);
 
/* PGQL query: find all shortest paths from account 10039 to account 2090 following only outgoing transaction
   edges. Output the list of transaction amounts along each path as well as the total amount of the transactions
   along each path. */
g.queryPgql(
  "  SELECT LISTAGG(my.get_prop(e.properties, 'amount'), ' + ') || ' = ' AS amounts_along_path, " +
  "         SUM(CAST(my.get_prop(e.properties, 'amount') AS DOUBLE)) AS total_amount " +
  "    FROM MATCH ALL SHORTEST (a) (-[e]-> WHERE my.get_prop(e.properties, 'type') = 'transaction')* (b) " +
  "   WHERE my.get_prop(a.properties, 'number') = '10039' AND " +
  "         my.get_prop(b.properties, 'number') = '2090' " +
  "ORDER BY total_amount").print().close();

Javaコードの出力は次のとおりです。

+--------------------------------------------+
| amounts_along_path          | total_amount |
+--------------------------------------------+
| 1000.0 + 1500.3 + 9999.5 =  | 12499.8      |
| 1000.0 + 3000.7 + 9999.5 =  | 14000.2      |
+--------------------------------------------+