Control Flow Statements

The GSQL Query Language includes a comprehensive set of control flow statements to empower sophisticated graph traversal and data computation: IF/ELSE, CASE, WHILE, and FOREACH.

Differences in Block Syntax

Note that any of these statements can be used as a query-body statement or as a DML-sub level statement.

If the control flow statement is at the query-body level, then its block(s) of statements are query-body statements ( queryBodyStmts ). In a queryBodyStmts block , each individual statement ends with a semicolon, so there is always a semicolon at the end.

If the control flow statement is at the DML-sub level, then its block(s) of statements are DML-sub statements ( dmlSubStmtList ). In a dmlSubStmtList block, a comma separates statements, but there is no punctuation at the end.

The "Statement Types" subsection in the Chapter on "CREATE / INSTALL / RUN / SHOW / DROP QUERY" has a more detailed general example of the difference between queryBodyStmts and dmlSubStmts.

IF Statement

The IF statement provides conditional branching: execute a block of statements ( queryBodyStmts or dmlSubStmtList ) only if a given condition is true. The IF statement allows for zero or more ELSE-IF clauses, followed by an optional ELSE clause. The IF statement can be used either at the query-body level or at the DML-sub-statement level. (See the note about differences in block syntax.)

IF syntax
queryBodyIfStmt := IF condition THEN queryBodyStmts
                 [ELSE IF condition THEN queryBodyStmts ]*
                 [ELSE queryBodyStmts ] END
dmlSubIfStmt :=    IF condition THEN dmlSubStmtList
                 [ELSE IF condition THEN dmlSubStmtList ]*
                 [ELSE dmlSubStmtList ] END

If a particular IF condition is not true, then the flow proceeds to the next ELSE IF condition. When a true condition is encountered, its corresponding block of statements is executed, and then the IF statement terminates (skipping any remaining ELSE-IF or ELSE clauses). If an ELSE-clause is present, its block of statements are executed if none of the preceding conditions are true. Overall, the functionality can be summarized as "execute the first block of statements whose conditional test is true."

IF semantics
# if then
IF x == 5 THEN y = 10; END;      # y is assigned to 10 only if x is 5.

# if then else
IF x == 5 THEN y = 10;       # y is 10 only if x is 5.
ELSE y = 20; END;            # y is 20 only if x is NOT 5.

#if with ELSE IF
IF x == 5 THEN y = 10;       # y is 10 only if x is 5.
ELSE IF x == 7 THEN y = 5;   # y is 5  only if x is 7.
ELSE y = 20; END;            # y is 20 only if x is NOT 5 and NOT 7.
Example 1. countFriendsOf2.gsql : Simple IF-ELSE at query-body level
# count the number of friends a person has, and optionally include coworkers in that count
CREATE QUERY countFriendsOf2(vertex<person> seed, BOOL includeCoworkers) FOR GRAPH friendNet
{
       SumAccum<INT> @@numFriends = 0;
       start = {seed};

       IF includeCoworkers THEN
          friends = SELECT v FROM start -((friend | coworker):e)-> :v
                ACCUM @@numFriends +=1;
       ELSE
          friends = SELECT v FROM start -(friend:e)-> :v
                ACCUM @@numFriends +=1;
       END;
       PRINT @@numFriends, includeCoworkers;
}
Example 1 Results
GSQL > RUN QUERY countFriendsOf2("person2", true)
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{
    "@@numFriends": 5,
    "includeCoworkers": true
  }]
}
GSQL > RUN QUERY countFriendsOf2("person2", false)
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{
    "@@numFriends": 2,
    "includeCoworkers": false
  }]
}
Example 2. IF-ELSE IF-ELSE at query-body level
# determine if a user is active in terms of social networking (i.e., posts frequently)
CREATE QUERY calculateActivity(vertex<person> seed) FOR GRAPH socialNet
{
    SumAccum<INT> @@numberPosts = 0;
    start = {seed};
    result = SELECT postVertex FROM start -(posted:e)-> :postVertex
           ACCUM @@numberPosts += 1;

    IF  @@numberPosts < 2 THEN
       PRINT "Not very active";
    ELSE IF @@numberPosts < 3 THEN
       PRINT "Semi-active";
    ELSE
       PRINT "Very active";
    END;
}
Example 2 Results for Query calculateActivity
GSQL > RUN QUERY calculateActivity("person1")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"Not very active": "Not very active"}]
}
GSQL > RUN QUERY calculateActivity("person5")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"Semi-active": "Semi-active"}]
}
Example 3. Nested IF at query-body level
# use a more advanced activity calculation, taking into account number of posts
# and number of likes that a user made
CREATE QUERY calculateInDepthActivity(vertex<person> seed) FOR GRAPH socialNet
{
    SumAccum<INT> @@numberPosts = 0;
    SumAccum<INT> @@numberLikes = 0;
    start = {seed};
    result = SELECT postVertex FROM start -(posted:e)-> :postVertex
           ACCUM @@numberPosts += 1;
    result = SELECT likedPost FROM start -(liked:e)-> :likedPost
           ACCUM @@numberLikes += 1;

    IF @@numberPosts < 2 THEN
        IF @@numberLikes < 1 THEN
            PRINT "Not very active";
        ELSE
            PRINT "Semi-active";
        END;
    ELSE IF @@numberPosts < 3 THEN
        IF @@numberLikes < 2 THEN
            PRINT "Semi-active";
        ELSE
            PRINT "Active";
        END;
    ELSE
        PRINT "Very active";
    END;
}
Example 3 Results for Query calculateInDepthActivity
GSQL > RUN QUERY calculateInDepthActivity("person1")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"Semi-active": "Semi-active"}]
}
Example 4. Nested IF at DML-sub level
# give each user post an accumulated rating based on the subject and how many likes it has
# This query is equivalent to the query ratePosts shown above
CREATE QUERY ratePosts2() FOR GRAPH socialNet {
    SumAccum<INT> @rating = 0;
    allPeople = {person.*};

    results = SELECT v FROM allPeople -(:e)-> post:v
        ACCUM IF e.type == "posted" THEN
                IF v.subject == "cats" THEN
                  v.@rating += -1     # -1 if post is about cats
                ELSE IF v.subject == "Graphs" THEN
                  v.@rating += 2      # +2 if post is about graphs
                ELSE IF v.subject == "tigergraph" THEN
                  v.@rating += 10     # +10 if post is about tigergraph
                END
              ELSE IF e.type == "liked" THEN
                v.@rating += 3                            # +3 each time post was liked
            END
        ORDER BY v.@rating DESC
        LIMIT 5;
    PRINT results;
}
Example 4 Results for Query ratePosts2
GSQL > RUN QUERY ratePosts2()
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"results": [
    {
      "v_id": "6",
      "attributes": {
        "postTime": "2011-02-05 02:02:05",
        "subject": "tigergraph",
        "@rating": 13
      },
      "v_type": "post"
    },
    {
      "v_id": "0",
      "attributes": {
        "postTime": "2010-01-12 11:22:05",
        "subject": "Graphs",
        "@rating": 11
      },
      "v_type": "post"
    },
    {
      "v_id": "1",
      "attributes": {
        "postTime": "2011-03-03 23:02:00",
        "subject": "tigergraph",
        "@rating": 10
      },
      "v_type": "post"
    },
    {
      "v_id": "5",
      "attributes": {
        "postTime": "2011-02-06 01:02:02",
        "subject": "tigergraph",
        "@rating": 10
      },
      "v_type": "post"
    },
    {
      "v_id": "4",
      "attributes": {
        "postTime": "2011-02-07 05:02:51",
        "subject": "coffee",
        "@rating": 6
      },
      "v_type": "post"
    }
  ]}]
}

CASE Statement

The CASE statement provides conditional branching: execute a block of statements only if a given condition is true. CASE statements can be used as query-body statements or DML-sub-statements. (See the note about differences in block syntax.)

CASE syntax
queryBodyCaseStmt := CASE  (WHEN condition THEN queryBodyStmts)+ [ELSE queryBodyStmts] END
               | CASE expr (WHEN constant  THEN queryBodyStmts)+ [ELSE queryBodyStmts] END
dmlSubCaseStmt := CASE     (WHEN condition THEN dmlSubStmtList)+ [ELSE dmlSubStmtList] END
               | CASE expr (WHEN constant  THEN dmlSubStmtList)+ [ELSE dmlSubStmtList] END

One CASE statement contains one or more WHEN-THEN clauses, each WHEN presenting one expression. The CASE statement may also have one ELSE clause whose statements are executed if none of the preceding conditions are true.

There are two syntaxes of the CASE statement: one equivalent to an if-else statement, and the other is structured like a switch statement. The if-else version evaluates the boolean condition within each WHEN-clause and executes the first block of statements whose condition is true. The optional concluding ELSE-clause is executed only if all WHEN-clause conditions are false.

The switch version evaluates the expression following the keyword WHEN and compares its value to the expression immediately following the keyword CASE. These expressions do not need to be boolean; the CASE statement compares pairs of expressions to see if their values are equal. The first WHEN-THEN clause to have an expression value equal to the CASE expression value is executed; the remaining clauses are skipped. The optional ELSE-clause is executed only if no WHEN-clause expression has a value matching the CASE value.

CASE Semantics
STRING drink = "Juice";

# CASE statement: if-else version
CASE
  WHEN drink == "Juice" THEN @@calories += 50
  WHEN drink == "Soda"  THEN @@calories += 120
  ...
  ELSE @@calories = 0       # Optional else-clause
END
# Since drink = "Juice", 50 will be added to calories

# CASE statement: switch version
CASE drink
  WHEN "Juice" THEN @@calories += 50
  WHEN "Soda"  THEN @@calories += 120
  ...
  ELSE  @@calories = 0    # Optional else-clause
END
# Since drink = "Juice", 50 will be added to calories
Example 1. CASE as IF-ELSE
# Display the total number times connected users posted about a certain subject
CREATE QUERY userNetworkPosts (vertex<person> seedUser, STRING subjectName) FOR GRAPH socialNet {
    SumAccum<INT> @@topicSum = 0;
    OrAccum @visited;
    reachableVertices = {};            # empty vertex set
    visitedVertices (ANY) = {seedUser};  # set that can contain ANY type of vertex

    WHILE visitedVertices.size() !=0 DO        # loop terminates when all neighbors are visited
        visitedVertices = SELECT s              # s is all neighbors of visitedVertices which have not been visited
            FROM visitedVertices-(:e)->:s
            WHERE s.@visited == false
            ACCUM s.@visited = true,
                CASE
                    WHEN s.type == "post" and s.subject == subjectName THEN @@topicSum += 1
                END;
    END;
    PRINT @@topicSum;
}
Example 1 Results for Query userNetworkPosts
GSQL > RUN QUERY userNetworkPosts("person1", "Graphs")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"@@topicSum": 3}]
}
Example 2. CASE as switch
# tally male and female friends of the starting vertex
CREATE QUERY countGenderOfFriends(vertex<person> seed) FOR GRAPH socialNet {
       SumAccum<INT> @@males = 0;
       SumAccum<INT> @@females = 0;
       SumAccum<INT> @@unknown = 0;
       startingVertex = {seed};

       people = SELECT v FROM startingVertex -(friend:e)->:v
       	        ACCUM CASE v.gender
	      	      WHEN "Male" THEN @@males += 1
		   		  WHEN "Female" THEN @@females +=1
		    	  ELSE @@unknown += 1
		   	    END;
	PRINT @@males, @@females, @@unknown;
}
Example 2 Results for Query countGenderOfFriends
GSQL > RUN QUERY countGenderOfFriends("person4")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{
    "@@males": 2,
    "@@unknown": 0,
    "@@females": 1
  }]
}
Example 3. Multiple CASE statements
# give each social network user a social impact score which accumulates
# based on how many friends and posts they have
CREATE QUERY scoreSocialImpact() FOR GRAPH socialNet api("v2") {
       SumAccum<INT> @socialImpact = 0;
       allPeople = {person.*};
       people = SELECT v FROM allPeople:v
              ACCUM CASE WHEN v.outdegree("friend") > 1 THEN v.@socialImpact +=1 END, # +1 point for having > 1 friend
                    CASE WHEN v.outdegree("friend") > 2 THEN v.@socialImpact +=1 END, # +1 point for having > 2 friends
                    CASE WHEN v.outdegree("posted") > 1 THEN v.@socialImpact +=1 END, # +1 point for having > 1 posts
                    CASE WHEN v.outdegree("posted") > 3 THEN v.@socialImpact +=2 END; # +2 points for having > 2 posts
       #PRINT people.@socialImpact;       // api v1
       PRINT people[people.@socialImpact]; // api v2
}
Example 3 Results for Query scoreSocialImpact
GSQL > RUN QUERY scoreSocialImpact()
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"people": [
    {
      "v_id": "person4",
      "attributes": {"people.@socialImpact": 2},
      "v_type": "person"
    },
    {
      "v_id": "person3",
      "attributes": {"people.@socialImpact": 1},
      "v_type": "person"
    },
    {
      "v_id": "person7",
      "attributes": {"people.@socialImpact": 2},
      "v_type": "person"
    },
    {
      "v_id": "person1",
      "attributes": {"people.@socialImpact": 1},
      "v_type": "person"
    },
    {
      "v_id": "person5",
      "attributes": {"people.@socialImpact": 2},
      "v_type": "person"
    },
    {
      "v_id": "person6",
      "attributes": {"people.@socialImpact": 2},
      "v_type": "person"
    },
    {
      "v_id": "person2",
      "attributes": {"people.@socialImpact": 1},
      "v_type": "person"
    },
    {
      "v_id": "person8",
      "attributes": {"people.@socialImpact": 3},
      "v_type": "person"
    }
  ]}]
}
Example 4. Nested CASE statements
# give each user post a rating based on the subject and how many likes it has
CREATE QUERY ratePosts() FOR GRAPH socialNet api("v2") {
  SumAccum<INT> @rating = 0;
  allPeople = {person.*};

  results = SELECT v FROM allPeople -(:e)-> post:v
    ACCUM CASE e.type
      WHEN "posted" THEN
        CASE
          WHEN v.subject == "cats" THEN v.@rating += -1    # -1 if post about cats
          WHEN v.subject == "Graphs" THEN v.@rating += 2   # +2 if post about graphs
          WHEN v.subject == "tigergraph" THEN v.@rating += 10 # +10 if post about tigergraph
          END
      WHEN "liked" THEN v.@rating += 3                 # +3 each time post was liked
      END;
  #PRINT results.@rating; // api v1
  PRINT results[results.@rating]; // api v2
}
Example 4 Results for Query ratePosts
GSQL > RUN QUERY ratePosts()
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"results": [
    {
      "v_id": "0",
      "attributes": {"results.@rating": 11},
      "v_type": "post"
    },
    {
      "v_id": "10",
      "attributes": {"results.@rating": 2},
      "v_type": "post"
    },
    {
      "v_id": "2",
      "attributes": {"results.@rating": 0},
      "v_type": "post"
    },
    {
      "v_id": "4",
      "attributes": {"results.@rating": 6},
      "v_type": "post"
    },
    {
      "v_id": "9",
      "attributes": {"results.@rating": -1},
      "v_type": "post"
    },
    {
      "v_id": "3",
      "attributes": {"results.@rating": 2},
      "v_type": "post"
    },
    {
      "v_id": "5",
      "attributes": {"results.@rating": 10},
      "v_type": "post"
    },
    {
      "v_id": "7",
      "attributes": {"results.@rating": 2},
      "v_type": "post"
    },
    {
      "v_id": "1",
      "attributes": {"results.@rating": 10},
      "v_type": "post"
    },
    {
      "v_id": "11",
      "attributes": {"results.@rating": -1},
      "v_type": "post"
    },
    {
      "v_id": "8",
      "attributes": {"results.@rating": 2},
      "v_type": "post"
    },
    {
      "v_id": "6",
      "attributes": {"results.@rating": 13},
      "v_type": "post"
    }
  ]}]
}

WHILE Statement

The WHILE statement provides unbounded iteration over a block of statements. WHILE statements can be used as query-body statements or DML-sub-statements. (See the note about differences in block syntax.)

WHILE syntax
queryBodyWhileStmt := WHILE condition [LIMIT simpleSize] DO queryBodyStmts END
dmlSubWhileStmt :=    WHILE condition [LIMIT simpleSize] DO dmlSubStmtList END
simpleSize := integer | varName | paramName

The WHILE statement iterates over its body ( queryBodyStmts or dmlSubStmtList ) until the condition evaluates to false or until the iteration limit is met. A condition is any expression that evaluates to a boolean. The condition is evaluated before each iteration. CONTINUE statements can be used to change the control flow within the while block. BREAK statements can be used to exit the while loop.

A WHILE statement may have an optional LIMIT clause. LIMIT clauses has a constant positive integer value or integer variable to constrain the maximum number of loop iterations. The example below demonstrates how the LIMIT behaves.

If a limit value is not specified, it is possible for a WHILE loop to iterate infinitely. It is the responsibility of the query author to design the condition logic so that it is guaranteed to eventually be true (or to set a limit).

WHILE LIMIT semantics
# These three WHILE statements behave the same.  Each terminates when
# (v.size == 0) or after 5 iterations of the loop.
WHILE v.size() !=0 LIMIT 5 DO
    # Some statements
END;

INT iter = 0;
WHILE (v.size() !=0) AND (iter < 5) DO
	# Some statements
    iter = iter + 1;
END;

INT iter = 0;
WHILE v.size() !=0 DO
    IF iter == 5 THEN  BREAK;  END;
    # Some statements
	iter = iter + 1;
END;

Below are a number of examples that demonstrate the use of WHILE statements.

Example 1. Simple WHILE loop
# find all vertices which are reachable from a starting seed vertex (i.e., breadth-first search)
CREATE QUERY reachable(vertex<person> seed) FOR GRAPH workNet
{
    OrAccum @visited;
    reachableVertices = {};        # empty vertex set
    visitedVertices (ANY) = {seed};  # set that can contain ANY type of vertex

    WHILE visitedVertices.size() !=0 DO        # loop terminates when all neighbors are visited
        visitedVertices = SELECT s              # s is all neighbors of visitedVertices which have not been visited
                FROM visitedVertices-(:e)->:s
                WHERE s.@visited == false
                POST-ACCUM s.@visited = true;
        reachableVertices = reachableVertices UNION visitedVertices;
    END;
    PRINT reachableVertices;
}
reachable Results
GSQL > RUN QUERY reachable("person1")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"reachableVertices": [
    {
      "v_id": "person3",
      "attributes": {
        "interestList": ["teaching"],
        "skillSet": [ 6, 1, 4 ],
        "skillList": [ 4, 1, 6 ],
        "locationId": "jp",
        "interestSet": ["teaching"],
        "@visited": true,
        "id": "person3"
      },
      "v_type": "person"
    },
    {
      "v_id": "person9",
      "attributes": {
        "interestList": [ "financial", "teaching" ],
        "skillSet": [ 2, 7, 4 ],
        "skillList": [ 4, 7, 2 ],
        "locationId": "us",
        "interestSet": [ "teaching", "financial" ],
        "@visited": true,
        "id": "person9"
      },
      "v_type": "person"
    },
    {
      "v_id": "person4",
      "attributes": {
        "interestList": ["football"],
        "skillSet": [ 10, 1, 4 ],
        "skillList": [ 4, 1, 10 ],
        "locationId": "us",
        "interestSet": ["football"],
        "@visited": true,
        "id": "person4"
      },
      "v_type": "person"
    },
    {
      "v_id": "person7",
      "attributes": {
        "interestList": [ "art", "sport" ],
        "skillSet": [ 6, 8 ],
        "skillList": [ 8, 6 ],
        "locationId": "us",
        "interestSet": [ "sport", "art" ],
        "@visited": true,
        "id": "person7"
      },
      "v_type": "person"
    },
    {
      "v_id": "person1",
      "attributes": {
        "interestList": [ "management", "financial" ],
        "skillSet": [ 3, 2, 1 ],
        "skillList": [ 1, 2, 3 ],
        "locationId": "us",
        "interestSet": [ "financial", "management" ],
        "@visited": true,
        "id": "person1"
      },
      "v_type": "person"
    },
    {
      "v_id": "person5",
      "attributes": {
        "interestList": [ "sport", "financial", "engineering" ],
        "skillSet": [ 5, 2, 8 ],
        "skillList": [ 8, 2, 5 ],
        "locationId": "can",
        "interestSet": [ "engineering", "financial", "sport" ],
        "@visited": true,
        "id": "person5"
      },
      "v_type": "person"
    },
    {
      "v_id": "person6",
      "attributes": {
        "interestList": [ "music", "art" ],
        "skillSet": [ 10, 7 ],
        "skillList": [ 7, 10 ],
        "locationId": "jp",
        "interestSet": [ "art", "music" ],
        "@visited": true,
        "id": "person6"
      },
      "v_type": "person"
    },
    {
      "v_id": "person2",
      "attributes": {
        "interestList": ["engineering"],
        "skillSet": [ 6, 5, 3, 2 ],
        "skillList": [ 2, 3, 5, 6 ],
        "locationId": "chn",
        "interestSet": ["engineering"],
        "@visited": true,
        "id": "person2"
      },
      "v_type": "person"
    },
    {
      "v_id": "person8",
      "attributes": {
        "interestList": ["management"],
        "skillSet": [ 2, 5, 1 ],
        "skillList": [ 1, 5, 2 ],
        "locationId": "chn",
        "interestSet": ["management"],
        "@visited": true,
        "id": "person8"
      },
      "v_type": "person"
    },
    {
      "v_id": "company3",
      "attributes": {
        "country": "jp",
        "@visited": true,
        "id": "company3"
      },
      "v_type": "company"
    },
    {
      "v_id": "company2",
      "attributes": {
        "country": "chn",
        "@visited": true,
        "id": "company2"
      },
      "v_type": "company"
    },
    {
      "v_id": "company1",
      "attributes": {
        "country": "us",
        "@visited": true,
        "id": "company1"
      },
      "v_type": "company"
    },
    {
      "v_id": "person10",
      "attributes": {
        "interestList": [ "football", "sport" ],
        "skillSet": [3],
        "skillList": [3],
        "locationId": "us",
        "interestSet": [ "sport", "football" ],
        "@visited": true,
        "id": "person10"
      },
      "v_type": "person"
    }
  ]}]
}
Example 2. WHILE loop using a LIMIT
# find all vertices which are reachable within two hops from a starting seed vertex (i.e., breadth-first search)
CREATE QUERY reachableWithinTwo(vertex<person> seed) FOR GRAPH workNet
{
    OrAccum @visited;
    reachableVertices = {};        # empty vertex set
    visitedVertices (ANY) = {seed};  # set that can contain ANY type of vertex

    WHILE visitedVertices.size() !=0 LIMIT 2 DO # loop terminates when all neighbors within 2-hops of the seed vertex are visited
        visitedVertices = SELECT s               # s is all neighbors of visitedVertices which have not been visited
                FROM visitedVertices-(:e)->:s
                WHERE s.@visited == false
                POST-ACCUM s.@visited = true;
        reachableVertices = reachableVertices UNION visitedVertices;
    END;
    PRINT reachableVertices;
}
reachableWithinTwo Results
GSQL > RUN QUERY reachableWithinTwo("person1")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"reachableVertices": [
    {
      "v_id": "person4",
      "attributes": {
        "interestList": ["football"],
        "skillSet": [ 10, 1, 4 ],
        "skillList": [ 4, 1, 10 ],
        "locationId": "us",
        "interestSet": ["football"],
        "@visited": true,
        "id": "person4"
      },
      "v_type": "person"
    },
    {
      "v_id": "person3",
      "attributes": {
        "interestList": ["teaching"],
        "skillSet": [ 6, 1, 4 ],
        "skillList": [ 4, 1, 6 ],
        "locationId": "jp",
        "interestSet": ["teaching"],
        "@visited": true,
        "id": "person3"
      },
      "v_type": "person"
    },
    {
      "v_id": "person9",
      "attributes": {
        "interestList": [ "financial", "teaching" ],
        "skillSet": [ 2, 7, 4 ],
        "skillList": [ 4, 7, 2 ],
        "locationId": "us",
        "interestSet": [ "teaching", "financial" ],
        "@visited": true,
        "id": "person9"
      },
      "v_type": "person"
    },
    {
      "v_id": "person5",
      "attributes": {
        "interestList": [ "sport", "financial", "engineering" ],
        "skillSet": [ 5, 2, 8 ],
        "skillList": [ 8, 2, 5 ],
        "locationId": "can",
        "interestSet": [ "engineering", "financial", "sport" ],
        "@visited": true,
        "id": "person5"
      },
      "v_type": "person"
    },
    {
      "v_id": "person6",
      "attributes": {
        "interestList": [ "music", "art" ],
        "skillSet": [ 10, 7 ],
        "skillList": [ 7, 10 ],
        "locationId": "jp",
        "interestSet": [ "art", "music" ],
        "@visited": true,
        "id": "person6"
      },
      "v_type": "person"
    },
    {
      "v_id": "person10",
      "attributes": {
        "interestList": [ "football", "sport" ],
        "skillSet": [3],
        "skillList": [3],
        "locationId": "us",
        "interestSet": [ "sport", "football" ],
        "@visited": true,
        "id": "person10"
      },
      "v_type": "person"
    },
    {
      "v_id": "person8",
      "attributes": {
        "interestList": ["management"],
        "skillSet": [ 2, 5, 1 ],
        "skillList": [ 1, 5, 2 ],
        "locationId": "chn",
        "interestSet": ["management"],
        "@visited": true,
        "id": "person8"
      },
      "v_type": "person"
    },
    {
      "v_id": "company1",
      "attributes": {
        "country": "us",
        "@visited": true,
        "id": "company1"
      },
      "v_type": "company"
    },
    {
      "v_id": "person2",
      "attributes": {
        "interestList": ["engineering"],
        "skillSet": [ 6, 5, 3, 2 ],
        "skillList": [ 2, 3, 5, 6 ],
        "locationId": "chn",
        "interestSet": ["engineering"],
        "@visited": true,
        "id": "person2"
      },
      "v_type": "person"
    },
    {
      "v_id": "company2",
      "attributes": {
        "country": "chn",
        "@visited": true,
        "id": "company2"
      },
      "v_type": "company"
    },
    {
      "v_id": "person7",
      "attributes": {
        "interestList": [ "art", "sport" ],
        "skillSet": [ 6, 8 ],
        "skillList": [ 8, 6 ],
        "locationId": "us",
        "interestSet": [ "sport", "art" ],
        "@visited": true,
        "id": "person7"
      },
      "v_type": "person"
    },
    {
      "v_id": "person1",
      "attributes": {
        "interestList": [ "management", "financial" ],
        "skillSet": [ 3, 2, 1 ],
        "skillList": [ 1, 2, 3 ],
        "locationId": "us",
        "interestSet": [ "financial", "management" ],
        "@visited": true,
        "id": "person1"
      },
      "v_type": "person"
    }
  ]}]
}

FOREACH Statement

The FOREACH statement provides bounded iteration over a block of statements. FOREACH statements can be used as query-body statements or DML-sub-statements. (See the note about differences in block syntax.)

FOREACH syntax
queryBodyForEachStmt := FOREACH forEachControl DO queryBodyStmts END
dmlSubForEachStmt :=    FOREACH forEachControl DO dmlSubStmtList END

forEachControl := ( iterationVar | "(" keyVar ("," valueVar)+ ")") (IN | ":") setBagExpr
                | iterationVar IN RANGE "[" expr "," expr"]" ["." STEP(" expr ")"]
iterationVar := name
keyVar := name
valueVar := name

The formal syntax for forEachControl appears complex. It can be broken down into the following cases:

  • name IN setBagExpr

  • (key, value) pair IN setBagExpr // because it’s a Map

  • name IN RANGE [ expr, expr ]

  • name IN RANGE [ expr, expr ].STEP ( expr )

Note that setBagExpr includes container accumulators and explicit sets.

The FOREACH statement has the following restrictions:

  • In a DML-sub level FOREACH, it is never permissible to update the loop variable (the variable declared before IN, e.g., var in "FOREACH var IN setBagExpr").

  • In a query-body level FOREACH, in most cases it is not permissible to update the loop variable. The following exceptions apply:

    • If the iteration is over a ListAccum, its values can be updated.

    • If the iteration is over a MapAccum, its values can be updated, but its keys cannot.

  • If the iteration is over a set of vertices, it is not permissible to access (read or write) their vertex-attached accumulators.

  • A query-body-level FOREACH cannot iterate over a set or bag of constants. For example, FOREACH i in (1,2,3) is not supported. However, DML-sub FOREACH does support this.

FOREACH …​ IN RANGE

The FOREACH statement has an optional RANGE clause RANGE[expr, expr], which can be used to define the iteration collection. Optionally, the range may specify a step size:
RANGE[expr, expr].STEP(expr)

Each expr must evaluate to an integer. Any of the integers may be negative, but the step expr may not be 0.

The clause RANGE[a,b].STEP(c) produces the sequence of integers from a to b, inclusive, with step size c. That is,
(a, a+c, a+2*c, a+3*c, …​ a+k*c), where k = the largest integer such that |k*c| ≤ |b-a|.

If the .STEP method is not given, then the step size c = 1.

Nested FOREACH IN RANGE with MapAccum
CREATE QUERY foreachRangeEx() FOR GRAPH socialNet {
  ListAccum<INT> @@t;
  Start = {person.*};
  FOREACH i IN RANGE[0, 2] DO
    @@t += i;
    L = SELECT Start
        FROM Start
        WHERE Start.id == "person1"
        ACCUM
          FOREACH j IN RANGE[0, i] DO
            @@t += j
          END
        ;
  END;
  PRINT @@t;
}
Results for Query foreachRangeEx
GSQL > RUN QUERY foreachRangeEx()
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"@@t": [ 0, 0, 1, 0, 1, 2, 0, 1, 2 ]}]
}
FOREACH IN RANGE with step
CREATE QUERY foreachRangeStep(INT a, INT b, INT c) FOR GRAPH minimalNet {
  ListAccum<INT> @@t;
  FOREACH i IN RANGE[a,b].step(c) DO
    @@t += i;
  END;
  PRINT @@t;
}

The step value can be positive for an ascending range or negative for a descending range. If the step has the wrong polarity, then the loop has zero iterations; that is, the exit condition is already satisfied.

foreachRangeStep.json Results
GSQL > RUN QUERY foreachRangeStep(100,0,-9)
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"@@t": [
    100,
    91,
    82,
    73,
    64,
    55,
    46,
    37,
    28,
    19,
    10,
    1
  ]}]
}
GSQL > RUN QUERY foreachRangeStep(-100,100,-9)
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"@@t": []}]
}

Query-body-level FOREACH Examples

Example 1 - FOREACH with ListAccum
# Count the number of companies whose country matches the provided string
CREATE QUERY companyCount(STRING countryName) FOR GRAPH workNet {
  ListAccum<STRING> @@companyList;
  INT countryCount;
  start = {ANY};                        # start will have a set of all vertex types

  s = SELECT v FROM start:v             # get all vertices
      WHERE v.type == "company"         # that have a type of "company"
      ACCUM @@companyList += v.country; # append the country attribute from all company vertices to the ListAccum

  # Iterate the ListAccum and compare each element to the countryName parameter
  FOREACH item in @@companyList DO
    IF item == countryName THEN
       countryCount = countryCount + 1;
    END;
  END;
  PRINT countryCount;
}
companyCount Results
GSQL > RUN QUERY companyCount("us")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"countryCount": 2}]
}
GSQL > RUN QUERY companyCount("can")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"countryCount": 1}]
}
Example 2 - FOREACH with a seed set
#Find all company person who live in a given country
CREATE QUERY employeesByCompany(STRING country) FOR GRAPH workNet {
  ListAccum<VERTEX<company>> @@companyList;
  start = {ANY};

  # Build a list of all company vertices
  #  (these are vertex IDs only)
  s = SELECT v FROM start:v
      WHERE v.type == "company"
      ACCUM @@companyList += v;

  # Use the vertex IDs as Seeds for vertex sets
  FOREACH item IN @@companyList DO
    companyItem = {item};
    employees = SELECT t FROM companyItem -(worksFor)-> :t
                WHERE (t.locationId == country);
    PRINT employees;
  END;
}
employeesByCompany Results
GSQL > RUN QUERY employeesByCompany("us")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [ {"employees": []},
    {"employees": []},
    {"employees": [
      {
        "v_id": "person9",
        "attributes": {
          "interestList": [
            "financial",
            "teaching"
          ],
          "skillSet": [ 2, 7, 4 ],
          "skillList": [ 4, 7, 2 ],
          "locationId": "us",
          "interestSet": [ "teaching", "financial" ],
          "id": "person9"
        },
        "v_type": "person"
      },
      {
        "v_id": "person10",
        "attributes": {
          "interestList": [ "football", "sport" ],
          "skillSet": [3],
          "skillList": [3],
          "locationId": "us",
          "interestSet": [ "sport", "football" ],
          "id": "person10"
        },
        "v_type": "person"
      },
      {
        "v_id": "person7",
        "attributes": {
          "interestList": [ "art", "sport" ],
          "skillSet": [ 6, 8 ],
          "skillList": [ 8, 6 ],
          "locationId": "us",
          "interestSet": [ "sport", "art" ],
          "id": "person7"
        },
        "v_type": "person"
      }
    ]},
    {"employees": [
      {
        "v_id": "person4",
        "attributes": {
          "interestList": ["football"],
          "skillSet": [ 10, 1, 4 ],
          "skillList": [ 4, 1, 10 ],
          "locationId": "us",
          "interestSet": ["football"],
          "id": "person4"
        },
        "v_type": "person"
      },
      {
        "v_id": "person9",
        "attributes": {
          "interestList": [ "financial", "teaching" ],
          "skillSet": [ 2, 7, 4 ],
          "skillList": [ 4, 7, 2 ],
          "locationId": "us",
          "interestSet": [ "teaching", "financial" ],
          "id": "person9"
        },
        "v_type": "person"
      },
      {
        "v_id": "person7",
        "attributes": {
          "interestList": [ "art", "sport" ],
          "skillSet": [ 6, 8 ],
          "skillList": [ 8, 6 ],
          "locationId": "us",
          "interestSet": [ "sport", "art" ],
          "id": "person7"
        },
        "v_type": "person"
      },
      {
        "v_id": "person1",
        "attributes": {
          "interestList": [ "management", "financial" ],
          "skillSet": [ 3, 2, 1 ],
          "skillList": [ 1, 2, 3 ],
          "locationId": "us",
          "interestSet": [ "financial", "management" ],
          "id": "person1"
        },
        "v_type": "person"
      }
    ]},
    {"employees": [
      {
        "v_id": "person10",
        "attributes": {
          "interestList": [
            "football",
            "sport"
          ],
          "skillSet": [3],
          "skillList": [3],
          "locationId": "us",
          "interestSet": [ "sport", "football" ],
          "id": "person10"
        },
        "v_type": "person"
      },
      {
        "v_id": "person1",
        "attributes": {
          "interestList": [ "management", "financial" ],
          "skillSet": [ 3, 2, 1 ],
          "skillList": [ 1, 2, 3 ],
          "locationId": "us",
          "interestSet": [ "financial", "management" ],
          "id": "person1"
        },
        "v_type": "person"
      }
    ]}
  ]
}
Example 3 - Nested FOREACH with MapAccum
# Count the number of employees from a given country and list their ids
CREATE QUERY employeeByCountry(STRING countryName) FOR GRAPH workNet {
  MapAccum <STRING, ListAccum<STRING>> @@employees;

  # start will have a set of all person type vertices
  start = {person.*};

  # Build a map using person locationId as a key and a list of strings to hold multiple person ids
  s = SELECT v FROM start:v
      ACCUM @@employees += (v.locationId -> v.id);

  # Iterate the map using (key,value) pairs
  FOREACH (key,val) in @@employees DO
    IF key == countryName THEN
      PRINT val.size();

      # Nested foreach to iterate over the list of person ids
      FOREACH employee in val DO
        PRINT employee;
      END;

      # MapAccum keys are unique so we can BREAK out of the loop
      BREAK;
    END;
  END;
}
employeeByCountry Results
GSQL > RUN QUERY employeeByCountry("us")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [
    {"val.size()": 5},
    {"employee": "person4"},
    {"employee": "person10"},
    {"employee": "person7"},
    {"employee": "person1"},
    {"employee": "person9"}
  ]
}

DML-sub FOREACH Examples

ACCUM FOREACH
# Show post topics liked by users and show total likes per topic
CREATE QUERY topicLikes() FOR GRAPH socialNet {
 SetAccum<STRING> @@personPosts;
 SumAccum<INT> @postLikes;
 MapAccum<STRING,INT> @@likesByTopic;


  start = {person.*};

  # Find all user posts and generate a set of post topics
  # (set has no duplicates)
  posts = SELECT g FROM start - (posted) -> :g
          ACCUM @@personPosts += g.subject;

  # Use set of topics to increment how many times a specfic
  #  post is liked by other users
  likedPosts = SELECT f FROM start - (liked) -> :f
               ACCUM FOREACH x in @@personPosts DO
                         CASE WHEN (f.subject == x) THEN
                           f.@postLikes += 1
                         END
                     END
               # Aggregate all liked totals by topic
               POST-ACCUM @@likesByTopic += (f.subject -> f.@postLikes);

  # Display the number of likes per topic
  PRINT @@likesByTopic;
}
Results for Query topicLikes
GSQL > RUN QUERY topicLikes()
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"@@likesByTopic": {
    "cats": 3,
    "coffee": 2,
    "Graphs": 3,
    "tigergraph": 1
  }}]
}
Example 1 - POST-ACCUM FOREACH
#Show a summary of the number of friends all persons have by gender
CREATE QUERY friendGender() FOR GRAPH socialNet {
  ListAccum<STRING> @friendGender;
  SumAccum<INT> @@maleGenderCount;
  SumAccum<INT> @@femaleGenderCount;

  start = {person.*};

  # Record a list showing each friend's gender
  socialMembers = SELECT s from start:s -(friend)-> :g
              ACCUM s.@friendGender += (g.gender)

              # Loop over each list of genders and total them
              POST-ACCUM FOREACH x in s.@friendGender DO
	                       CASE WHEN (x == "Male") THEN
	                         @@maleGenderCount += 1
	                       ELSE
	                         @@femaleGenderCount += 1
	                       END
                         END;

  PRINT @@maleGenderCount;
  PRINT @@femaleGenderCount;
}
Results for Query friendGender
GSQL > RUN QUERY friendGender()
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [
    {"@@maleGenderCount": 11},
    {"@@femaleGenderCount": 7}
  ]
}

CONTINUE and BREAK Statements

The CONTINUE and BREAK statements can only be used within a block of a WHILE or FOREACH statement. The CONTINUE statement branches control flow to the end of the loop, skipping any remaining statements in the current iteration, and proceeding to the next iteration. That is, everything in the loop block after the CONTINUE statement will be skipped, and then the loop will continue as normal.

The BREAK statement branches control flow out of the loop, i.e., it will exit the loop and stop iteration.

Below are a number of examples that demonstrate the use of BREAK and CONTINUE.

Continue and Break Semantics
# While with a continue
INT i = 0;
INT nCount = 0;
WHILE i < 10 DO
  i = i + 1;
  IF (i % 2 == 0) { CONTINUE; }
  nCount = nCount + 1;
END;
# i is 10, nCount is 5 (skips the increment for every even i).

# While with a break
i = 0;
WHILE i < 10 DO
  IF (i == 5) { BREAK; }  # When i is 5 the loop is exited
  i = i + 1;
END;
# i is now 5
Example 1. Break
# find posts of a given person, and post of friends of that person, friends of friends, etc
# until a post about cats is found. The number of friend-hops to reach is the 'degree' of cats
CREATE QUERY findDegreeOfCats(vertex<person> seed) FOR GRAPH socialNet
{
    SumAccum<INT> @@degree = 0;
    OrAccum @@foundCatPost = false;
    OrAccum @visited = false;

    friends (ANY) = {seed};
    WHILE @@foundCatPost != true AND friends.size() > 0 DO
          posts = SELECT v FROM friends-(posted:e)->:v
                  ACCUM CASE WHEN v.subject == "cats" THEN @@foundCatPost += true END;

          IF @@foundCatPost THEN
            BREAK;
          END;

          friends = SELECT v FROM friends-(friend:e)->:v
                  WHERE v.@visited == false
                  ACCUM v.@visited = true;
          @@degree += 1;
    END;
    PRINT @@degree;
}
Results of Query findDegreeOfCats
GSQL > RUN QUERY findDegreeOfCats("person2")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"@@degree": 2}]
}
GSQL > RUN QUERY findDegreeOfCats("person4")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"@@degree": 0}]
}
Example 2. findEnoughFriends.gsql: While loop using continue statement
# find all 3-hop friends of a starting vertex. count coworkers as friends
# if there are not enough friends
CREATE QUERY findEnoughFriends(vertex<person> seed) FOR GRAPH friendNet
{
    SumAccum<INT> @@distance = 0;   # keep track of the distance from the seed
    OrAccum @visited = false;
    visitedVertices = {seed};
    WHILE true LIMIT 3 DO
        @@distance += 1;
        # traverse from visitedVertices to its friends
        friends = SELECT v
            FROM visitedVertices -(friend:e)-> :v
            WHERE v.@visited == false
            POST-ACCUM v.@visited = true;
        PRINT @@distance, friends;

        # if number of friends at this level is sufficient, finish this iteration
        IF visitedVertices.size() >= 2 THEN
            visitedVertices = friends;
            CONTINUE;
        END;
        # if fewer than 4 friends, add in coworkers
        coworkers = SELECT v
            FROM visitedVertices -(coworker:e)-> :v
            WHERE v.@visited == false
            POST-ACCUM v.@visited = true;
        visitedVertices = friends UNION coworkers;
        PRINT @@distance, coworkers;
    END;
}
findEnoughFriends.json Example 2 Results
GSQL > RUN QUERY findEnoughFriends("person1")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [
    {
      "@@distance": 1,
      "friends": [
        {
          "v_id": "person4",
          "attributes": {
            "@visited": true,
            "id": "person4"
          },
          "v_type": "person"
        },
        {
          "v_id": "person2",
          "attributes": {
            "@visited": true,
            "id": "person2"
          },
          "v_type": "person"
        },
        {
          "v_id": "person3",
          "attributes": {
            "@visited": true,
            "id": "person3"
          },
          "v_type": "person"
        }
      ]
    },
    {
      "coworkers": [
        {
          "v_id": "person5",
          "attributes": {
            "@visited": true,
            "id": "person5"
          },
          "v_type": "person"
        },
        {
          "v_id": "person6",
          "attributes": {
            "@visited": true,
            "id": "person6"
          },
          "v_type": "person"
        }
      ],
      "@@distance": 1
    },
    {
      "@@distance": 2,
      "friends": [
        {
          "v_id": "person9",
          "attributes": {
            "@visited": true,
            "id": "person9"
          },
          "v_type": "person"
        },
        {
          "v_id": "person1",
          "attributes": {
            "@visited": true,
            "id": "person1"
          },
          "v_type": "person"
        },
        {
          "v_id": "person8",
          "attributes": {
            "@visited": true,
            "id": "person8"
          },
          "v_type": "person"
        }
      ]
    },
    {
      "@@distance": 3,
      "friends": [
        {
          "v_id": "person12",
          "attributes": {
            "@visited": true,
            "id": "person12"
          },
          "v_type": "person"
        },
        {
          "v_id": "person10",
          "attributes": {
            "@visited": true,
            "id": "person10"
          },
          "v_type": "person"
        },
        {
          "v_id": "person7",
          "attributes": {
            "@visited": true,
            "id": "person7"
          },
          "v_type": "person"
        }
      ]
    }
  ]
}
Example 3. While loop using break statement
# find at least the top-k companies closest to a given seed vertex, if they exist
CREATE QUERY topkCompanies(vertex<person> seed, INT k) FOR GRAPH workNet
{
    SetAccum<vertex<company>> @@companyList;
    OrAccum @visited = false;
    visitedVertices (ANY) = {seed};
    WHILE true DO
        visitedVertices = SELECT v                  # traverse from x to its unvisited neighbors
                FROM visitedVertices -(:e)-> :v
                WHERE v.@visited == false
                ACCUM CASE
                    WHEN (v.type == "company") THEN # count the number of company vertices encountered
                        @@companyList += v
                    END
                POST-ACCUM v.@visited += true;      # mark vertices as visited

        # exit loop when at least k companies have been counted
        IF @@companyList.size() >= k OR visitedVertices.size() == 0 THEN
           BREAK;
        END;
    END;
    PRINT @@companyList;
}
Example 3. topkCompanies Results
GSQL > RUN QUERY topkCompanies("person1", 2)
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"@@companyList": [
    "company2",
    "company1"
  ]}]
}
GSQL > RUN QUERY topkCompanies("person2", 3)
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [{"@@companyList": [
    "company3",
    "company2",
    "company1"
  ]}]
}
Example 4 - Usage of CONTINUE in FOREACH
#List out all companies from a given country
CREATE QUERY companyByCountry(STRING countryName) FOR GRAPH workNet {
  MapAccum <STRING, ListAccum<STRING>> @@companies;
  start = {company.*};                   # start will have a set of all company type vertices

  #Build a map using company country as a key and a list of strings to hold multiple company ids
  s = SELECT v FROM start:v
      ACCUM @@companies += (v.country -> v.id);

  #Iterate the map using (key,value) pairs
  FOREACH (key,val) IN @@companies DO
    IF key != countryName THEN
      CONTINUE;
    END;

    PRINT val.size();

    #Nested foreach to iterate over the list of company ids
    FOREACH comp IN val DO
      PRINT comp;
    END;
  END;
}
companyByCountry Results
GSQL > RUN QUERY companyByCountry("us")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [
    {"val.size()": 2},
    {"comp": "company1"},
    {"comp": "company4"}
  ]
}
Example 5 - Usage of BREAK in FOREACH
#List all the persons located in the specified country
CREATE QUERY employmentByCountry(STRING countryName) FOR GRAPH workNet {
  MapAccum < STRING, ListAccum<STRING> > @@employees;
  start = {person.*};                   # start will have a set of all person type vertices

  #Build a map using person locationId as a key and a list of strings to hold multiple person ids
  s = SELECT v FROM start:v
      ACCUM @@employees += (v.locationId -> v.id);

  #Iterate the map using (key,value) pairs
  FOREACH (key,val) IN @@employees DO
    IF key == countryName THEN
      PRINT val.size();

      #Nested foreach to iterate over the list of person ids
      FOREACH employee IN val DO
        PRINT employee;
      END;

      BREAK;
    END;
  END;
}
employmentByCountry Result
GSQL > RUN QUERY employmentByCountry("us")
{
  "error": false,
  "message": "",
  "version": {
    "edition": "developer",
    "schema": 0,
    "api": "v2"
  },
  "results": [
    {"val.size()": 5},
    {"employee": "person1"},
    {"employee": "person4"},
    {"employee": "person7"},
    {"employee": "person9"},
    {"employee": "person10"}
  ]
}