6.9.1.4.6.3 Path Query Options

A few options are available for executing path queries in PGQL. There are two basic evaluation methods available in Oracle SQL: CONNECT BY or recursive WITH clauses. Recursive WITH is the default evaluation method. In addition, you can further modify the recursive WITH evaluation method to include a DISTINCT modifier during the recursive step of query evaluation. Computing distinct vertices at each step helps prevent a combinatorial explosion in highly connected graphs. The DISTINCT modifier is not added by default because it requires a specific parameter setting in the database ("_recursive_with_control"=8).

You can also control the maximum length of paths searched. Path length in this case is defined as the number of repetitions allowed when evaluating the * and + operators. The default maximum length is unlimited.

Path evaluation options are summarized as follows.

  • CONNECT BY: To use CONNECT BY, specify 'USE_RW=F' in the options argument or specify -Doracle.pg.rdbms.pgql.useRW=false in the Java command line.

  • Distinct Modifier in Recursive WITH: To use the DISTINCT modifier in the recursive step, first set "_recursive_with_control"=8 in your database session, then specify 'USE_DIST_RW=T' in the options argument or specify -Doracle.pg.rdbms.pgql.useDistRW=true in the Java command line. You will encounter ORA-32486: unsupported operation in recursive branch of recursive WITH clause if "_recursive_with_control" has not been set to 8 in your session.

  • Path Length Restriction: To limit maximum number of repetitions when evaluating * and + to n, specify 'MAX_PATH_LEN=n' in the query options argument or specify -Doracle.pg.rdbms.pgql.maxPathLen=n in the Java command line.

Example 6-19 PgqlExample12.java

PgqlExample12.java shows path query translations under various options.

import java.sql.Connection;
import java.sql.Statement;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlSqlQueryTrans;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to use various options with PGQL path queries.
 */
public class PgqlExample12
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Set "_recursive_with_control"=8 to enable distinct optimization
      // optimization for recursive with
      Statement stmt = conn.createStatement();
      stmt.executeUpdate("alter session set \"_recursive_with_control\"=8");
      stmt.close();

      // Path Query to illustrate options      
      String pgql = 
        "PATH fof AS ()-[:\"friendOf\"]->() "+
        "SELECT id(v1), id(v2) "+
        "FROM MATCH (v1)-/:fof*/->(v2) "+
        "WHERE id(v1) = 2";


      // get SQL translation with defaults - Non-distinct Recursive WITH
      PgqlSqlQueryTrans sqlTrans = 
        ps.translateQuery(pgql /* query string */, 
                          2    /* parallel: default is 1 */,
                          2    /* dynamic sampling: default is 2 */,
                          -1   /* max results: -1 implies no limit */,
                          ""   /* options */);
      System.out.println("-- Default Path Translation --------------------");
      System.out.println(sqlTrans.getSqlTranslation()+"\n");

      // get SQL translation with DISTINCT reachability optimization
      sqlTrans = 
        ps.translateQuery(pgql /* query string */, 
                          2    /* parallel: default is 1 */,
                          2    /* dynamic sampling: default is 2 */,
                          -1   /* max results: -1 implies no limit */,
                          " USE_DIST_RW=T " /* options */);
      System.out.println("-- DISTINCT RW Path Translation --------------------");
      System.out.println(sqlTrans.getSqlTranslation()+"\n");

      // get SQL translation with CONNECT BY
      sqlTrans = 
        ps.translateQuery(pgql /* query string */, 
                          2    /* parallel: default is 1 */,
                          2    /* dynamic sampling: default is 2 */,
                          -1   /* max results: -1 implies no limit */,
                          " USE_RW=F " /* options */);
      System.out.println("-- CONNECT BY Path Translation --------------------");
      System.out.println(sqlTrans.getSqlTranslation()+"\n");
    }
    finally {
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample12.java gives the following output for test_graph (which can be loaded using GraphLoaderExample.java code).

-- Default Path Translation --------------------
SELECT /*+ parallel(2) */ * FROM(SELECT 7 AS "id(v1)$T",
to_nchar(T0$0.SVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v1)$V",
T0$0.SVID AS "id(v1)$VN",
to_timestamp_tz(null) AS "id(v1)$VT",
7 AS "id(v2)$T",
to_nchar(T0$0.DVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v2)$V",
T0$0.DVID AS "id(v2)$VN",
to_timestamp_tz(null) AS "id(v2)$VT"
FROM (/*Path[*/SELECT DISTINCT SVID, DVID
FROM (
SELECT 2 AS SVID, 2 AS DVID
FROM SYS.DUAL
WHERE EXISTS(
SELECT 1
FROM "SCOTT".TEST_GRAPHVT$
WHERE VID = 2)
UNION ALL
SELECT SVID,DVID FROM
(WITH RW (ROOT, DVID) AS
( SELECT ROOT, DVID FROM
(SELECT SVID ROOT, DVID
FROM (SELECT T0$0.SVID AS SVID,
T0$0.DVID AS DVID
FROM "SCOTT".TEST_GRAPHGT$ T0$0
WHERE T0$0.SVID = 2 AND
(T0$0.EL = n'friendOf' AND T0$0.EL IS NOT NULL))
) UNION ALL
SELECT RW.ROOT, R.DVID
FROM (SELECT T0$0.SVID AS SVID,
T0$0.DVID AS DVID
FROM "SCOTT".TEST_GRAPHGT$ T0$0
WHERE (T0$0.EL = n'friendOf' AND T0$0.EL IS NOT NULL)) R, RW
WHERE RW.DVID = R.SVID )
CYCLE DVID SET cycle_col TO 1 DEFAULT 0
SELECT ROOT SVID, DVID FROM RW))/*]Path*/) T0$0
WHERE T0$0.SVID = 2)

-- DISTINCT RW Path Translation --------------------
SELECT /*+ parallel(2) */ * FROM(SELECT 7 AS "id(v1)$T",
to_nchar(T0$0.SVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v1)$V",
T0$0.SVID AS "id(v1)$VN",
to_timestamp_tz(null) AS "id(v1)$VT",
7 AS "id(v2)$T",
to_nchar(T0$0.DVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v2)$V",
T0$0.DVID AS "id(v2)$VN",
to_timestamp_tz(null) AS "id(v2)$VT"
FROM (/*Path[*/SELECT DISTINCT SVID, DVID
FROM (
SELECT 2 AS SVID, 2 AS DVID
FROM SYS.DUAL
WHERE EXISTS(
SELECT 1
FROM "SCOTT".TEST_GRAPHVT$
WHERE VID = 2)
UNION ALL
SELECT SVID,DVID FROM
(WITH RW (ROOT, DVID) AS
( SELECT ROOT, DVID FROM
(SELECT SVID ROOT, DVID
FROM (SELECT T0$0.SVID AS SVID,
T0$0.DVID AS DVID
FROM "SCOTT".TEST_GRAPHGT$ T0$0
WHERE T0$0.SVID = 2 AND
(T0$0.EL = n'friendOf' AND T0$0.EL IS NOT NULL))
) UNION ALL
SELECT DISTINCT RW.ROOT, R.DVID
FROM (SELECT T0$0.SVID AS SVID,
T0$0.DVID AS DVID
FROM "SCOTT".TEST_GRAPHGT$ T0$0
WHERE (T0$0.EL = n'friendOf' AND T0$0.EL IS NOT NULL)) R, RW
WHERE RW.DVID = R.SVID )
CYCLE DVID SET cycle_col TO 1 DEFAULT 0
SELECT ROOT SVID, DVID FROM RW))/*]Path*/) T0$0
WHERE T0$0.SVID = 2)

-- CONNECT BY Path Translation --------------------
SELECT /*+ parallel(2) */ * FROM(SELECT 7 AS "id(v1)$T",
to_nchar(T0$0.SVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v1)$V",
T0$0.SVID AS "id(v1)$VN",
to_timestamp_tz(null) AS "id(v1)$VT",
7 AS "id(v2)$T",
to_nchar(T0$0.DVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v2)$V",
T0$0.DVID AS "id(v2)$VN",
to_timestamp_tz(null) AS "id(v2)$VT"
FROM (/*Path[*/SELECT DISTINCT SVID, DVID
FROM (
SELECT 2 AS SVID, 2 AS DVID
FROM SYS.DUAL
WHERE EXISTS(
SELECT 1
FROM "SCOTT".TEST_GRAPHVT$
WHERE VID = 2)
UNION ALL
SELECT SVID, DVID
FROM
(SELECT CONNECT_BY_ROOT T0$0.SVID AS SVID, T0$0.DVID AS DVID
FROM(
SELECT T0$0.SVID AS SVID,
T0$0.DVID AS DVID
FROM "SCOTT".TEST_GRAPHGT$ T0$0
WHERE (T0$0.EL = n'friendOf' AND T0$0.EL IS NOT NULL)) T0$0
START WITH T0$0.SVID = 2
CONNECT BY NOCYCLE PRIOR DVID = SVID))/*]Path*/) T0$0
WHERE T0$0.SVID = 2)

The query plan for the first query with the default recursive WITH strategy should look similar to the following.

-- default RW
---------------------------------------------------------------------------------------
| Id  | Operation                                         | Name                      |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                                  |                           |
|   1 |  TEMP TABLE TRANSFORMATION                        |                           |
|   2 |   LOAD AS SELECT (CURSOR DURATION MEMORY)         | SYS_TEMP_0FD9D6662_37AA44 |
|   3 |    UNION ALL (RECURSIVE WITH) BREADTH FIRST       |                           |
|   4 |     PX COORDINATOR                                |                           |
|   5 |      PX SEND QC (RANDOM)                          | :TQ20000                  |
|   6 |       LOAD AS SELECT (CURSOR DURATION MEMORY)     | SYS_TEMP_0FD9D6662_37AA44 |
|   7 |        PX PARTITION HASH ALL                      |                           |
|*  8 |     TABLE ACCESS BY LOCAL INDEX ROWID BATCHED     | TEST_GRAPHGT$             |
|*  9 |      INDEX RANGE SCAN                             | TEST_GRAPHXSG$            |
|  10 |     PX COORDINATOR                                |                           |
|  11 |      PX SEND QC (RANDOM)                          | :TQ10000                  |
|  12 |       LOAD AS SELECT (CURSOR DURATION MEMORY)     | SYS_TEMP_0FD9D6662_37AA44 |
|  13 |        NESTED LOOPS                               |                           |
|  14 |     PX BLOCK ITERATOR                             |                           |
|* 15 |      TABLE ACCESS FULL                            | SYS_TEMP_0FD9D6662_37AA44 |
|  16 |     PARTITION HASH ALL                            |                           |
|* 17 |      TABLE ACCESS BY LOCAL INDEX ROWID BATCHED    | TEST_GRAPHGT$             |
|* 18 |       INDEX RANGE SCAN                            | TEST_GRAPHXSG$            |
|  19 |   PX COORDINATOR                                  |                           |
|  20 |    PX SEND QC (RANDOM)                            | :TQ30001                  |
|  21 |     VIEW                                          |                           |
|  22 |      HASH UNIQUE                                  |                           |
|  23 |       PX RECEIVE                                  |                           |
|  24 |        PX SEND HASH                               | :TQ30000                  |
|  25 |     HASH UNIQUE                                   |                           |
|  26 |      VIEW                                         |                           |
|  27 |       UNION-ALL                                   |                           |
|  28 |        PX SELECTOR                                |                           |
|* 29 |         FILTER                                    |                           |
|  30 |          FAST DUAL                                |                           |
|  31 |          PARTITION HASH SINGLE                    |                           |
|* 32 |           INDEX SKIP SCAN                         | TEST_GRAPHXQV$            |
|  33 |        VIEW                                       |                           |
|* 34 |         VIEW                                      |                           |
|  35 |          PX BLOCK ITERATOR                        |                           |
|  36 |           TABLE ACCESS FULL                       | SYS_TEMP_0FD9D6662_37AA44 |
---------------------------------------------------------------------------------------

The query plan for the second query that adds a DISTINCT modifier in the recursive step should look similar to the following.

--------------------------------------------------------------------------------------------
| Id  | Operation                                              | Name                      |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                                       |                           |
|   1 |  TEMP TABLE TRANSFORMATION                             |                           |
|   2 |   LOAD AS SELECT (CURSOR DURATION MEMORY)              | SYS_TEMP_0FD9D6669_37AA44 |
|   3 |    UNION ALL (RECURSIVE WITH) BREADTH FIRST            |                           |
|   4 |     PX COORDINATOR                                     |                           |
|   5 |      PX SEND QC (RANDOM)                               | :TQ20000                  |
|   6 |       LOAD AS SELECT (CURSOR DURATION MEMORY)          | SYS_TEMP_0FD9D6669_37AA44 |
|   7 |        PX PARTITION HASH ALL                           |                           |
|*  8 |     TABLE ACCESS BY LOCAL INDEX ROWID BATCHED          | TEST_GRAPHGT$             |
|*  9 |      INDEX RANGE SCAN                                  | TEST_GRAPHXSG$            |
|  10 |     PX COORDINATOR                                     |                           |
|  11 |      PX SEND QC (RANDOM)                               | :TQ10001                  |
|  12 |       LOAD AS SELECT (CURSOR DURATION MEMORY)          | SYS_TEMP_0FD9D6669_37AA44 |
|  13 |        SORT GROUP BY                                   |                           |
|  14 |     PX RECEIVE                                         |                           |
|  15 |      PX SEND HASH                                      | :TQ10000                  |
|  16 |       SORT GROUP BY                                    |                           |
|  17 |        NESTED LOOPS                                    |                           |
|  18 |         PX BLOCK ITERATOR                              |                           |
|* 19 |          TABLE ACCESS FULL                             | SYS_TEMP_0FD9D6669_37AA44 |
|  20 |         PARTITION HASH ALL                             |                           |
|* 21 |          TABLE ACCESS BY LOCAL INDEX ROWID BATCHED     | TEST_GRAPHGT$             |
|* 22 |           INDEX RANGE SCAN                             | TEST_GRAPHXSG$            |
|  23 |   PX COORDINATOR                                       |                           |
|  24 |    PX SEND QC (RANDOM)                                 | :TQ30001                  |
|  25 |     VIEW                                               |                           |
|  26 |      HASH UNIQUE                                       |                           |
|  27 |       PX RECEIVE                                       |                           |
|  28 |        PX SEND HASH                                    | :TQ30000                  |
|  29 |     HASH UNIQUE                                        |                           |
|  30 |      VIEW                                              |                           |
|  31 |       UNION-ALL                                        |                           |
|  32 |        PX SELECTOR                                     |                           |
|* 33 |         FILTER                                         |                           |
|  34 |          FAST DUAL                                     |                           |
|  35 |          PARTITION HASH SINGLE                         |                           |
|* 36 |           INDEX SKIP SCAN                              | TEST_GRAPHXQV$            |
|  37 |        VIEW                                            |                           |
|* 38 |         VIEW                                           |                           |
|  39 |          PX BLOCK ITERATOR                             |                           |
|  40 |           TABLE ACCESS FULL                            | SYS_TEMP_0FD9D6669_37AA44 |
--------------------------------------------------------------------------------------------

The query plan for the third query that uses CONNECTY BY should look similar to the following.

-----------------------------------------------------------------------------
| Id  | Operation                                      | Name               |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT                               |                    |
|   1 |  VIEW                                          |                    |
|   2 |   HASH UNIQUE                                  |                    |
|   3 |    VIEW                                        |                    |
|   4 |     UNION-ALL                                  |                    |
|*  5 |      FILTER                                    |                    |
|   6 |       FAST DUAL                                |                    |
|   7 |       PARTITION HASH SINGLE                    |                    |
|*  8 |        INDEX SKIP SCAN                         | TEST_GRAPHXQV$     |
|*  9 |      VIEW                                      |                    |
|* 10 |       CONNECT BY WITH FILTERING                |                    |
|  11 |        PX COORDINATOR                          |                    |
|  12 |     PX SEND QC (RANDOM)                        | :TQ10000           |
|  13 |      PX PARTITION HASH ALL                     |                    |
|* 14 |       TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| TEST_GRAPHGT$      |
|* 15 |        INDEX RANGE SCAN                        | TEST_GRAPHXSG$     |
|  16 |        NESTED LOOPS                            |                    |
|  17 |     CONNECT BY PUMP                            |                    |
|  18 |     PARTITION HASH ALL                         |                    |
|* 19 |      TABLE ACCESS BY LOCAL INDEX ROWID BATCHED | TEST_GRAPHGT$      |
|* 20 |       INDEX RANGE SCAN                         | TEST_GRAPHXSG$     |
-----------------------------------------------------------------------------

Example 6-20 PgqlExample13.java

PgqlExample13.java shows how to set length restrictions during path query evaluation.

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to use the maximum path length option for 
 * PGQL path queries.
 */
public class PgqlExample13
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Path Query to illustrate options
      String pgql = 
        "PATH fof AS ()-[:\"friendOf\"]->() "+
        "SELECT v1.\"fname\" AS fname1, v2.\"fname\" AS fname2 "+
        "FROM MATCH (v1)-/:fof*/->(v2) "+
        "WHERE v1.\"fname\" = 'Ray'";


      // execute query for 1-hop
      rs = ps.executeQuery(pgql, " MAX_PATH_LEN=1 ");

      // print results
      System.out.println("-- Results for 1-hop ----------------");
      rs.print();

      // close result set
      rs.close();

      // execute query for 2-hop
      rs = ps.executeQuery(pgql, " MAX_PATH_LEN=2 ");

      // print results
      System.out.println("-- Results for 2-hop ----------------");
      rs.print();

      // close result set
      rs.close();

      // execute query for 3-hop
      rs = ps.executeQuery(pgql, " MAX_PATH_LEN=3 ");

      // print results
      System.out.println("-- Results for 3-hop ----------------");
      rs.print();

      // close result set
      rs.close();

    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample13.java has the following output for test_graph (which can be loaded using GraphLoaderExample.java code).

-- Results for 1-hop ----------------
+-----------------+
| FNAME1 | FNAME2 |
+-----------------+
| Ray    | Ray    |
| Ray    | Susan  |
+-----------------+
-- Results for 2-hop ----------------
+-----------------+
| FNAME1 | FNAME2 |
+-----------------+
| Ray    | Susan  |
| Ray    | Ray    |
| Ray    | John   |
+-----------------+
-- Results for 3-hop ----------------
+-----------------+
| FNAME1 | FNAME2 |
+-----------------+
| Ray    | Susan  |
| Ray    | Bill   |
| Ray    | Ray    |
| Ray    | John   |
+-----------------+