控制流语句
GSQL图形查询语言中包括了一系列功能全面的控制流语句,用户可以利用它们进行复杂的图遍历和数据计算,它们包括了:IF/ELSE,CASE,WHILE和FOREACH语句。
块语法(Block Syntax)的差异
请注意,这些语句中的任何一个都可以用于查询主体层语句或DML子句。
如果控制流语句位于查询的主体层,则其语句块是查询主体层语句(queryBodyStmts)。 在查询主体层语句中,每个单独的语句以分号结束,语句块末尾也是分号。
如果控制流语句处于DML子句层,则该语句块为DML子句(DMLSubStmtList)。 在DML子句中,语句之间由逗号分隔,但最后的末尾没有标点符号。
有关查询主体层语句和DML子句之间差异,详见“创建/安装/执行/显示/删除查询”一章中的“语句类型”小节。
IF语句
IF语句根据设定的条件产生逻辑分支:仅当给定条件为真时才执行语句块(查询主体层语句或DML子句)。 IF语句允许零个或多个ELSE-IF子句,后跟可选的ELSE子句。 IF语句可以在查询主体层语句或DML子句使用。 (请参阅有关块语法差异的说明。)
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的条件不为真,则跳过该条件的语句而检测下一个ELSE IF条件。 直到遇到条件为真时,执行相应的语句块,然后IF语句结束(即跳过任何剩余的ELSE-IF或ELSE子句)。 当存在ELSE子句时,则如果前述所有条件均不为真,则执行该ELSE语句块。 总的来说,功能可以概括为“执行条件测试为真的第一个语句块”。
# 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.
# 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;
}
GSQL > RUN QUERY countFriendsOf2("person2", true)
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{
"@@numFriends": 5,
"includeCoworkers": true
}]
}
GSQL > RUN QUERY countFriendsOf2("person2", false)
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{
"@@numFriends": 2,
"includeCoworkers": false
}]
}
# 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;
}
GSQL > RUN QUERY calculateActivity("person1")
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"Not very active": "Not very active"}]
}
GSQL > RUN QUERY calculateActivity("person5")
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"Semi-active": "Semi-active"}]
}
# 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;
}
GSQL > RUN QUERY calculateInDepthActivity("person1")
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"Semi-active": "Semi-active"}]
}
# 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;
}
GSQL > RUN QUERY ratePosts2()
{
"error": false,
"message": "",
"version": {
"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语句
CASE语句根据条件设定产生逻辑分支:仅当给定条件为真时才执行语句块。 CASE语句可以用作查询主体层语句或DML子句。(请参阅有关块语法差异的说明。)
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
一个CASE语句包含一个或多个WHEN-THEN子句,每个子句都是一个表达式。 CASE语句也可以包含一个ELSE子句,当之前的条件都不为真时,则执行该ELSE子语句。
CASE语句有两种语法:其中的一种语法等效于if-else语句,而另一种则类似于SWITCH语句。对于等效于IF-ELSE语句来说,系统会计算每个WHEN子句中的布尔条件,并执行条件为真的第一个语句块。仅当所有WHEN子句条件均为假时,才会执行末尾的ELSE子句(如果有)。
另一种类似于SWITCH语句的语法的运算逻辑则如下:计算关键字WHEN之后的表达式,并将其结果与紧跟在关键字CASE之后的表达式的值进行比较。这些值并不需要一定是布尔类型; CASE语句会比较它们,并判断它们是否相等。系统会执行第一个判断结果相等的WHEN-THEN子句;剩下的子句则被跳过。仅当没有WHEN子句结果与CASE的输出值相等的时候,系统才会执行可选的ELSE子句。
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
# 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;
}
GSQL > RUN QUERY userNetworkPosts("person1", "Graphs")
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"@@topicSum": 3}]
}
例 2.与switch等效的CASE语句的示例
# 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;
}
# 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;
}
GSQL > RUN QUERY countGenderOfFriends("person4")
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{
"@@males": 2,
"@@unknown": 0,
"@@females": 1
}]
}
# 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
}
GSQL > RUN QUERY scoreSocialImpact()
{
"error": false,
"message": "",
"version": {
"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"
}
]}]
}
# 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
}
GSQL > RUN QUERY ratePosts()
{
"error": false,
"message": "",
"version": {
"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语句
WHILE语句提供无限次的迭代功能。 WHILE语句可以用作查询主体层语句或DML子句。 (请参阅有关块语法差异的说明。)
queryBodyWhileStmt := WHILE condition [LIMIT (name | integer)] DO queryBodyStmts END
DMLSubWhileStmt := WHILE condition [LIMIT (name | integer)] DO DMLSubStmtList END
WHILE语句反复运行其代码(查询主体层语句或DML子句),直到条件判断结果为假或直到满足迭代次数的上限为止。 其条件设定可以是任何输出值为布尔值的运算,每次迭代之前需要计算条件是否为真。 CONTINUE
子句可用于改变WHILE语句中的控制流。 BREAK
子句可用于退出WHILE循环。
用户也可以选择WHILE语句中添加LIMIT子句。 由于LIMIT子句输出一个正整数或整数变量,所以可以用其约束循环迭代的最大次数。 下面的示例演示了添加LIMIT子句的使用方法。
如果不指定限制值,则WHILE循环会无限次迭代。 编写查询的人必须设计合理的条件逻辑,以确保循环拥有正确的退出机制(或设置循环次数限制)。
# 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;
下面是一些WHILE语句的使用示例。
# 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;
}
GSQL > RUN QUERY reachable("person1")
{
"error": false,
"message": "",
"version": {
"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"
}
]}]
}
# 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;
}
GSQL > RUN QUERY reachableWithinTwo("person1")
{
"error": false,
"message": "",
"version": {
"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语句
FOREACH语句用于执行带有条件约束的迭代循环。 FOREACH语句可以用于查询主体层语句或DML子句。 (请参阅有关块语法差异的说明。)
queryBodyForEachStmt := FOREACH forEachControl DO queryBodyStmts END
DMLSubForEachStmt := FOREACH forEachControl DO DMLSubStmtList END
forEachControl := (name | "(" name [, name]+ ")") IN setBagExpr
| name IN RANGE "[" expr, expr"]" [".STEP(" expr ")"]
forEachControl的正式语法看起来很复杂,它可以分为以下几种情况:
name IN setBagExpr
tuple IN setBagExpr
name IN RANGE [expr,expr]
name IN RANGE [expr,expr] .STEP(expr)
请注意,setBagExpr包括了累加器和集合。
FOREACH语句具有以下限制:
在DML子句中的FOREACH语句中,永远不允许更新循环变量值(即在IN之前声明的变量,例如,“FOREACH var INsetBagExpr”中的var)。
在查询主体层FOREACH语句中,在大多数情况下,不允许更新循环变量。 但以下情况除外:
如果在ListAccum累加器上迭代,则可以更新其值。
如果在MapAccum累加器上迭代,则可以更新其值,但不能更新其键的键值。
如果迭代位于一组顶点之上,则不允许访问(读取或写入)附属于顶点之上的累加器。
在查询主体层语句中的FOREACH语句不允许循环迭代一个常量集合(包括Set或Bag)。 例如,不支持FOREACH i in (1,2,3)这样的操作。 但是,在DML子句上,这样的操作是允许的。
FOREACH ... IN RANGE的语句
用户可以选择在FOREACH语句中添加一个RANGE子句,格式为RANGE [expr,expr];用户可以用它定义迭代集合的范围。用户还能选定迭代的步进长度以控制范围。
RANGE [expr,expr] .STEP(expr)
每个expr的运算结果必须为整数。 可以是负整数,但Step expr 可能不为0。
RANGE [a,b] .STEP(c)子句会生成一个从a到b的整数序列,包括步长c。 即如下所示:
(a,a + c,a + 2 * c,a + 3 * c,... a + k * c)
,其中k =最大整数,使得| k * c | ≤| b-a |。
如果未给出.STEP参数,则步长c 默认为1。
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;
}
GSQL > RUN QUERY foreachRangeEx()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"@@t": [ 0, 0, 1, 0, 1, 2, 0, 1, 2 ]}]
}
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;
}
步长值为正表示升序,为负表示降序。 如果循环的计数方向设置错误,则该循环不会进行,因为一开始循环的退出条件就已经满足了。
GSQL > RUN QUERY foreachRangeStep(100,0,-9)
{
"error": false,
"message": "",
"version": {
"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": {
"schema": 0,
"api": "v2"
},
"results": [{"@@t": []}]
}
查询主体级(Query-body-level)语句中的FOREACH示例
# 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;
}
GSQL > RUN QUERY companyCount("us")
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"countryCount": 2}]
}
GSQL > RUN QUERY companyCount("can")
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"countryCount": 1}]
}
#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;
}
GSQL > RUN QUERY employeesByCompany("us")
{
"error": false,
"message": "",
"version": {
"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"
}
]}
]
}
# 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;
}
GSQL > RUN QUERY employeeByCountry("us")
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"val.size()": 5},
{"employee": "person4"},
{"employee": "person10"},
{"employee": "person7"},
{"employee": "person1"},
{"employee": "person9"}
]
}
DML子句中的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;
}
GSQL > RUN QUERY topicLikes()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"@@likesByTopic": {
"cats": 3,
"coffee": 2,
"Graphs": 3,
"tigergraph": 1
}}]
}
#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;
}
GSQL > RUN QUERY friendGender()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"@@maleGenderCount": 11},
{"@@femaleGenderCount": 7}
]
}
CONTINUE子句和BREAK子句
CONTINUE子句和BREAK子句只能在WHILE或FOREACH语句块中使用。 CONTINUE子句跳过当前循环中的剩余的所有语句,直接前往循环的末尾,然后继续下一次循环。 也就是说,运算过程会跳过CONTINUE子句后的所有内容,但循环仍然继续正常进行。
BREAK子句使得运算过程退出循环,也就是说,执行过程会退出当前的循环并停止迭代。
以下是一些演示BREAK和CONTINUE使用方法的示例。
# 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
# 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;
}
GSQL > RUN QUERY findDegreeOfCats("person2")
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"@@degree": 2}]
}
GSQL > RUN QUERY findDegreeOfCats("person4")
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"@@degree": 0}]
}
# 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;
}
GSQL > RUN QUERY findEnoughFriends("person1")
{
"error": false,
"message": "",
"version": {
"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"
}
]
}
]
}
# 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;
}
GSQL > RUN QUERY topkCompanies("person1", 2)
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"@@companyList": [
"company2",
"company1"
]}]
}
GSQL > RUN QUERY topkCompanies("person2", 3)
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"@@companyList": [
"company3",
"company2",
"company1"
]}]
}
#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;
}
GSQL > RUN QUERY companyByCountry("us")
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"val.size()": 2},
{"comp": "company1"},
{"comp": "company4"}
]
}
#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;
}
GSQL > RUN QUERY employmentByCountry("us")
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"val.size()": 5},
{"employee": "person1"},
{"employee": "person4"},
{"employee": "person7"},
{"employee": "person9"},
{"employee": "person10"}
]
}
Last updated