累加器是一种特殊的变量类型,它可以在遍历和探索图形的过程中累积有关图形的信息。 因为它是GSQL图形查询语言中非常独特且重要的特性,所以我们为它开辟了专门的章节。但更多详细的使用方法也会被整合到其他的章节中,比如有关“SELECT语句”的部分。 本章的介绍包括了以下这些定义的EBNF范式:
Copy ccumDeclStmt := accumType "@"name ["=" constant][, "@"name ["=" constant]]*
| "@"name ["=" constant][, "@"name ["=" constant]]* accumType
| [STATIC] accumType "@@"name ["=" constant][, "@@"name ["=" constant]]*
| [STATIC] "@@"name ["=" constant][, "@@"name ["=" constant]]* accumType
accumType := "SumAccum" "<" ( INT | FLOAT | DOUBLE | STRING | STRING COMPRESS) ">"
| "MaxAccum" "<" ( INT | FLOAT | DOUBLE ) ">"
| "MinAccum" "<" ( INT | FLOAT | DOUBLE ) ">"
| "AvgAccum"
| "OrAccum"
| "AndAccum"
| "BitwiseOrAccum"
| "BitwiseAndAccum"
| "ListAccum" "<" type ">"
| "SetAccum" "<" elementType ">"
| "BagAccum" "<" elementType ">"
| "MapAccum" "<" elementType "," type ">"
| "HeapAccum" "<" name ">" "(" (integer | name) "," name [ASC | DESC]["," name [ASC | DESC]]* ")"
| "GroupByAccum" "<" elementType name ["," elementType name]*, accumType name ["," accumType name]* ">"
| "ArrayAccum" "<" name ">"
elementType := baseType | name | STRING COMPRESS
gAccumAccumStmt := "@@"name "+=" expr
accumClause := ACCUM DMLSubStmtList
postAccumClause := POST-ACCUM DMLSubStmtList
累加器有许多不同的类型,每个累加器提供特定的累加功能。 一个累加器可以被声明为一种类型,也可以是这两种类型的综合:全局(global)或附属于顶点(vertex-attached)。
从技术上讲,累加器是一种可变互斥变量,并在所有图形计算线程之间共享,探索指定的图形。 为了提高性能,图形处理引擎采用多线程方式运作。 针对累加器的修改是实时协调的,因此才能让累加运算符可以在所有线程上正确地(即相互排他)工作。 这个特性在ACCUM子句中尤为重要。 在遍历图形期间,所有被选中的边或顶点会被分配给一组线程。 这些线程共享了对累加器的互斥访问。
累加器的声明
所有累加器变量必须在查询开始时声明,紧跟在任何typedef之后,并位于其他任何类型的语句之前。 累加器变量的作用域覆盖整个查询。
附属于顶点的累加器(vertex-attached accumulator)以 “@”开头。 全局累加器的名称以“@@”开头。 另外,用户可以将全局累加器声明为静态。
Copy accumDeclStmt := accumType "@"name ["=" constant][, "@"name ["=" constant]]*
| "@"name ["=" constant][, "@"name ["=" constant]]* accumType
| [STATIC] accumType "@@"name ["=" constant][, "@@"name ["=" constant]]*
| [STATIC] "@@"name ["=" constant][, "@@"name ["=" constant]]* accumType
附属于顶点的累加器
附属于顶点的累加器是一种状态可变的变量。它们在查询的整个持续时间内被附着到图中的每个顶点上,成为顶点在查询运行时的一个属性。 它们在所有查询进程中共享,并相互排斥。 用户可以使用等号(“=”)运算符为附属于顶点的累加器赋值。另外,累加运算符“+=”可用于更新累加器的状态; “+=”的具体效果取决于累加器类型。 在下面的示例中,每个顶点都有两个累加器。 对于某个给定类型的累加器,它的初始值是预定义好的,当然,用户也可以在声明中对其进行更改,例如下面的累加器@weight。 所有附属于顶点的累加器的名称都有一个前导符号“@”。
Copy SumAccum<int> @neighbors;
MaxAccum<float> @weight = 2.8;
如果有一个包含10个顶点的图形,且每个顶点都有一个@neighbors和@weight累加器实例(即总共20个累加器实例)。 访问它们需要用到点运算符(“.”),例如,v.@neighbor)。 累加器运算符+=仅影响正在被引用的顶点上的累加器。 诸如v1.@neighbors +=1这样的语句只会影响v1的@neighbors累加器而不影响其他顶点的@neighbors累加器。
一般来说,用户只能在SELECT语句中的ACCUM或POST-ACCUM子句中访问或更新(通过=或+=操作)附属于顶点的累加器。唯一的例外是PRINT语句。用户也可以在PRINT语句中引用附属于顶点的累加器,因为PRINT可以访问附加到顶点集和的所有信息。
全局累加器
全局累加器是一种可变的累加器,可以在查询中访问或更新。 全局累加器的名称以双重符号“@@”开头。
Copy SumAccum<int> @@totalNeighbors;
MaxAccum<float> @@entropy = 1.0;
全局累加器只能在SELECT语句之外使用=运算符赋值(即,不在ACCUM或POST-ACCUM子句中)。但用户可以通过累积运算符+=在查询中的任何位置访问或更新全局累加器,包括在SELECT语句内。
值得注意的是,ACCUM子句中的全局累加器的累加操作对每个进程都要执行一次。也就是说,如果FROM子句使用边引发的选择操作(edge-induced selection)(在“SELECT语句”一节中详述),则ACCUM子句将为选中的每条边执行一个进程。如果FROM子句使用顶点引发的选择操作(vertex-induced selection)(在“SELECT语句”一节中详述),则ACCUM子句将为选中的每个顶点执行一个进程。由于全局累加器在进程之间以互斥的方式共享,因此它的行为与非累加器变量的行为有着巨大的不同(请参阅“变量类型”一节以获取更多详细信息)。在以下示例中,全局累加器@@globalRelationshipCount 在进程之间共享,并遍历每条worksFor 边并累积数据。相反,relationshipCount 却只增加了一次,这是因为进程之间不共享非累加器变量导致。每个进程都有自己独立的relationshipCount 非共享副本,并将原始值 增加1。 (例如,每个线程都将自己的relationshipCount 从0增加到1.) 这种情况中没有累积,最终值为1。
Copy #Count the total number of employment relationships for all companies
CREATE QUERY countEmploymentRelationships() FOR GRAPH workNet {
INT localRelationshipCount;
SumAccum<INT> @@globalRelationshipCount;
start = {company.*};
companies = SELECT s FROM start:s -(worksFor)-> :t
ACCUM @@globalRelationshipCount += 1,
localRelationshipCount = localRelationshipCount + 1;
PRINT localRelationshipCount;
PRINT @@globalRelationshipCount;
}
countEmploymentRelationship.json 结果
Copy GSQL > RUN QUERY countEmploymentRelationships()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"localRelationshipCount": 1},
{"@@globalRelationshipCount": 17}
]
}
静态全局累加器
静态全局累加器会在查询运行结束后仍然保留初始值。 要声明静态全局累加器,请在声明语句的开头包含STATIC关键字。 例如,如果一个查询每次执行,都讲将其静态全局累加器增加1,则最终,该累加器的值即等于查询运行的次数。 每个静态全局累加器都属于声明它的那个查询; 它不能在不同的查询之间共享。 其值仅在多次运行同一查询的过程中持续存在。当用户重新启动GPE时,该值将被重置为默认值。
Copy CREATE QUERY staticAccumEx(INT x) FOR GRAPH minimalNet {
STATIC ListAccum<INT> @@testList;
@@testList += x;
PRINT @@testList;
}
Copy GSQL > RUN QUERY staticAccumEx(3)
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"@@testList": [
3,
-5,
3
]}]
}
GSQL > RUN QUERY staticAccumEx(-5)
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"@@testList": [
3,
-5,
3,
-5
]}]
}
没有命令可以删除静态全局累加器。 如果某个静态全局累加器是一个集合累加器,但我们不再需要它了,我们应将其包含的数据清除以将内存占用降到最低。
累加器的类型
以下是我们目前支持的累加器类型。 每种类型的累加器都支持一种或多种数据类型。
Copy accumType := "SumAccum" "<" ( INT | FLOAT | DOUBLE | STRING ) ">"
| "MaxAccum" "<" ( INT | FLOAT | DOUBLE ) ">"
| "MinAccum" "<" ( INT | FLOAT | DOUBLE ) ">"
| "AvgAccum"
| "OrAccum"
| "AndAccum"
| "BitwiseOrAccum"
| "BitwiseAndAccum"
| "ListAccum" "<" type ">"
| "SetAccum" "<" elementType ">"
| "BagAccum" "<" elementType ">"
| "MapAccum" "<" elementType "," type ">"
| "HeapAccum" "<" name ">" "(" (integer | name) "," name [ASC | DESC]["," name [ASC | DESC]]* ")"
| "GroupByAccum" "<" elementType name ["," elementType name]*, accumType name ["," accumType name]* ">"
| "ArrayAccum" "<" name ">"
elementType := baseType | name | STRING COMPRESS
gAccumAccumStmt := "@@"name "+=" expr
累加器主要有两个大类:
标量累加器(Scalar Accumulator)存储一个单独的值:
BitwiseAndAccum, BitwiseOrAccum
集合累加器(Collection Accumulator)存储一系列值:
每个累加器类型的详细信息总结在下表中。 “累加操作”列说明了在执行语句accumName += newVal 时如何更新累加器accumName 。 下表是每个累加器类型的示例。
图Ac1:累加器类型和累加行为描述:
累加操作
(运行accumName+=newVal的结果)
SumAccum<STRINGorSTRINGCOMPRESS>
内部id较大的顶点,是newVal或accumName
内部id较小的顶点,是newVal或accumName
NewVal和accumName中累积的值的双精度平均数
newVal和accumName的AND运算结果值
-1(INT)=64-bitsequenceof1s
0(INT)=64-bitsequenceof0s
一个列表,列表中newVal附加在accumName的末尾。newVal可以是一个单独的值,也可以是一个列表。假设accumName是[2,4,6],然后将accumName+=4,则此时accumName=[2,4,6,4]
SetAccum<type>
(无序的元素集合,但不允许元素重复)
newVal和accumName两个Set的并集。newVal可以是一个单独的数据也可以是一个set或bag。假设accumName是(2,4,6),然后将accumName+=4,则此时accumName=(2,4,6)
BagAccum<type>
(无序的元素集合,且允许元素重复)
newVal和accumName两个Bag的并集。newVal可以是一个单独的数据也可以是一个set或bag。假设accumName是(2,4,6),然后将accumName+=4,则此时accumName=(2,4,4,6)
MapAccum<type,type>
(无序的键值对集合)
向accumName映射添加或更新键值对。假设如果accumName是[("red",3),("green",4),("blue",2)],然后使得accumName+=("black"->5),则此时accumName等于[("red",3),("green",4),("blue",2),("black",5)]
HeapAccum<tuple>(heapSize,sortKey[,sortKey_i]*)
(已经排好序的元组集合)
将newVal插入到accumName堆栈中,并根据在HeapAccum声明中的sortKey(s)大小限制,维持该堆栈的排序。
GroupByAccum<type[,type]*,accumType[,accumType]*>
在accumName中添加或更新一个键值对;详见“按累加器分组”一节
S umAccum类型累加器
SumAccum类型累加器计算并存储数值的累加和或字符串的串联。 SumAccum的输出是单个数字或字符串值。 SumAccum变量仅对INT,UINT,FLOAT,DOUBLE或STRING类型的值进行操作。
用户通过+=运算符更新累加器的状态。 对于INT,FLOAT和DOUBLE类型来说,+=arg执行数字加法,而对于字符串来说,+=arg的意思是将arg添加到SumAccum当前值的末尾。
Copy # SumAccum Example
CREATE QUERY sumAccumEx() FOR GRAPH minimalNet {
SumAccum<INT> @@intAccum;
SumAccum<FLOAT> @@floatAccum;
SumAccum<DOUBLE> @@doubleAccum;
SumAccum<STRING> @@stringAccum;
@@intAccum = 1;
@@intAccum += 1;
@@floatAccum = @@intAccum;
@@floatAccum = @@floatAccum / 3;
@@doubleAccum = @@floatAccum * 8;
@@doubleAccum += -1;
@@stringAccum = "Hello ";
@@stringAccum += "World";
PRINT @@intAccum;
PRINT @@floatAccum;
PRINT @@doubleAccum;
PRI
Copy GSQL > RUN QUERY sumAccumEx()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"@@intAccum": 2},
{"@@floatAccum": 0.66667},
{"@@doubleAccum": 4.33333},
{"@@stringAccum": "Hello World"}
]
}
MinAccum/MaxAccum类型的累加器
MinAccum和MaxAccum累加器计算并存储一系列累加值的最小值或最大值。 MinAccum或MaxAccum的输出为单个数值。MinAccum和MaxAccum变量仅对INT,UINT,FLOAT和DOUBLE,VERTEX(可选择指定顶点类型)的值进行操作。
对于MinAccum来说,+=arg命令会检查当前值是否小于arg,并存储两者中较小的一个。 MaxAccum的逻辑与之类似,区别在于它会检查并存储更大的那个,而不是的较小值。
Copy # MinAccum and MaxAccum Example
CREATE QUERY minMaxAccumEx() FOR GRAPH minimalNet {
MinAccum<INT> @@minAccum;
MaxAccum<FLOAT> @@maxAccum;
@@minAccum += 40;
@@minAccum += 20;
@@minAccum += -10;
@@maxAccum += -1.1;
@@maxAccum += 2.5;
@@maxAccum += 2.8;
PRINT @@minAccum;
PRINT @@maxAccum;
}
Copy GSQL > RUN QUERY minMaxAccumEx()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"@@minAccum": -10},
{"@@maxAccum": 2.8}
]
}
在顶点类上运行的MinAccum和MaxAccum的操作比较特殊。 它们比较的不是顶点id,而是TigerGraph的内部id,它们可能与外部id不同。 比较内部ID要快得多,因此MinAccum/MaxAccum <VERTEX>提供了更高效的比较和选择顶点算法。 这对于需要对顶点进行编号和排序的某些图型算法很有帮助。 例如,以下查询从每个person返回一个post。 返回的顶点的id不一定是按字母表顺序排列最大的。
Copy # Output one random post vertex from each person
CREATE QUERY minMaxAccumVertex() FOR GRAPH socialNet api("v2") {
MaxAccum<VERTEX> @maxVertex;
allUser = {person.*};
allUser = SELECT src
FROM allUser:src -(posted)-> post:tgt
ACCUM src.@maxVertex += tgt
ORDER BY src.id;
PRINT allUser[allUser.@maxVertex]; // api v2
}
minMaxAccuxVertex.json 的结果
Copy GSQL > RUN QUERY minMaxAccumVertex()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"allUser": [
{
"v_id": "person1",
"attributes": {"allUser.@maxVertex": "0"},
"v_type": "person"
},
{
"v_id": "person2",
"attributes": {"allUser.@maxVertex": "1"},
"v_type": "person"
},
{
"v_id": "person3",
"attributes": {"allUser.@maxVertex": "2"},
"v_type": "person"
},
{
"v_id": "person4",
"attributes": {"allUser.@maxVertex": "3"},
"v_type": "person"
},
{
"v_id": "person5",
"attributes": {"allUser.@maxVertex": "11"},
"v_type": "person"
},
{
"v_id": "person6",
"attributes": {"allUser.@maxVertex": "10"},
"v_type": "person"
},
{
"v_id": "person7",
"attributes": {"allUser.@maxVertex": "9"},
"v_type": "person"
},
{
"v_id": "person8",
"attributes": {"allUser.@maxVertex": "7"},
"v_type": "person"
}
]}]
}
AvgAccum类型的累加器
AvgAccum类型的累加器计算并存储一系列数值的累加平均值。 在内部,其状态信息包括所有输入值的总和以及它累积的输入值的数量。 输出是平均值; 使用者无法访问总和值和计数值。 AvgAccum变量的数据类型未被声明; 所有AvgAccum累加器都接受INT,UINT,FLOAT和DOUBLE类型的输入。 输出始终为DOUBLE类型。
+=arg操作将AvgAccum变量更新为所有先前结果和当前参数的均值; =arg操作清除所有先前累积的值,并将其更新为arg,计数为1。
Copy # AvgAccum Example
CREATE QUERY avgAccumEx() FOR GRAPH minimalNet {
AvgAccum @@averageAccum;
@@averageAccum += 10;
@@averageAccum += 5.5; # avg = (10+5.5) / 2.0
@@averageAccum += -1; # avg = (10+5.5-1) / 3.0
PRINT @@averageAccum; # 4.8333...
@@averageAccum = 99; # reset
@@averageAccum += 101; # avg = (99 + 101) / 2
PRINT @@averageAccum; # 100
}
Copy GSQL > RUN QUERY avgAccumEx()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"@@averageAccum": 4.83333},
{"@@averageAccum": 100}
]
}
AndAccum/OrAccum类型的累加器
AndAccum和OrAccum类型的累加器计算并存储一系列布尔运算的累积结果。 AndAccum或OrAccum的输出是单个布尔值(True或False)。 AndAccum和OrAccum变量仅对布尔值起作用,该类型计算并不需要声明数据类型。
对于AndAccum,+=arg将累加器更新为当前布尔值与arg之间的AND运算结果。 OrAccum类似,只是它存储的是OR运算的结果。
Copy # AndAccum and OrAccum Example
CREATE QUERY andOrAccumEx() FOR GRAPH minimalNet {
# T = True
# F = False
AndAccum @@andAccumVar; # (default value = T)
OrAccum @@orAccumVar; # (default value = F)
@@andAccumVar += True; # T and T = T
@@andAccumVar += False; # T and F = F
@@andAccumVar += True; # F and T = F
PRINT @@andAccumVar;
@@orAccumVar += False; # F or F == F
@@orAccumVar += True; # F or T == T
@@orAccumVar += False; # T or F == T
PRINT @@orAccumVar;
}
Copy GSQL > RUN QUERY andOrAccumEx()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"@@andAccumVar": false},
{"@@orAccumVar": true}
]
}
BitwiseAndAccum/BitwiseOrAccum类型的累加器
BitwiseAndAccum和BitwiseOrAccum类型的累加器计算并存储一系列逐位布尔运算的累积结果,并会存储运算得到的位序列。仅INT数据类型包含BitwiseAndAccum和BitwiseOrAccum运算符。 该类型计算不需要声明数据类型。
理解和使用逐位布尔运算需要一些基础知识:假设某个整数存储在base-2中,是一个64位的由0和1组成的序列。 “逐位”表示每个位被视为一个单独的布尔值,1表示true,0表示false。 因此,每个整数等同于一个布尔值序列。 计算两个数字A和B的逐位AND运算意味着计算比特序列C,其中C的第j位(Cj)等于(Aj AND Bj)。
对于BitwiseAndAccum来说,+=arg将累加器更新为当前状态和arg的逐位AND运算结果。 BitwiseOrAccum类似,只是它给出逐位OR运算结果。
逐位运算和负整数
大多数计算机系统使用“2的补码”格式("2's complement" format)表示负整数,其中最高位具有特殊意义。 影响最高位的操作越过正数和负数之间的边界,反之亦然。
BitwiseAndAccum 和 BitwiseOrAccum的例子
Copy # BitwiseAndAccum and BitwiseOrAccum Example
CREATE QUERY bitwiseAccumEx() FOR GRAPH minimalNet {
BitwiseAndAccum @@bwAndAccumVar; # default value = 64-bits of 1 = -1 (INT)
BitwiseOrAccum @@bwOrAccumVar; # default value = 64-bits of 0 = 0 (INT))
# 11110000 = 240
# 00001111 = 15
# 10101010 = 170
# 01010101 = 85
# BitwiseAndAccum
@@bwAndAccumVar += 170; # 11111111 & 10101010 -> 10101010
@@bwAndAccumVar += 85; # 10101010 & 01010101 -> 00000000
PRINT @@bwAndAccumVar; # 0
@@bwAndAccumVar = 15; # reset to 00001111
@@bwAndAccumVar += 85; # 00001111 & 01010101 -> 00000101
PRINT @@bwAndAccumVar; # 5
# BitwiseOrAccum
@@bwOrAccumVar += 170; # 00000000 | 10101010 -> 10101010
@@bwOrAccumVar += 85; # 10101010 | 01010101 -> 11111111 = 255
PRINT @@bwOrAccumVar; # 255
@@bwOrAccumVar = 15; # reset to 00001111
@@bwOrAccumVar += 85; # 00001111 | 01010101 -> 01011111 = 95
PRINT @@bwOrAccumVar; # 95
}
Copy GSQL > RUN QUERY bitwiseAccumEx()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"@@bwAndAccumVar": 0},
{"@@bwAndAccumVar": 5},
{"@@bwOrAccumVar": 255},
{"@@bwOrAccumVar": 95}
]
}
ListAccum类型的累加器
ListAccum类型的累加器维护一个有序元素集合。 ListAccum的输出是按元素添加的顺序列出的一系列值。 元素类型可以是任何基本类,元组或压缩字符串。 此外,ListAccum还可以包含ListAccum的嵌套。 ListAccums的嵌套限制为三层深度。
+=arg操作将arg附加到列表的末尾。 在这种情况下,arg可以是单个元素,也可以是另一个ListAccum。
ListAccum支持两个额外的操作:
@list1 + @list2创建一个新的ListAccum,其中包含@list1的元素,后跟@list2的元素。 两个ListAccums必须具有相同的数据类型。
“+”的定义的更新
不再支持v2.0版本之前的旧版ListAccum“+”运算符(即@list + arg是指向@list的每个成员添加arg)。
@list1 * @list2(仅限STRING数据)生成一个新的字符串列表,该列表包含所有来自列表一元素和列表而元素顺序连接组合而成的字符串。
ListAccum还支持以下类函数。
修改ListAccum(mutator函数)的函数只能在以下条件下使用:
● 全局累加器的Mutator函数只能在查询主体层语句中使用。
● 附属于顶点的累加器的Mutator函数只能在POST-ACCUM子句中使用。
若列表包含/不包含value值,则返回true/false
返回给定的index在列表中的位置。Index由0开始。如果index超出边界(包括任何负数值),则返回该类型的默认值
update(INTindex, T value)
将列表中位于index位置的元素用value赋值。
Copy # ListAccum Example
CREATE QUERY listAccumEx() FOR GRAPH minimalNet {
ListAccum<INT> @@intListAccum;
ListAccum<STRING> @@stringListAccum;
ListAccum<STRING> @@stringMultiplyListAccum;
ListAccum<STRING> @@stringAdditionAccum;
ListAccum<STRING> @@letterListAccum;
ListAccum<ListAccum<STRING>> @@nestedListAccum;
@@intListAccum = [1,3,5];
@@intListAccum += [7,9];
@@intListAccum += 11;
@@intListAccum += 13;
@@intListAccum += 15;
PRINT @@intListAccum;
PRINT @@intListAccum.get(0), @@intListAccum.get(1);
PRINT @@intListAccum.get(8); # Out of bound: default value of int: 0
#Other built-in functions
PRINT @@intListAccum.size();
PRINT @@intListAccum.contains(2);
PRINT @@intListAccum.contains(3);
@@stringListAccum += "Hello";
@@stringListAccum += "World";
PRINT @@stringListAccum; // ["Hello","World"]
@@letterListAccum += "a";
@@letterListAccum += "b";
# ListA + ListB produces a new list equivalent to ListB appended to ListA.
# Ex: [a,b,c] + [d,e,f] => [a,b,c,d,e,f]
@@stringAdditionAccum = @@stringListAccum + @@letterListAccum;
PRINT @@stringAdditionAccum;
#Multiplication produces a list of all list-to-list element combinations (STRING TYPE ONLY)
# Ex: [a,b] * [c,d] = [ac, ad, bc, bd]
@@stringMultiplyListAccum = @@stringListAccum * @@letterListAccum;
PRINT @@stringMultiplyListAccum;
#Two dimensional list (3 dimensions is possible as well)
@@nestedListAccum += [["foo", "bar"], ["Big", "Bang", "Theory"], ["String", "Theory"]];
PRINT @@nestedListAccum;
PRINT @@nestedListAccum.get(0);
PRINT @@nestedListAccum.get(0).get(1);
}
Copy GSQL > RUN QUERY listAccumEx()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [ {"@@intListAccum": [ 1, 3, 5, 7, 9, 11, 13, 15 ]},
{
"@@intListAccum.get(0)": 1,
"@@intListAccum.get(1)": 3
},
{"@@intListAccum.get(8)": 0},
{"@@intListAccum.size()": 8},
{"@@intListAccum.contains(2)": false},
{"@@intListAccum.contains(3)": true},
{"@@stringListAccum": [ "Hello", "World" ]},
{"@@stringAdditionAccum": [ "Hello", "World", "a", "b"]},
{"@@stringMultiplyListAccum": [ "Helloa", "Worlda", "Hellob", "Worldb" ]},
{"@@nestedListAccum": [
[ "foo", "bar" ],
[ "Big", "Bang", "Theory" ],
[ "String", "Theory" ]
]},
{"@@nestedListAccum.get(0)": [ "foo", "bar" ]},
{"@@nestedListAccum.get(0).get(1)": "bar"}
]
}
Copy CREATE QUERY listAccumUpdateEx() FOR GRAPH workNet {
# Global ListAccum
ListAccum<INT> @@intListAccum;
ListAccum<STRING> @@stringListAccum;
ListAccum<BOOL> @@passFail;
@@intListAccum += [0,2,4,6,8];
@@stringListAccum += ["apple","banana","carrot","daikon"];
# Global update at Query-Body Level
@@passFail += @@intListAccum.update(1,-99);
@@passFail += @@intListAccum.update(@@intListAccum.size()-1,40); // last element
@@passFail += @@stringListAccum.update(0,"zero"); // first element
@@passFail += @@stringListAccum.update(4,"four"); // FAIL: out-of-range
PRINT @@intListAccum, @@stringListAccum, @@passFail;
}
istAcccumUpdateEx.json的结果
Copy GSQL > RUN QUERY listAccumUpdateEx()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{
"@@passFail": [ true, true, true, false ],
"@@intListAccum": [ 0, -99, 4, 6, 40 ],
"@@stringListAccum": [ "zero", "banana", "carrot", "daikon" ]
}]
}
例:在一个附属于顶点的ListAccum累加器上更新函数
Copy CREATE QUERY listAccumUpdateEx2(SET<VERTEX<person>> seed) FOR GRAPH workNet api("v2") {
# Each person has an LIST<INT> of skills and a LIST<STRING COMPRESS> of interests.
# This function copies their lists into ListAccums, and then udpates the last
# int with -99 and updates the last string with "fizz".
ListAccum<INT> @intList;
ListAccum<STRING COMPRESS> @stringList;
ListAccum<STRING> @@intFails, @@strFails;
S0 (person) = seed;
S1 = SELECT s
FROM S0:s
ACCUM
s.@intList = s.skillList,
s.@stringList = s.interestList
POST-ACCUM
INT len = s.@intList.size(),
IF NOT s.@intList.update(len-1,-99) THEN
@@intFails += s.id END,
INT len2 = s.@stringList.size(),
IF NOT s.@stringList.update(len2-1,"fizz") THEN
@@strFails += s.id END
;
PRINT S1[S1.skillList, S1.interestList, S1.@intList, S1.@stringList]; // api v2
PRINT @@intFails, @@strFails;
}
Copy GSQL > RUN QUERY listAccumUpdateEx2(["person1","person5"])
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"S1": [
{
"v_id": "person1",
"attributes": {
"S1.@stringList": [ "management","fizz" ],
"S1.interestList": [ "management", "financial"],
"S1.skillList": [ 1, 2, 3 ],
"S1.@intList": [ 1, 2, -99 ]
},
"v_type": "person"
},
{
"v_id": "person5",
"attributes": {
"S1.@stringList": [ "sport", "financial", "fizz" ],
"S1.interestList": [ "sport", "financial", "engineering" ],
"S1.skillList": [ 8, 2, 5 ],
"S1.@intList": [ 8, 2, -99 ]
},
"v_type": "person"
}
]},
{
"@@strFails": [],
"@@intFails": []
}
]
}
SetAccum类型的累加器
SetAccum 类型的累加器维护一系列不重复的元素。 SetAccum的输出的元素列表是没有顺序的。 SetAccum实例可以包含同一种类型的值。 元素类型可以是任何基本类,元组或压缩字符串。
对于SetAccum来说,+=arg会将非重复元素或元素集合添加到该集合中。 如果元素已在集合中,则SetAccum状态保持不变。
SetAccum还可以与三个标准的集合运算符一起使用:并集(UNION),交集(INTERSECT)和集合相减(MINUS)(详见“Set和Bag表达式和运算符”一节)。
SetAccum还支持以下类函数。
修改SetAccum(mutator类型函数)的函数只能在以下条件下使用:
全局累加器的Mutator函数只能在查询主体层语句中使用。
附属于顶点的累加器的Mutator函数只能在POST-ACCUM子句中使用。
若列表包含/不包含value值,则返回true/false
Copy # SetAccum Example
CREATE QUERY setAccumEx() FOR GRAPH minimalNet {
SetAccum<INT> @@intSetAccum;
SetAccum<STRING> @@stringSetAccum;
@@intSetAccum += 5;
@@intSetAccum.clear();
@@intSetAccum += 4;
@@intSetAccum += 11;
@@intSetAccum += 1;
@@intSetAccum += 11; # Sets do not store duplicates
@@intSetAccum += (1,2,3,4); # Can create simple sets this way
PRINT @@intSetAccum;
@@intSetAccum.remove(2);
PRINT @@intSetAccum AS RemovedVal2; # Demostrate remove.
PRINT @@intSetAccum.contains(3);
@@stringSetAccum += "Hello";
@@stringSetAccum += "Hello";
@@stringSetAccum += "There";
@@stringSetAccum += "World";
PRINT @@stringSetAccum;
PRINT @@stringSetAccum.contains("Hello");
PRINT @@stringSetAccum.size();
}
Copy GSQL > RUN QUERY setAccumEx()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [ {"@@intSetAccum": [ 3, 2, 1, 11, 4 ]},
{"@@intSetAccum.contains(3)": true},
{"@@stringSetAccum": [ "World", "There", "Hello" ]},
{"@@stringSetAccum.contains(Hello)": true},
{"@@stringSetAccum.size()": 3}
]
}
BagAccum类型的累加器
BagAccum类型也是一类集合,但允许包含重复的元素。 BagAccum的输出是一系列没有先后顺序的元素。 BagAccum实例可以包含的值可以是同一类型。 其中的元素类型可以是任何基本类,元组或压缩字符串。
对于BagAccum,+=arg操作会在BAG中添加一个元素或一个BAG。
BagAccum也支持+运算符:
@bag1 + @bag2创建一个新的BagAccum,其中包含@bag1的元素和@bag2的元素。 两个BagAccums必须具有相同的数据类型。
BagAccum还支持以下类函数。
用于修改BagAccum的函数(mutator函数)只能在以下条件下使用:
全局累加器的Mutator函数只能在查询主体层语句中使用。
附属于顶点的累加器的Mutator函数只能在POST-ACCUM子句中使用。
全局累加器的Mutator函数只能在查询主体层语句中使用。
附属于顶点的累加器的Mutator函数只能在POST-ACCUM子句中使用。
若Bag包含/不包含value值,则返回true/false
Copy # BagAccum Example
CREATE QUERY bagAccumEx() FOR GRAPH minimalNet {
#Unordered collection
BagAccum<INT> @@intBagAccum;
BagAccum<STRING> @@stringBagAccum;
@@intBagAccum += 5;
@@intBagAccum.clear();
@@intBagAccum += 4;
@@intBagAccum += 11;
@@intBagAccum += 1;
@@intBagAccum += 11; #Bag accums can store duplicates
@@intBagAccum += (1,2,3,4);
PRINT @@intBagAccum;
PRINT @@intBagAccum.size();
PRINT @@intBagAccum.contains(4);
@@stringBagAccum += "Hello";
@@stringBagAccum += "Hello";
@@stringBagAccum += "There";
@@stringBagAccum += "World";
PRINT @@stringBagAccum.contains("Hello");
@@stringBagAccum.remove("Hello"); #Remove one matching element
@@stringBagAccum.removeAll("There"); #Remove all matching elements
PRINT @@stringBagAccum;
}
Copy GSQL > RUN QUERY bagAccumEx()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [ {"@@intBagAccum": [ 2, 3, 1, 1, 11, 11, 4, 4 ]},
{"@@intBagAccum.size()": 8},
{"@@intBagAccum.contains(4)": true},
{"@@stringBagAccum.contains(Hello)": true},
{"@@stringBagAccum": [ "World", "Hello" ]}
]
}
MapAccum类型的累加器
MapAccum类型的累加器是键值对的集合。 MapAccum的输出是一组键和值的配对,其中键是唯一的。
MapAccum的键类型可以是所有的基本类,元组或压缩字符串。如果键类型是VERTEX,则只存储和显示顶点的id。
除了HeapAccum之外,MapAccum值的类型可以是任何的基本类,元组,压缩字符串或任何类型的累加器。
对于MapAccum来说,如果某个键key尚未在MapAccum中使用,则+=(key->val)
操作会向集合中添加该键值元素。如果MapAccum中已包含该键key,则val会被累加到当前值,这其中的具体累加操作取决于val的数据类型。 (字符串将被连接,列表将被追加,数值将被累加,等等)
MapAccum还支持+运算符:
@map1 + @map2会创建一个新的MapAccum,它包含添加到@map1键值对的@map2的键值对。两个MapAccums必须具有相同的数据类型。
MapAccum还支持以下类函数。
用于修改BagAccum的函数(mutator函数)只能在以下条件下使用:
全局累加器的Mutator函数只能在查询主体层语句中使用。
附属于顶点的累加器的Mutator函数只能在POST-ACCUM子句中使用。
若映射包含/不包含key值,则返回true/false
返回关于映射和key键的关联的值。如果映射中不包含key,则返回的值没有被定义
Copy #MapAccum Example
CREATE QUERY mapAccumEx() FOR GRAPH minimalNet {
#Map(Key, Value)
# Keys can be INT or STRING only
MapAccum<STRING, INT> @@intMapAccum;
MapAccum<INT, STRING> @@stringMapAccum;
MapAccum<INT, MapAccum<STRING, STRING>> @@nestedMapAccum;
@@intMapAccum += ("foo" -> 1);
@@intMapAccum.clear();
@@intMapAccum += ("foo" -> 3);
@@intMapAccum += ("bar" -> 2);
@@intMapAccum += ("baz" -> 2);
@@intMapAccum += ("baz" -> 1); #add 1 to existing value
PRINT @@intMapAccum.containsKey("baz");
PRINT @@intMapAccum.get("bar");
PRINT @@intMapAccum.get("root");
@@stringMapAccum += (1 -> "apple");
@@stringMapAccum += (2 -> "pear");
@@stringMapAccum += (3 -> "banana");
@@stringMapAccum += (4 -> "a");
@@stringMapAccum += (4 -> "b"); #append "b" to existing value
@@stringMapAccum += (4 -> "c"); #append "c" to existing value
PRINT @@intMapAccum;
PRINT @@stringMapAccum;
#Checking and getting keys
if @@stringMapAccum.containsKey(1) THEN
PRINT @@stringMapAccum.get(1);
END;
#Map nesting
@@nestedMapAccum += ( 1 -> ("foo" -> "bar") );
@@nestedMapAccum += ( 1 -> ("flip" -> "top") );
@@nestedMapAccum += ( 2 -> ("fizz" -> "pop") );
@@nestedMapAccum += ( 1 -> ("foo" -> "s") );
PRINT @@nestedMapAccum;
if @@nestedMapAccum.containsKey(1) THEN
if @@nestedMapAccum.get(1).containsKey("foo") THEN
PRINT @@nestedMapAccum.get(1).get("foo");
END;
END;
}
Copy GSQL > RUN QUERY mapAccumEx()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"@@intMapAccum.containsKey(baz)": true},
{"@@intMapAccum.get(bar)": 2},
{"@@intMapAccum.get(root)": 0},
{"@@intMapAccum": {
"bar": 2,
"foo": 3,
"baz": 3
}},
{"@@stringMapAccum": {
"1": "apple",
"2": "pear",
"3": "banana",
"4": "abc"
}},
{"@@stringMapAccum.get(1)": "apple"},
{"@@nestedMapAccum": {
"1": {
"foo": "bars",
"flip": "top"
},
"2": {"fizz": "pop"}
}},
{"@@nestedMapAccum.get(1).get(foo)": "bars"}
]
}
ArrayAccum的类型的累加器
ArrayAccum类型是一个累加器数组。 数组是固定长度的元素序列,可以按位置直接访问某个元素。 ArrayAccum具有以下特点:
每个元素都是累加器,而不是基本元素或基本数据类型。 累加器可以是除了HeapAccum,MapAccum和GroupByAccum之外的所有类型。
ArrayAccum实例可以是多维的。 尺寸数量没有限制。
声明ArrayAccum累加器时,实例名称后面应跟着一对括号来对应每个维度。 括号内可以包含一个整数常量来设置数组的大小,但也可以为空。 在这种情况下,必须使用reallocate函数设置大小,然后才能使用ArrayAccum。
Copy ArrayAccum<SetAccum<STRING>> @@names[10];
ArrayAccum<SetAccum<INT>> @@ids[][]; // 2-dimensional, size to be determined
因为ArrayAccum本身的每个元素都是一个累加器,所以运算符=,+=和+可以在两种情况中使用:累加器层和元素层。
元素层级(Element-level )的运算符
如果@A是一个长度为6的ArrayAccum累加器,那么@A [0]和@A [5]分别表示引用它的第一个和最后一个元素。 引用ArrayAccum元素类似于引用对应类型的累加器。 例如,给出以下定义:
Copy ArrayAccum<SumAccum<INT>> @@Sums[3];
ArrayAccum<ListAccum<STRING>> @@Lists[2];
则@@Sums [0],@@Sums [1]和@@Sums [2]各自都表示一个SumAccum <INT>,@@Lists [0]和@@Lists [1]各自都是指ListAccum <STRING>,它们支持那些个累加器和数据类型的所有操作。
Copy @@Sums[1] = 1;
@@Sums[1] += 2; // value is now 3
@@Lists[0] = "cat";
@@Lists[0] += "egory"; // value is now "category"
累加器层的运算符
当将ArrayAccum作为一个整体时看待时,运算符=,+=和+具有特殊含义。此时的 操作会高效率地更新整个ArrayAccum累加器集。 所有ArrayAccums必须具有相同的元素类型。
让等号左边的ArrayAccum等于等号右边的ArrayAccum,两个ArrayAccum必须是同样的元素类型。左边的ArrayAccum会改变它自身的大小和维度,从而符合右边的ArrayAccum。
针对两个ArrayAccum内的元素逐个相加,所以这两个ArrayAccum必须类型相同,大小也相同。结果是一个新的ArrayAccum,大小与之前一致。
@C = @A + @B;
// @A 和@B 必须大小一致
将右边的ArrayAccum按元素逐个累加到左侧。它们必须是同样大小同样类型。
@A += @B;
// @A 和 @B必须大小一致
ArrayAccum还支持以下类函数。
用于修改BagAccum的函数(mutator函数)只能在以下条件下使用:
全局累加器的Mutator函数只能在查询主体层语句中使用。
附属于顶点的累加器的Mutator函数只能在POST-ACCUM子句中使用。
返回(多维)数组中的元素总数。 例如,声明内容为@A [3][4]的ArrayAccum的大小为12。
丢弃先前的ArrayAccum实例并创建一个新的ArrayAccum,其大小如下。 一个N维的ArrayAccum需要N个整数参数。 Reallocate函数不能用于更改维度数目。
Copy CREATE QUERY ArrayAccumElem() FOR GRAPH minimalNet {
ArrayAccum<SumAccum<DOUBLE>> @@aaSumD[2][2]; # 2D Sum Double
ArrayAccum<SumAccum<STRING>> @@aaSumS[2][2]; # 2D Sum String
ArrayAccum<MaxAccum<INT>> @@aaMax[2];
ArrayAccum<MinAccum<UINT>> @@aaMin[2];
ArrayAccum<AvgAccum> @@aaAvg[2];
ArrayAccum<AndAccum<BOOL>> @@aaAnd[2];
ArrayAccum<OrAccum<BOOL>> @@aaOr[2];
ArrayAccum<BitwiseAndAccum> @@aaBitAnd[2];
ArrayAccum<BitwiseOrAccum> @@aaBitOr[2];
ArrayAccum<ListAccum<INT>> @@aaList[2][2]; # 2D List
ArrayAccum<SetAccum<FLOAT>> @@aaSetF[2];
ArrayAccum<BagAccum<DATETIME>> @@aaBagT[2];
## for test data
ListAccum<STRING> @@words;
BOOL toggle = false;
@@words += "1st"; @@words += "2nd"; @@words += "3rd"; @@words += "4th";
# Int: a[0] += 1, 2; a[1] += 3, 4
# Bool: alternate true/false
# Float: a[0] += 1.111, 2.222; a[1] += 3.333, 4.444
# 2D Doub: a[0][0] += 1.111, 2.222; a[0][1] += 5.555, 6.666;
# a[1][0] += 3.333, 4.444; a[0][1] += 7.777, 8.888;
FOREACH i IN RANGE [0,1] DO
FOREACH n IN RANGE [1, 2] DO
toggle = NOT toggle;
@@aaMax[i] += i*2 + n;
@@aaMin[i] += i*2 + n;
@@aaAvg[i] += i*2 + n;
@@aaAnd[i] += toggle;
@@aaOr[i] += toggle;
@@aaBitAnd[i] += i*2 + n;
@@aaBitOr[i] += i*2 + n;
@@aaSetF[i] += (i*2 + n)/0.9;
@@aaBagT[i] += epoch_to_datetime(i*2 + n);
FOREACH j IN RANGE [0,1] DO
@@aaSumD[i][j] += (j*4 + i*2 + n)/0.9;
@@aaSumS[i][j] += @@words.get((j*2 + i + n)%4);
@@aaList[i][j] += j*4 +i*2 + n ;
END;
END;
END;
PRINT @@aaSumD;PRINT @@aaSumS;
PRINT @@aaMax;PRINT @@aaMin;PRINT @@aaAvg;
PRINT @@aaAnd;PRINT @@aaOr;
PRINT @@aaBitAnd;PRINT @@aaBitOr;
PRINT @@aaList;PRINT @@aaSetF;PRINT @@aaBagT;
}
Copy GSQL > RUN QUERY ArrayAccumElem()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"@@aaSumD": [
[ 3.33333, 12.22222 ],
[ 7.77778, 16.66667 ]
]},
{"@@aaSumS": [
[ "2nd3rd", "4th1st" ],
[ "3rd4th", "1st2nd" ]
]},
{"@@aaMax": [ 2, 4 ]},
{"@@aaMin": [ 1, 3 ]},
{"@@aaAvg": [ 1.5, 3.5 ]},
{"@@aaAnd": [ false, false ]},
{"@@aaOr": [ true, true ]},
{"@@aaBitAnd": [ 0, 0 ]},
{"@@aaBitOr": [ 3, 7]},
{"@@aaList": [
[
[ 1, 2 ],
[ 5, 6]
],
[
[ 3, 4 ],
[ 7, 8 ]
]
]},
{"@@aaSetF": [
[ 2.22222, 1.11111],
[ 4.44444, 3.33333 ]
]},
{"@@aaBagT": [
[ 2, 1 ],
[ 4, 3 ]
]}
]
}
Copy CREATE QUERY ArrayAccumOp3(INT lenA) FOR GRAPH minimalNet {
ArrayAccum<SumAccum<INT>> @@arrayA[5]; // Original size
ArrayAccum<SumAccum<INT>> @@arrayB[2];
ArrayAccum<SumAccum<INT>> @@arrayC[][]; // No size
STRING msg;
@@arrayA.reallocate(lenA); # Set/Change size dynamically
@@arrayB.reallocate(lenA+1);
@@arrayC.reallocate(lenA, lenA+1);
// Initialize arrays
FOREACH i IN RANGE[0,lenA-1] DO
@@arrayA[i] += i*i;
FOREACH j IN RANGE[0,lenA] DO
@@arrayC[i][j] += j*10 + i;
END;
END;
FOREACH i IN RANGE[0,lenA] DO
@@arrayB[i] += 100-i;
END;
msg = "Initial Values";
PRINT msg, @@arrayA, @@arrayB, @@arrayC;
msg = "Test 1: A = C, C = B";// = operator
@@arrayA = @@arrayC;// change dimensions: 1D <- 2D
@@arrayC = @@arrayB;// change dimensions: 2D <- 1D
PRINT msg, @@arrayA, @@arrayC;
msg = "Test 2: B += C"; // += operator
@@arrayB += @@arrayC; // B and C must have same size & dim
PRINT msg, @@arrayB, @@arrayC;
msg = "Test 3: A = B + C"; // + operator
@@arrayA = @@arrayB + @@arrayC; // B & C must have same size & dim
PRINT msg, @@arrayA; // A changes size & dim
}
Copy GSQL > RUN QUERY ArrayAccumOp3(3)
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{
"msg": "Initial Values",
"@@arrayC": [
[ 0, 10, 20, 30 ],
[ 1, 11, 21, 31 ],
[ 2, 12, 22, 32 ]
],
"@@arrayB": [ 100, 99, 98, 97 ],
"@@arrayA": [ 0, 1, 4 ]
},
{
"msg": "Test 1: A = C, C = B",
"@@arrayC": [ 100, 99, 98, 97 ],
"@@arrayA": [
[ 0, 10, 20, 30 ],
[ 1, 11, 21, 31 ],
[ 2, 12, 22, 32 ]
]
},
{
"msg": "Test 2: B += C",
"@@arrayC": [ 100, 99, 98, 97 ],
"@@arrayB": [ 200, 198,196, 194 ]
},
{
"msg": "Test 3: A = B + C",
"@@arrayA": [ 300, 297, 294, 291 ]
}
]
}
Copy CREATE QUERY arrayAccumLocal() FOR GRAPH socialNet api("v2") {
# Count each person's edges by type
# friend/liked/posted edges are type 0/1/2, respectively
ArrayAccum<SumAccum<INT>> @edgesByType[3];
Persons = {person.*};
Persons = SELECT s
FROM Persons:s -(:e)-> :t
ACCUM CASE e.type
WHEN "friend" THEN s.@edgesByType[0] += 1
WHEN "liked" THEN s.@edgesByType[1] += 1
WHEN "posted" THEN s.@edgesByType[2] += 1
END
ORDER BY s.id;
#PRINT Persons.@edgesByType; // api v1
PRINT Persons[Persons.@edgesByType]; // api v2
}
Copy GSQL > RUN QUERY arrayAccumLocal()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [{"Persons": [
{
"v_id": "person1",
"attributes": {"Persons.@edgesByType": [ 2, 1, 1 ]},
"v_type": "person"
},
{
"v_id": "person2",
"attributes": {"Persons.@edgesByType": [ 2, 2, 1 ]},
"v_type": "person"
},
{
"v_id": "person3",
"attributes": {"Persons.@edgesByType": [ 2, 1, 1 ]},
"v_type": "person"
},
{
"v_id": "person4",
"attributes": {"Persons.@edgesByType": [ 3, 1, 1 ]},
"v_type": "person"
},
{
"v_id": "person5",
"attributes": {"Persons.@edgesByType": [ 2, 1, 2 ]},
"v_type": "person"
},
{
"v_id": "person6",
"attributes": {"Persons.@edgesByType": [ 2, 1, 2 ]},
"v_type": "person"
},
{
"v_id": "person7",
"attributes": {"Persons.@edgesByType": [ 2, 1, 2 ]},
"v_type": "person"
},
{
"v_id": "person8",
"attributes": {"Persons.@edgesByType": [ 3, 1, 2 ]},
"v_type": "person"
}
]}]
}
HeapAccum类型的累加器
HeapAccum类型的累加器是元组的有序集合,并维持集合中元组个数不超过最大容量。 HeapAccum的输出是元组元素的有序集合。 +=arg操作按排列的顺序将元组挨个添加到集合中。 如果在应用+=运算符时HeapAccum已处于最大容量,则从HeapAccum中删除排位最后一个的元组。 对于元组的排序基于一个或多个元组字段,可选择按升序或降序排列,排序按照多个元组字段的从左到右进行。
HeapAccum的声明比大多数其他累加器更复杂,因为用户必须提供自定义元组类型,设置HeapAccum的最大容量,并说明如何对HeapAccum进行排序。 该声明的语法如下所示:
Copy TYPEDEF TUPLE<type field_1,.., type field_n> tupleName;
...
HeapAccum<tupleName>(capacity, field_a [ASC|DESC],..., field_z [ASC|DESC]);
首先,HeapAccum声明必须以TYPEDEF语句开头,该语句定义元组的类型,且至少有一个字段(field_1,...,field_n)是可以被排序的数据类型。
在HeapAccum自身的声明中,关键字“HeapAccum”后面是由尖括号引用的元组类型。 随后是两个或多个参数(用括号引用)。 第一个参数是HeapAccum可以存储的最大元组数,且必须是正整数。 后续参数是元组字段的子集,用于对键的排序。键的排序按从左到右顺序进行,最左边的键是主排序键。 关键字ASC和DESC表示升序(最低值优先)或降序(最高值优先)排序。 升序是默认值。
HeapAccum还支持以下类函数。
用于修改BagAccum的函数(mutator函数)只能在以下条件下使用:
全局累加器的Mutator函数只能在查询主体层语句中使用。
附属于顶点的累加器的Mutator函数只能在POST-ACCUM子句中使用。
返回最上层的元组。若该堆栈为空,则返回一个每个元素都等于默认值的元组
返回最上层的元组并将其从堆栈中删除。若该堆栈为空,则返回一个每个元素都等于默认值的元组
Copy #HeapAccum Example
CREATE QUERY heapAccumEx() FOR GRAPH minimalNet {
TYPEDEF tuple<STRING firstName, STRING lastName, INT score> testResults;
#Heap with max size of 4 sorted decending by score then ascending last name
HeapAccum<testResults>(4, score DESC, lastName ASC) @@topTestResults;
PRINT @@topTestResults.top();
@@topTestResults += testResults("Bruce", "Wayne", 80);
@@topTestResults += testResults("Peter", "Parker", 80);
@@topTestResults += testResults("Tony", "Stark", 100);
@@topTestResults += testResults("Bruce", "Banner", 95);
@@topTestResults += testResults("Jean", "Summers", 95);
@@topTestResults += testResults("Clark", "Kent", 80);
#Show element with the highest sorted position
PRINT @@topTestResults.top();
PRINT @@topTestResults.top().firstName, @@topTestResults.top().lastName, @@topTestResults.top().score;
PRINT @@topTestResults;
#Increase the size of the heap to add more elements
@@topTestResults.resize(5);
#Find the size of the current heap
PRINT @@topTestResults.size();
@@topTestResults += testResults("Bruce", "Wayne", 80);
@@topTestResults += testResults("Peter", "Parker", 80);
PRINT @@topTestResults;
#Resizing smaller WILL REMOVE excess elements from the HeapAccum
@@topTestResults.resize(3);
PRINT @@topTestResults;
#Increasing capacity will not restore dropped elements
@@topTestResults.resize(5);
PRINT @@topTestResults;
#Removes all elements from the HeapAccum
@@topTestResults.clear();
PRINT @@topTestResults.size();
}
Copy GSQL > RUN QUERY heapAccumEx()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"@@topTestResults.top()": {
"firstName": "",
"lastName": "",
"score": 0
}},
{"@@topTestResults.top()": {
"firstName": "Tony",
"lastName": "Stark",
"score": 100
}},
{
"@@topTestResults.top().firstName": "Tony",
"@@topTestResults.top().lastName": "Stark",
"@@topTestResults.top().score": 100
},
{"@@topTestResults": [
{
"firstName": "Tony",
"lastName": "Stark",
"score": 100
},
{
"firstName": "Bruce",
"lastName": "Banner",
"score": 95
},
{
"firstName": "Jean",
"lastName": "Summers",
"score": 95
},
{
"firstName": "Clark",
"lastName": "Kent",
"score": 80
}
]},
{"@@topTestResults.size()": 4},
{"@@topTestResults": [
{
"firstName": "Tony",
"lastName": "Stark",
"score": 100
},
{
"firstName": "Bruce",
"lastName": "Banner",
"score": 95
},
{
"firstName": "Jean",
"lastName": "Summers",
"score": 95
},
{
"firstName": "Clark",
"lastName": "Kent",
"score": 80
},
{
"firstName": "Peter",
"lastName": "Parker",
"score": 80
}
]},
{"@@topTestResults": [
{
"firstName": "Tony",
"lastName": "Stark",
"score": 100
},
{
"firstName": "Bruce",
"lastName": "Banner",
"score": 95
},
{
"firstName": "Jean",
"lastName": "Summers",
"score": 95
}
]},
{"@@topTestResults": [
{
"firstName": "Tony",
"lastName": "Stark",
"score": 100
},
{
"firstName": "Bruce",
"lastName": "Banner",
"score": 95
},
{
"firstName": "Jean",
"lastName": "Summers",
"score": 95
}
]},
{"@@topTestResults.size()": 0}
]
}
GroupByAccum类型的累加器
GroupByAccum是复合累加器,即累加器的累加器,位于最上层。它是一个键和值都可以有多个字段的MapAccum累加器。而且,每个字段的类型都是累加器。
Copy GroupByAccum<type [, type]*, accumType [, accumType]* >
在上面的EBNF范式语法中, 所有type 一起构成了键集, 所有accumType一起构成了映射的值。 由于它们都是累加器,因此它们会执行分组操作。 与MapAccum一样,如果我们尝试存储一个键值对,但其中的键已经被用过了,则新值将累积到已存储的数据中。 在这种情况下,多字段值中的每个字段都有自己的累加函数。 GroupByAccum的一种理解的方式,即是每个唯一的键都是一个唯一的小组ID。
在GroupByAccum中,键类型可以是基本类,元组或压缩字符串。 这些累加器用于将小组值进行聚合。 每个累加器类型可以是除HeapAccum之外的任何类型。 每个基本类和每个累加器类型必须后跟一个别名(alias)。 下面是一个例子。
Copy GroupByAccum<INT a, STRING b, MaxAccum<INT> maxa, ListAccum<ListAccum<INT>> lists> @@group;
向此GroupByAccum添加新数据,数据的格式应该是(key1,key2 - > value1,value2)。
GroupByAccum还支持以下类函数。
用于修改BagAccum的函数(mutator函数)只能在以下条件下使用:
全局累加器的Mutator函数只能在查询主体层语句中使用。
附属于顶点的累加器的Mutator函数只能在POST-ACCUM子句中使用。
get( KEY1 key_value1,KEY2 key_value2 ... )
element
type(s) of the accumulator(s)
按照给定的键返回对应每个组中累加器的值;如果该键不存在,则返回累加器类型的默认值。
containsKey( KEY1key_value1, KEY2key_value2 ... )
如果累加器包含\不包含该键,则返回true\false
remove ( KEY1key_value1, KEY2key_value2 ... )
Copy #GroupByAccum Example
CREATE QUERY groupByAccumEx () FOR GRAPH socialNet {
## declaration, first two primitive type are group by keys; the rest accumulator type are aggregates
GroupByAccum<INT a, STRING b, MaxAccum<INT> maxa, ListAccum<ListAccum<INT>> lists> @@group;
GroupByAccum<STRING gender, MapAccum<VERTEX<person>, DATETIME> m> @@group2;
# nested GroupByAccum
GroupByAccum<INT a, MaxAccum<INT> maxa, GroupByAccum<INT a, MaxAccum<INT> maxa> heap> @@group3;
Start = { person.* };
## usage of global GroupByAccum
@@group += (1, "a" -> 1, [1]);
@@group += (1, "a" -> 2, [2]);
@@group += (2, "b" -> 1, [4]);
@@group3 += (2 -> 1, (2 -> 0) );
@@group3 += (2 -> 1, (2 -> 5) );
@@group3 += (2 -> 5, (3 -> 3) );
PRINT @@group, @@group.get(1, "a"), @@group.get(1, "a").lists, @@group.containsKey(1, "c"), @@group3;
## two kinds of foreach
FOREACH g IN @@group DO
PRINT g.a, g.b, g.maxa, g.lists;
END;
FOREACH (g1,g2,g3,g4) IN @@group DO
PRINT g1,g2,g3,g4;
END;
S = SELECT v
FROM Start:v - (liked:e) - post:t
ACCUM @@group2 += (v.gender -> (v -> e.actionTime));
PRINT @@group2, @@group2.get("Male").m, @@group2.get("Female").m;
}
Copy GSQL > RUN QUERY groupByAccumEx()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{
"@@group.get(1,a).lists": [
[1],
[2]
],
"@@group3": [{
"a": 2,
"heap": [
{
"a": 3,
"maxa": 3
},
{
"a": 2,
"maxa": 5
}
],
"maxa": 5
}],
"@@group.containsKey(1,c)": false,
"@@group.get(1,a)": {
"lists": [
[1],
[2]
],
"maxa": 2
},
"@@group": [
{
"a": 2,
"b": "b",
"lists": [[4]],
"maxa": 1
},
{
"a": 1,
"b": "a",
"lists": [
[1],
[2]
],
"maxa": 2
}
]
},
{
"g.b": "b",
"g.maxa": 1,
"g.lists": [[4]],
"g.a": 2
},
{
"g.b": "a",
"g.maxa": 2,
"g.lists": [
[1],
[2]
],
"g.a": 1
},
{
"g1": 2,
"g2": "b",
"g3": 1,
"g4": [[4]]
},
{
"g1": 1,
"g2": "a",
"g3": 2,
"g4": [
[1],
[2]
]
},
{
"@@group2.get(Male).m": {
"person3": 1263618953,
"person1": 1263209520,
"person8": 1263180365,
"person7": 1263295325,
"person6": 1263468185
},
"@@group2": [
{
"gender": "Male",
"m": {
"person3": 1263618953,
"person1": 1263209520,
"person8": 1263180365,
"person7": 1263295325,
"person6": 1263468185
}
},
{
"gender": "Female",
"m": {
"person4": 1263352565,
"person2": 2526519281,
"person5": 1263330725
}
}
],
"@@group2.get(Female).m": {
"person4": 1263352565,
"person2": 2526519281,
"person5": 1263330725
}
}
]
}
Copy GroupByAccum<INT a, STRING b, MaxAccum<INT> maxa, ListAccum<ListAccum<INT>> lists> @@group;
累加器的嵌套
某些集合累加器允许嵌套。 也就是说,累加器中的元素本身也是累加器。 例如:
Copy ListAccum<ListAccum<INT>> @@matrix; # a 2-dimensional jagged array of integers。Each inner list has its own unique size.
只有ListAccum,ArrayAccum,MapAccum和GroupByAccum类型的累加器可以包含其他类型的累加器。 但是,并非所有集合累加器的组合都是允许的。 以下为具体的限制:
ListAccum: : ListAccum是唯一允许被嵌套在ListAccum中的累加器,最大深度为3
Copy ListAccum<ListAccum<INT>>
ListAccum<ListAccum<ListAccum<INT>>>
ListAccum<SetAccum<INT>> # illegal
MapAccum: 可以嵌套除了 HeapAccum之外的任何累加器类型, 允许以值类型嵌套在 MapAccum内。例如,
Copy MapAccum<STRING, ListAccum<INT>>
MapAccum<INT, MapAccum<INT, STRING>>
MapAccum<VERTEX, SumAccum<INT>>
MapAccum<STRING, SetAccum<VERTEX>>
MapAccum<STRING, GroupByAccum<VERTEX a, MaxAccum<INT> maxs>>
MapAccum<SetAccum<INT>, INT> # illegal
GroupByAccum: 可以嵌套除了 HeapAccum之外的任何累加器类型, 允许以累加器类型嵌套在 GroupByAccum内。 例:
Copy GroupByAccum<INT a, STRING b, MaxAccum<INT> maxs, ListAccum<ListAccum<INT>> lists>
ArrayAccum: 前面的累加器中,嵌套为可选选项;但ArrayAccum累加器的嵌套是必选项。详见 ArrayAccum 章节.
通过定义一个嵌套的ListAccum累加器来构成一个多维度的数组的操作是合法的。请特别注意下面示例中的声明语句和用于嵌套的方括号。
Copy CREATE QUERY nestedAccumEx() FOR GRAPH minimalNet {
ListAccum<ListAccum<INT>> @@_2d_list;
ListAccum<ListAccum<ListAccum<INT>>> @@_3d_list;
ListAccum<INT> @@_1d_list;
SumAccum <INT> @@sum = 4;
@@_1d_list += 1;
@@_1d_list += 2;
// add 1D-list to 2D-list as element
@@_2d_list += @@_1d_list;
// add 1D-enum-list to 2D-list as element
@@_2d_list += [@@sum, 5, 6];
// combine 2D-enum-list and 2d-list
@@_2d_list += [[7, 8, 9], [10, 11], [12]];
// add an empty 1D-list
@@_1d_list.clear();
@@_2d_list += @@_1d_list;
// combine two 2D-list
@@_2d_list += @@_2d_list;
PRINT @@_2d_list;
// test 3D-list
@@_3d_list += @@_2d_list;
@@_3d_list += [[7, 8, 9], [10, 11], [12]];
PRINT @@_3d_list;
}
Copy GSQL > RUN QUERY nestedAccumEx()
{
"error": false,
"message": "",
"version": {
"schema": 0,
"api": "v2"
},
"results": [
{"@@_2d_list": [
[1,2],
[4,5,6],
[7,8,9],
[10,11],
[12],
[],
[1,2],
[4,5,6],
[7,8,9],
[10,11],
[12],
[]
]},
{"@@_3d_list": [
[
[1,2],
[4,5,6],
[7,8,9],
[10,11],
[12],
[],
[1,2],
[4,5,6],
[7,8,9],
[10,11],
[12],
[]
],
[
[7,8,9],
[10,11],
[12]
]
]}
]
}