One-hop patterns

Introduction

Pattern matching by nature is declarative. It enables users to focus on specifying what they want from a query without worrying about the underlying query processing.

The pattern specifies sets of vertex types and how they are connected by edge types.

A pattern usually appears in the FROM clause, the most fundamental part of the query structure. A pattern can be refined further with conditions in the WHERE clause.

In this tutorial, we’ll start with simple one-hop path patterns, and then extend it multi-hop patterns and finally multiple-path patterns.

Pattern matching queries support nested queries.

1-Hop pattern

The easiest way to understand patterns is to start with a simple 1-Hop pattern. Even a single hop has several options. After we’ve tackled single hops, then we’ll see how to add repetition to make variable length patterns and how to connect single hops to form bigger patterns.

In GSQL syntax V2, we use the punctuation -( )- to denote a 1-hop pattern.

The edge type(s) is enclosed in the parentheses () and the hyphens - symbolize connection. The directionality of the connection is explicitly stated for each edge type. Arrowheads are used to indicate direction: > or <.

  • For an undirected edge E, leave as is: E

  • For a directed edge E from left to right, use a right-pointing arrowhead: E>

  • For a directed edge E from right to left, use a left-pointing arrowhead: <E

In this notation, E is a placeholder for any edge in a graph. "Left" and "right" refer to the order of the actual written pattern itself.

Vertex_A -(Edge)- Vertex_B

Here, Edge is to the right of Vertex_A and to the left of Vertex_B.

Alternation separator

In the LDBC SNB schema, there are two directed relationships between Person and Message.

  1. Person LIKES Message

  2. Message HAS_CREATOR Person

Person:p -(LIKES>:e)- Message:m
Person:p -(<HAS_CREATOR:e)- Message:m

Despite the fact that these relationships are in opposite directions, we can use the alternation separator (the pipe) | to include both of them in the same pattern:

Person:p -((LIKES>|<HAS_CREATOR):e)- Message:m

Edge type wildcards

Use the underscore _ or the keyword ANY to denote any undirected edge type. Empty parentheses () means any edge, directed or undirected.

If a type or wildcard is not given, it includes both directed and undirected edges in the results.

Example including directed and undirected edges
Person:p -(:e)- Message:m

Vertex type wildcards

Use _, ANY or omit the vertex type to indicate any vertex type. If you omit the vertex type, you must provide a vertex alias.

The source or the target vertex set may be:

  • a vertex type SELECT t FROM Person:s -(IS_LOCATED_IN>:e)- City:t

  • an alternation of vertex types SELECT t FROM (Post|Comment):s -(IS_LOCATED_IN>:e)- Country:t

  • omitted, with only an alias, meaning any vertex type SELECT s FROM :s -(IS_LOCATED_IN>:e)- Country:t

  • omitted, without an alias, meaning any vertex type SELECT t FROM -(IS_LOCATED_IN>:e)- Country:t

Performance may be better when types are explicitly provided.

Examples of 1-Hop patterns

  1. FROM X:x -(E1:e1)- Y:y

    • E1 is an undirected edge, x and y bind to the end points of E1, and e1 is the alias of E1.

  2. FROM X:x -(E2>:e2)- Y:y

    • Right directed edge x binds to the source of E2; y binds to the target of E2.

  3. FROM X:x -(<E3:e3)- Y:y

    • Left directed edge; y binds to the source of E3; x binds to the target of E3.

  4. FROM X:x -(:e)- Y:y

    • Any directed or undirected edge between a member of X and a member of Y.

  5. FROM X:x -(_:e)- Y:y

    • Any undirected edge between a member of X and a member of Y.

  6. FROM X:x -(_>:e)- Y:y

    • Any right directed edge with source in X and target in Y.

  7. FROM X:x -(<_:e)- Y:y

    • Any left directed edge with source in Y and target in X.

  8. FROM X:x -((<_|_):e)- Y:y

    • Any left directed or any undirected; | means OR, and parentheses enclose the group of edge descriptors; e is the alias for the edge pattern (<_|_).

  9. FROM X:x -((E1|E2>|<E3):e)- Y:y

    • Any one of the three edge patterns.

  10. FROM X:x -()- Y:y

    • Any directed or undirected edge between a member of X and a member of Y.

    • Same as (<_|_>|_) or (:e).

Running anonymous queries without installing

In this tutorial, we will use Interpreted Mode for GSQL. Interpreted mode lets us skip the INSTALL step, and even run a query as soon as we create it, to offer a more interactive experience. These one-step interpreted queries are unnamed (anonymous) and parameterless.

To run an interpreted query, replace the keyword CREATE with INTERPRET.

INTERPRET QUERY () FOR GRAPH graph_name SYNTAX v2 { <query body> }

Interpreted queries may run slower than installed queries, so we recommend increasing the query timeout threshold:

GSQL: Set Query Timeout to 1 Minute
// 1 unit is 1 millisecond
SET query_timeout = 60000

Examples of 1-Hop Fixed Length Query

You can copy any of the shown GSQL scripts to a file with the file extension .gsql and invoke it in a Linux shell to see the results.

Linux Bash
gsql example.gsql

Example 1

Find persons who know the person named "Viktor Akhiezer" and return the top 3 oldest such persons.

Example 1. Left-Directed Edge Pattern
USE GRAPH ldbc_snb

INTERPRET QUERY () SYNTAX v2 {
   // 1-hop pattern.
   friends = SELECT p
             FROM Person:s -(Knows:e)- Person:p (1)
             WHERE s.first_name == "Viktor" AND s.last_name == "Akhiezer"
             ORDER BY p.birthday ASC
             LIMIT 3;

    PRINT  friends[friends.first_name, friends.last_name, friends.birthday];
}
1 In the FROM clause, we directly use vertex type Person as the starting vertex set. This syntax enhancement is available in syntax V2 only.
Output of Example 1
{
  "error": false,
  "message": "",
  "version": {
    "schema": 0,
    "edition": "enterprise",
    "api": "v2"
  },
  "results": [{"friends": [
    {
      "v_id": "4398046517846",
      "attributes": {
        "friends.first_name": "Abdul-Malik",
        "friends.birthday": "1980-04-24 00:00:00",
        "friends.last_name": "Glosca"
      },
      "v_type": "Person"
    },
    {
      "v_id": "10995116279461",
      "attributes": {
        "friends.first_name": "Gregorio",
        "friends.birthday": "1980-05-13 00:00:00",
        "friends.last_name": "Cajes"
      },
      "v_type": "Person"
    },
    {
      "v_id": "6597069776731",
      "attributes": {
        "friends.first_name": "Sven",
        "friends.birthday": "1981-02-25 00:00:00",
        "friends.last_name": "Carlsson"
      },
      "v_type": "Person"
    }
  ]}]
}

Example 2

Find the total number of comments and total number of posts liked by Viktor. A Person can reach Comments or Posts via a directed edge LIKES.

Example 2. Right-directed Edge Pattern
USE GRAPH ldbc_snb

INTERPRET QUERY () SYNTAX v2 {
   SumAccum<int> @comment_cnt= 0;
   SumAccum<int> @post_cnt= 0;

   // 1-hop pattern.
   Result = SELECT s
            FROM Person:s -(Likes>)- :tgt
            WHERE s.first_name == "Viktor" AND s.last_name == "Akhiezer"
            ACCUM CASE WHEN tgt.type == "Comment" THEN
                           s.@comment_cnt += 1
                       WHEN tgt.type == "Post" THEN
                           s.@post_cnt += 1
                   END;

    PRINT  Result[Result.@comment_cnt, Result.@post_cnt];
}
Output of Example 2.
{
  "error": false,
  "message": "",
  "version": {
    "schema": 0,
    "edition": "enterprise",
    "api": "v2"
  },
  "results": [{"Result": [{
    "v_id": "28587302323577",
    "attributes": {
      "Result.@post_cnt": 51,
      "Result.@comment_cnt": 108
    },
    "v_type": "Person"
  }]}]
}

Example 3

Solve the same problem as in Example 2, but use a left-directed edge pattern.

Note below (line 8) that the source vertex set are now Comment and Post, and the target is Person.

Example 3. Left-directed Edge Pattern
USE GRAPH ldbc_snb

INTERPRET QUERY () SYNTAX v2{
   SumAccum<int> @comment_cnt= 0;
   SumAccum<int> @post_cnt= 0;

   Result = SELECT tgt
            FROM Person:tgt -(<Likes_Reverse)- (Comment|Post):src
            WHERE tgt.first_name == "Viktor" AND tgt.last_name == "Akhiezer"
            ACCUM CASE WHEN src.type == "Comment" THEN
                           tgt.@comment_cnt += 1
                       WHEN src.type == "Post" THEN
                           tgt.@post_cnt += 1
                   END;

  PRINT Result[Result.@comment_cnt, Result.@post_cnt];
}

The output should be the same as in Example 2.

Example 4

Find Viktor Akhiezer’s total number of related comments and total number of related posts. That is, a comment or post is either created by Viktor or is liked by Viktor. Note that the HAS_CREATOR edge type starts from Comment|Post, and the LIKES edge type starts from Person.

Example 4. Disjunctive 1-hop edge pattern.
USE GRAPH ldbc_snb
set query_timeout=60000

INTERPRET QUERY () SYNTAX v2{
  SumAccum<int> @comment_cnt= 0;
  SumAccum<int> @post_cnt= 0;

  Result = SELECT tgt
           FROM Person:tgt -(<Has_Creator|Likes>)- (Comment|Post):src
           WHERE tgt.first_name == "Viktor" AND tgt.last_name == "Akhiezer"
           ACCUM CASE WHEN src.type == "Comment" THEN
                          tgt.@comment_cnt += 1
                      WHEN src.type == "Post" THEN
                          tgt.@post_cnt += 1
                 END;

  PRINT Result[Result.@comment_cnt, Result.@post_cnt];
}
Output of Example 4.
{
  "error": false,
  "message": "",
  "version": {
    "schema": 0,
    "edition": "enterprise",
    "api": "v2"
  },
  "results": [{"Result": [{
    "v_id": "28587302323577",
    "attributes": {
      "Result.@post_cnt": 96,
      "Result.@comment_cnt": 152
    },
    "v_type": "Person"
  }]}]
}

Example 5

Find the total number of comments or posts related to "Viktor Akhiezer". This time, we count them together and, we use the wildcard _ to represent the two types of edges: HAS_CREATOR and LIKES_REVERSE. Both are following the same direction.

Example 5. Disjunctive 1-hop edge pattern.
USE GRAPH ldbc_snb

INTERPRET QUERY () SYNTAX v2{
  SumAccum<int> @@cnt= 0;

  Result = SELECT tgt
           FROM Person:tgt -(<_)- (Comment|Post):src
           WHERE tgt.first_name == "Viktor" AND tgt.last_name == "Akhiezer"
           ACCUM  @@cnt += 1;

  PRINT @@cnt;
}
Output of Example 5.
{
  "error": false,
  "message": "",
  "version": {
    "schema": 0,
    "edition": "enterprise",
    "api": "v2"
  },
  "results": [{"@@cnt": 248}]
}