创建Schema
创建包含客户、交易、反欺诈标记等顶点类型以及交易关系的边类型的图谱Schema,以便表示金融反欺诈的关联信息。
1. 创建顶点
-
客户(Customer)顶点
CREATE VERTEX Customer(
PRIMAEY_ID customer_id STRING,
name STRING
address STRING
credit_score INT
);
-
交易(Transaction)顶点
CREATE VERTEX Transation(
PRIMARY_ID transaction_id STRING,
amount FLOAT,
timestamp DATETIME
);
-
反欺诈标记(FraudMarker)顶点:
- 标记ID(marker_id):反欺诈标记的唯一标识符,作为PRIMARY_ID。
- 类型(type):反欺诈标记的类型。
- 描述(description):反欺诈标记的描述信息。
CREATE VERTEX FraudMarker(
PRIMARY_ID marker_id STRING,
type STRING,
description STRING
);
2. 创建边
交易关系(TransactionRelation)边:
- 发起交易(initiates):表示客户发起的交易。
- 接收交易(receives):表示客户接收的交易。
CREATE DIRECTED EDGE initiates(
FROM Customer,
To Transation
);
CREATE DIRECTED EDGE receives(
FROM Transaction,
To Customer
);
- 标记为欺诈(flagged_as_fraud):表示交易被标记为欺诈
CREATE UNDIRECTED EDGE flagged_as_fraud(
FROM Transaction,
To FraudMarker
);
反向边
如果声明CREATE DIRECTED EDGE时同时声明了参数WITH REVERSE_EDGE=" rev_name ",则一个额外的有向型边" rev_name "
会自动生成。该边的起点与终点与原始创建边相反。之后,每当一个新的边生成,就会自动生成一个反向的边。反向的边拥有与原始边相同的属性。同时,当原始的边有变更时,对应反向的边也同时会变更。
在TigerGraph系统中,反向的边可以大幅增加图查询的效率,特别是需要回溯的查询。例如,在图2中的图数据库纲目中,“如果书X有续作,那它的续作是什么?”是一个正向查询。 然而, 当该查询变成“书X是续作吗?如果是的话它的前作是什么?”,那么此时就需要反向查询。
3. 创建图谱
CREATE GRAPH AntiFraudGraph (
-- 定义顶点类型
Customer (PRIMARY_ID id STRING, name STRING, age INT, address STRING, credit_score INT),
Transaction (PRIMARY_ID id STRING, amount DOUBLE, timestamp DATETIME),
FraudMarker (PRIMARY_ID id STRING, reason STRING),
-- 定义边类型
TransactionOf (FROM Customer, TO Transaction),
FlaggedAsFraud (FROM Transaction, TO FraudMarker),
-- 定义索引
Customer_index ON Customer(id),
Transaction_index ON Transaction(id),
FraudMarker_index ON FraudMarker(id)
)
数据类型
INT
:INT
是整数类型,用于存储有符号的整数值。它可以表示负数和正数,但不包括小数部分。
UINT
:UINT
是无符号整数类型,用于存储非负整数值。它只能表示非负数,不包括负数和小数部分。
FLOAT
:FLOAT
是单精度浮点数类型,用于存储带有小数点的数值。它提供较高的精度和范围,适用于大多数浮点数计算需求。
DOUBLE
:DOUBLE
是双精度浮点数类型,用于存储双精度浮点数值。它提供更高的精度和范围,相对于FLOAT
,适用于需要更高精度计算的场景。
STRING
:STRING
是字符串类型,用于存储文本数据。它可以包含任意长度的字符序列。
VARCHAR
:VARCHAR
是可变长度字符串类型,用于存储可变长度的文本数据。与STRING
类型不同,VARCHAR
类型可以指定最大长度,节省存储空间。
导入数据
定义数据映射
1. 添加csv
2. 创建mapping
CREATE LOADING JOB load_job_ccb FOR GRAPH gqyj{
DEFINE FILENAME MyDataSource;
LOAD MyDataSource TO EDGE gqyj_e_share_relation VALUES
($7,$2,_,_,_,_,$0)
USING SEPARATOR=",",HEADER="FALSE",EOL="\n";
}
3. Loading
RUN LOADING JOB jobName using f=“”(文件路径)
编写查询
-
select 语法范式
resultSet = SELECT vSet
FROM startSet:s - (edgeTypes:e) -> targetTypes:t
[sampleClause] —— 随即采样
[whereClause] —— 条件过滤
[accumClause] —— 沿边并行计算
[postAccumClause] —— 基于点 并行计算
[havingClause] —— 条件过滤
[orderClause] —— 排序
[limitClause]——限制输出
-
From 目标结果
#可以从顶点集或者边集选择
FROM startSet:s
#可以从定义的模版选择
FROM startSet:s -(edgeTypes:e)-> targetTypes:t
-
起点Seed
S0 = seed;
S1 = SELECT t FROM S0:s -(:e)->:t...;
s2 = SELECT t FROM S!:s -(:e)->:t...;
#vertex or vertex set parameter
CREATE QUERY seedEx(VERTEX<person>seed,SET<VERTEX<person>> seedSet)
#<person> 表示顶点类型为 "person"
FOR GRAPH socialNet{
S1 = {seed}
S2 = SeedSet;
S3 = ANY; #ANY 表示图中的所有顶点,用于全局计算(如 PageRank 等
S4 = person.*。#person.* 表示图中的所有 person 类型的顶点。
}
累加器Accumulators
-
定义累加器
MaxAccum <INT> @@globalMax;
#1. 定义了一个名为 @@globalMax 的累加器,它的类型是 MaxAccum <INT>
#2. MaxAccum 是一种标量累加器,表示在累积过程中保留最大值。在这个例子中,累加器的类型是 INT,表示累积的值是整数类型。
#3. 通过定义 @@globalMax 累加器,您可以在查询中使用它来追踪累积过程中的最大值。例如,您可以在循环遍历图中的顶点或边时,将每个元素的某个属性与 @@globalMax 进行比较,并更新累加器的值,以保持最大值
SetAccum <String> @ tags;
#1. 定义了一个名为 @tags 的累加器,它的类型是 SetAccum <String>。累加器的类型是
#2. SetAccum,表示它是一个容器累加器,用于在查询过程中累积唯一的字符串值。在这个例子中,累加器的元素类型是 String,表示它会保存字符串类型的值。
#3. 通过定义 @tags 累加器,您可以在查询中使用它来收集和保存一组唯一的字符串值。例如,当您在遍历图中的顶点或边时,可以将每个元素的某个属性添加到 @tags 累加器中,以便收集所有唯一的标签或标识符。
(1)Scaler Accumulator(标量累加器)
- SumAccum #保留求和值
- MinAccum / MaxAccum / AvgAccum #保留最小、最大、平均值
- AndAccum / OrAccum #保留Bool值
- BitwiseAndAccum / BitwiseOrAccum
- CountAccum #用于计算累加项的数量
(2)Container Accumulators(容器累加器)
- ListAccum > [ ]
- SetAccum > ()
- BagAccum > 无序容器
- MapAccum > {}
- ArrayAccum > 多维数组
-HeapAccum > 堆排序
-GroupByAccum > 复合累加器
-
ACCUM 面向边计算
#1. 定义全局变量
AvgAccum @@avgIncome;
MinAccum<DOUBLE> @@minIncome;
MaxAccum<DOUBLE> @@maxIncome;
#2. 写查询
Result = SELECT cust
FROM Customer:cust -(salary_by:e)-> Company:comp
WHERE cust.age BETWEEN 18 AND 50
ACCUM @@avgIncome += e.income,
@@minIncome += e.income,
@@maxIncome += e.income,
#在执行这个查询语句时,将选择符合条件的 "Customer" 顶点,并根据边 "salary_by" 的收入属性累加计算平均收入、最低收入和最高收入。查询的结果存储在名为 "Result" 的变量中
#这里计算最大值最小值其实不用这么算,可以进行一个改写:
ACCUM @@minIncome = MIN(@@minIncome, e.income);
#假设我们有一个图谱,其中包含了用户(User)和商品(Product)的节点,以及用户购买商品的边(购买关系)。
我们希望统计每个用户购买商品的总金额,并计算购买金额最高的用户。
CREATE QUERY calculateTotalPurchase() FOR GRAPH myGraph {
SumAccum<DOUBLE> @@totalPurchase;
MaxAccum<DOUBLE> @@maxPurchase;
SetAccum<VERTEX<User>> @@maxPurchaseUsers;
Start = {User.*};
// 遍历购买关系边,累加购买金额,并找到最大购买金额和对应的用户
Result = SELECT s, e, t
FROM Start:s -(Purchase:e)-> Product:t
ACCUM @@totalPurchase += e.purchaseAmount,
@@maxPurchase = MAX(@@maxPurchase, e.purchaseAmount),
@@maxPurchaseUsers = CASE
WHEN e.purchaseAmount == @@maxPurchase
THEN @@maxPurchaseUsers + s
ELSE @@maxPurchaseUsers
END;
// 在查询结束后,输出统计结果
POST-ACCUM
PRINT "Total Purchase: " + STRING(@@totalPurchase),
PRINT "Max Purchase Amount: " + STRING(@@maxPurchase),
PRINT "Users with Max Purchase Amount: " + STRING(@@maxPurchaseUsers);
}
-
PostAccum面向点集计算
postAccum
(1)面向点集(2)在accum之后计算
users = {inputUser};
invited_users = SELECT t
FROM users:s -(User_Refer_Users:e)- :t
WHERE t!= inputUser
ACCUM @@visRes += e
POST-ACCUM @@invitedPersonNum +=1;
CREATE QUERY calculateFriendCount() FOR GRAPH socialNetwork {
SetAccum<VERTEX> @@usersWithMostFriends;
INT @maxFriendCount;
SumAccum<INT> @friendCount;
/* 遍历所有用户节点 */
users = {User.*};
Start = {users};
while Start.size() > 0 do
/* 计算当前节点的朋友数量,并将其累加到 @friendCount 中 */
Start = SELECT s FROM Start:s-(:e)->:t
ACCUM @friendCount += 1;
/* 更新拥有最多朋友的用户集合和最大朋友数量 */
POST-ACCUM @@usersWithMostFriends += SELECT s FROM Start:s
WHERE @friendCount == @maxFriendCount;
#将满足当前最大朋友数量的用户节点添加到 @@usersWithMostFriends 中
POST-ACCUM @maxFriendCount = MAX(@maxFriendCount, @friendCount);
#更新最大朋友数量
/* 继续遍历朋友节点 */
Start = {t};
PRINT Start.size();
end;
/* 输出拥有最多朋友的用户集合 */
PRINT @@usersWithMostFriends;
}
流程控制语句
判断
IF condition THEN statements
[ELSE IF condition THEN statements]
[ELSE statements]
END
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;
循环
WHILE condition [LIMIT (name|integer)] DO
statements
END
FOREACH item IN RANGE[a,b].STEP(c) DO
@@total += @@masterList[list];
END;
集合操作
VSet1 = SELECT ... [1,2,3]
VSet2 = SELECT ... [3,4,5]
VSet3 = VSet1 UNION VSet2; [1,2,3,4,5] #并集(去重)
VSet4 = VSet1 INTERSECT VSet2; [3] #交集
VSet5 = VSet1 MINUS VSet2; [1,2] #相减
嵌套查询的调用
CREATE QUARY sub (VERTEX v) FOR GRAPH MyGraph RETURNS (SumAccum<INT>) {
SumAccum<INT> @@result, @cnt = 1
Start = {v};
Start = select t from Start -(:e)-:t;
Start = select t from Start -(:e)-:t
WHERE t!=v
ACCUM @@result += t.@cnt;
Return @@result
}
CREATE QUERY main () FOR GRAPH MyGraph{
SumAccum<INT> @cnt;
Start = {*.*};
Start = select s from Start:s
POST-ACCUM s.@cnt = sub(s);
print Start;
}
案例
PageRank
CREATE QUERY pageRank (FLOAT maxChange, INT maxIter, FLOAT damping) FOR GRAPH gsql_demo
#初始化阻尼因子(damping factor)d、最大变化值maxChange和最大迭代次数maxIter
{
MaxAccum<FLOAT> @@maxDiff = 9999; #追踪每次迭代中节点得分的最大变化量
SumAccum<FLOAT> @recvScore = 0; #累加每个节点接收到的得分
SumAccum<FLOAT> @score = 1; #存储每个节点的当前得分
Vset = {Page.*};
#初始化节点集合Vset为包含所有节点的集合(在这里是Page.*,表示所有Page类型的节点)
WHILE @@maxDiff > maxChange LIMIT maxIter DO
#判断迭代的终止条件为每次迭代中节点得分的最大变化量是否大于maxChange,并且迭代次数未达到maxIter
@@maxDiff = 0;
#在每次迭代开始前,将@@maxDiff重置为0
Vset = SELECT s
FROM Vset:s-(Linkto)->:t
ACCUM t.@recvScore += s.@score/s.outdegree()
#将s节点的得分除以其出度后,加到t节点的接收得分@recvScore中
POST-ACCUM t.@score = damping + (1-damping) * t.@recvScore,
@@maxDiff += abs(t.@score - t.@score_prev),
t.@score_prev = t.@score,
t.@recvScore = 0;
#在开始每一轮迭代之前,我们将t.@recvScore清零,以便进行新的累积计算。这样可以确保每个节点在每一轮迭代中只接收到来自其他节点的最新得分,而不会受之前迭代的影响
PRINT @@maxDiff;
END;
PRINT Vset.page_id, Vset.@score;
#阻尼因子(damping factor)是PageRank算法中的一个参数,通常用字母d表示。阻尼因子表示用户在访问网页时选择按照链接导航的概率,而不是随机跳转到其他网页的概率。
在PageRank算法中,阻尼因子的典型取值为0.85,这意味着用户有85%的概率按照链接进行导航,而有15%的概率进行随机跳转。
#PageRank算法需要进行迭代,因为其计算过程涉及节点之间的相互影响。在每一轮迭代中,节点的得分会根据其连接的节点的得分进行更新。这意味着节点的得分依赖于其他节点的得分,而其他节点的得分也在同一轮迭代中发生变化。因此,为了得到稳定的结果,需要进行多轮迭代,直到节点的得分收敛到一个稳定的状态。