• [技术干货] GQL图查询语言语法概览
    2024年4月,国际标准化组织(ISO)与国际电工委员会(IEC)共同发布了编号为ISO/IEC 39075:202的图查询语言标准 GQL(Graph Query Language),这是继 ISO 发布第一版 SQL 37年后第二个数据库查询语言标准。目前,国际关联数据基准委员会(Linked Data Benchmark Council,以下简称LDBC)官网上已经发布了GQL相关的语法解析工具和基于antlr的文法文件。预计在GQL的影响下,图厂商会加强对GQL的支持力度,开发者后续进行图分析会更加方便。笔者发现,目前网上对GQL相关语句的示例比较少,于是读了GQL标准文档,并试着写了一些符合GQL标准语法的语句,与大家分享。希望后面有越来越多的介绍GQL的相关文章,一方面让开发者了解图查询语言,进而了解图可以解决什么问题;另一方面,让图厂商了解GQL标准,推动GQL更快的普及。注1:本文涉及的所有GQL语句,都在cid:link_3 上解析通过。GQL除了语法规则,还存在一部分语义约束,部分语句符合语法规则,但可能会违反语义约束,请读者自行甄别。注2:本文只是GQL语法功能和应用场景探讨,不涉及某个特性在某个具体图产品上的具体实现讨论。GQL语法介绍本文集中对GQL的图管理、数据修改、数据查询、表达式这几部分给出语句示例,涵盖标准的12-20章的内容,每一部分会先尝试描述支持的特性,然后给出语句示例。下列特性本文只是部分涉及,不做重点描述:Session管理 (第7章)和事务(第8章)复杂引用类型和字面量(Literal)规则(17章、21章)语句返回状态和诊断(23章)Binding Variable Definition Block(第9章)图管理语句图管理语句描述集中在标准的第12章,GQL提供了对图SCHEMA管理、图管理和图上点边类型管理三部分内容。SCHEMA管理SCHEMA管理(GC01)允许一个图数据库实例创建一个或多个SCHEMA,一个SCHEMA下可以创建多个图,不同的SCHEMA实现资源的隔离,从语义上支持逻辑多租。创建一个图SCHEMA,使用下列语句:CREATE SCHEMA IF NOT EXISTS /catelog/one_schema_name其中if not exists是一个可选的语法(GC02)。删除图SCHEMA,使用下列语句:DROP SCHEMA IF EXISTS /catelog/one_schema_name图管理GQL同时兼容强类型图(Closed Graph Type,如GES)和弱类型图(Open Graph Type,如Neo4j),因此创图语句也同时支持两种类型的图。创图-弱类型图CREATE OR REPLACE GRAPH /catelog/one_schema_name/graph_name TYPED ANY 或者CREATE GRAPH /catelog/one_schema_name/graph_name ::ANY GQL中支持使用typed关键字或者::关键字指定类型。创图-强类型图CREATE GRAPH /catelog/one_schema_name/graph_name :: one_graph_type或者在创图语句定义类型(GG03):CREATE GRAPH graph_name TYPED graph { (customer:Customer => {id::STRING, name::STRING}), (account:Account => {no1 TYPED STRING, acct_type TYPED STRING }), (customer)-[:HOLDS]->(account), (account)-[:TRANSFER {amount::INTEGER}]->(account) }此外,还支持引用其他图类型(GG04),以及创图时拷贝其他图类型的数据:CREATE GRAPH graph_name LIKE another_graph AS COPY OF another_graph其中AS COPY OF another_graph是可选语法项(GG05),表示数据拷贝,而like关键字(GG04)是表示的图上点边类型信息的拷贝。删除图的逻辑比较简单,示例如下:DROP GRAPH IF NOT EXISTS /catelog/schema_name/graph_name其中IF NOT EXISTS是可选语法项(GC05)。Graph Type管理在GQL中,对于Closed Graph Type,提供了一套定义点边类型的能力。这里有三点值得关注:对点边类型,GQL认为点边的类型不一定等价于点边的Label(GG02),即在某些情况下,类型可以表示为一组label sets和一组属性的定义的集合。GQL对无向边提供了支持(GH02),通过波浪线可以定义某条类型的边是无向边。如果启用了特性GG26, 不同type下的同名属性,允许支持不同类型。一个典型的定义Graph Type的语句如:CREATE GRAPH TYPE IF NOT EXISTS one_graph_type AS { (account:Account => {no1::STRING, acct_type::STRING }), (account)-[:Transfer {amount::INTEGER}]->(account), (account)~[:Transfer2 {amount::INTEGER}]~(account) }这里Account和Transfer都是Label名。如果Account和Transfer是类型名,要这么写:CREATE GRAPH TYPE IF NOT EXISTS one_graph_type AS { NODE Account(account{id::STRING, acct_type::STRING }), DIRECTED EDGE Transfer{amount::INTEGER} CONNECTING (account -> account), UNDIRECTED EDGE Transfer2{amount::INTEGER} CONNECTING (account ~ account) }如果图中类型信息包含了一组label信息(如支持可选特性GG02的图),语句估计会这么写:CREATE GRAPH TYPE IF NOT EXISTS one_graph_type AS { NODE Account(account:AccountA&AccountB{id::STRING, acct_type::STRING }), DIRECTED EDGE Transfer:Transfer{amount::INTEGER} CONNECTING (account -> account), UNDIRECTED EDGE Transfer2:Transfer2{amount::INTEGER} CONNECTING (account ~ account) }此外,也支持拷贝其他的graph_type,如:CREATE GRAPH TYPE one_graph_type AS COPY OF graph_or_external_reference; CREATE GRAPH TYPE one_graph_type LIKE like_graph; CREATE OR REPLACE GRAPH TYPE one_graph_type LIKE like_graph; 删除Graph Type语法比较简单:DROP GRAPH TYPE IF EXISTS /catelog/one_graph_type数据修改语句数据修改语句集中在第13章,和Cypher差异不大,这里只给出示例,不做过多描述。// insert USE graph_name INSERT (n:SomeLabel{prop1:'value1'}) INSERT (n:Account)-[r:Transfer{amount:10}]->(m:Account) // set MATCH (n:LabelA) WHERE element_id(n)='' SET n.prop='value1', n:LabelA, n = {p1:'v1',p2:'v2'} // remove MATCH (n:LabelA) WHERE element_id(n)='' REMOVE n.prop, n:LabelA // delete MATCH p=(n)--(m) DELETE n,m MATCH p=(n)--(m) DELETE nodes(p) 官方文档中,还有delete subQuery的语义(GD03),因为之前没有类似的概念参考,所以这里不进行举例。数据查询语句在数据查询语句部分(第14章),GQL扩展了较多的能力,将文法文件读下来,感受有几点:支持语句像电路一样串并联形成执行流水线,支持临时结果集,以及更灵活的子查询逻辑支持多种路径模式,对查询意图的表达能力更强支持变长路径(Quantified paths)以及嵌套变长路径查询,对时序图比较友好支持SELECT关键字,使用SQL风格的文法查询图数据库在开始介绍之前,首先枚举一下GQL涉及的查询类子句,以及Cypher中类似语义的子句或者关键字,方便了解Cypher的同学快速入门。GQL子句Cypher子句备注matchmatch能力增强optionaloptionalGQL支持optional后跟子查询filter/wherewhere能力类似,区别在表达式的不同letwith能力类似forunwindGQL支持输出元素下标值order byorder byGQL支持自定义null值排序时优先级limit/offsetlimit/offset能力类似returnreturn能力类似, GQL支持显式的group by子句selectGQL独有的SQL风格的子句,语义上支持多图联合查询union/except/otherwise …union能力增强yieldyield能力类似语句流水线经常使用图查询来进行业务开发的同学可能会有这种需求,需要将同一个语句的结果(如Q1)作为输入同时给多条语句(如Q2和Q3),然后将Q2和Q3的结果进行进一步加工处理,GQL提供了类似的能力,可以将若干语句像电路一样进行串联和并联,形成流水线执行。其中关键字如下:并联:UNION(GQ03)OTHERWISE(GQ02)EXCEPT(GQ04,GQ05)INTERSECT(GQ07)串联:NEXT(GQ20)例如下面的语句中,通过next和except关键字,检索了某个person的好友喜欢评论的帖子,都分布在哪些标签下,其中这些帖子不包含person自己早于$date时间创作的帖子。MATCH (person)-[:KNOWS]->{2}(friend) WHERE element_id(n)=$personId RETURN person, friend NEXT YIELD person, friend MATCH (friend)<-[:HAS_CREATOR]-(comment:Comment)-[:REPLY_OF]->(post:Post) RETURN post EXCEPT ALL MATCH (person)<-[:HAS_CREATOR]-(post:Post) WHERE post.creationDate < $date0 RETURN post MEXT YIELD post MATCH (post)<-[:HAS_TAG]-(tag) RETURN tag.name, count(*) 虽然Cypher也能表达相同的语义,但是,要么子查询会写的很复杂,要么受限于语句文法,会有重复遍历或者数据膨胀的问题。从这一点上看,语句流水线这个特性,能大大缓解一部分Cypher做星型查询或者长链路查询时,由于查询路径上某一类点结果特别多,造成的数据膨胀以及反复遍历的问题。Match子句match子句GQL的描述,Neo4j官网已经介绍了很多内容。这里主要围绕四个特性进行介绍:遍历模式可变长路径路径拼接点边pattern和label过滤遍历模式GQL提供了match mode和path mode/search mode两类遍历模式,其中match mode只能写在每个match关键字后,path mode/search mode可以写在每条路径前,或者match语句后接的keep语句中,这里简要列一下文档中的语法规则。<simple match statement> ::= MATCH <graph pattern binding table> <graph pattern binding table> ::= <graph pattern> [ <graph pattern yield clause> ] <graph pattern yield clause> ::= YIELD <graph pattern yield item list> <graph pattern yield item> ::= <element variable reference> | <path variable reference> <graph pattern> ::= [ <match mode> ] <path pattern list> [ <keep clause> ] [ <graph pattern where clause> ] <path pattern list> ::= <path pattern> [ { <comma> <path pattern> }... ] <path pattern> ::= [ <path variable declaration> ] [ <path pattern prefix> ] <path pattern expression> 提炼其中关于遍历模式相关的语法规则,简化后大概为:<graph pattern> ::= [ <match mode> ] [ <path variable declaration> ] [ <path pattern prefix> ] <path pattern expression> [ { <comma> [ <path variable declaration> ] [ <path pattern prefix> ] <path pattern expression> }... ] [ KEEP <path pattern prefix> ] [ <graph pattern where clause> ] 其中关于match mode和path pattern prefix,有如下规则:<match mode> ::= <repeatable elements match mode> | <different edges match mode> <path pattern prefix> ::= <path mode prefix> | <path search prefix> <path mode prefix> ::= <path mode> [ <path or paths> ] <path mode> ::= WALK | TRAIL | SIMPLE | ACYCLIC <path search prefix> ::= <all path search> | <any path search> | <shortest path search> 关于Match Mode,可以看到起作用范围为整条match子句,有下列两种模式,根据模式名称可以大概猜出其含义:模式名称含义特性编号DIFFERENT EDGES要求match后同一条路径中不能含有相同的边G002REPEATABLE ELEMENTS允许路径中出现重复元素G003其中Note 220提到了当match mode为DIFFERENT EDGES时,似乎是对Match后跟的path pattern数量有约束,即如果路径中出现selective path pattern,则当match mode为DIFFERENT EDGES时,path pattern list中只能有这一条path pattern。这里给出match mode相关的例句:MATCH DIFFERENT EDGES (n)-[r]->(m),(m)-[r1]->(s) WHERE element_id(n)='1' RETURN count(*) MATCH REPEATABLE ELEMENTS (n)-[r]->(m),(m)-[r1]->(s) WHERE element_id(n)='1' RETURN count(*) 关于path mode四种模式,其含义在聊聊超级快的图上多跳过滤查询文章中已经提到过,这里不再赘述。至于path search prefix,可以用来约束查询数量以及是否优先返回最短路。下面给出这些模式相关的例句:编号特性描述例句G010Explicit WALK keywordMATCH p=WALK (n)<-[:HAS_CREATOR]-(:Comment)-[:REPLY_OF]->(s:Post) WHERE element_id(n)='1' RETURN pG011Advanced path modes: TRAILMATCH p=TRAIL (n)<-[:HAS_CREATOR]-(:Comment)-[:REPLY_OF]->(s:Post) WHERE element_id(n)='1' RETURN pG012Advanced path modes: SIMPLEMATCH p=SIMPLE (n)<-[:HAS_CREATOR]-(:Comment)-[:REPLY_OF]->(s:Post) WHERE element_id(n)='1' RETURN pG013Advanced path modes:ACYCLICMATCH p=ACYCLIC (n)<-[:HAS_CREATOR]-(:Comment)-[:REPLY_OF]->(s:Post) WHERE element_id(n)='1' RETURN pG015All path search: explicit ALL keyword”MATCH p=ALL WALK (n)<-[:HAS_CREATOR]-(:Comment)-[:REPLY_OF]->(s:Post) WHERE element_id(n)='1' RETURN pG016Any path searchMATCH p=ANY 5 TRAIL (n)<-[:HAS_CREATOR]-(:Comment)-[:REPLY_OF]->(s:Post) WHERE element_id(n)='1' RETURN pG017All shortest path searchMATCH p=ALL SHORTEST (n)<-[:HAS_CREATOR]-(:Comment)-[:REPLY_OF]->(s:Post) WHERE element_id(n)='1' RETURN pG018Any shortest path searchMATCH p=ANY SHORTEST (n)<-[:HAS_CREATOR]-(:Comment)-[:REPLY_OF]->(s:Post) WHERE element_id(n)='1' RETURN pG019Counted shortest path searchmatch p=SHORTEST 5 (n)<-[:HAS_CREATOR]-(:Comment)-[:REPLY_OF]->(s:Post) WHERE element_id(n)='1' RETURN pG020Counted shortest group searchmatch p=SHORTEST 5 GROUP (n)<-[:HAS_CREATOR]-(:Comment)-[:REPLY_OF]->(s:Post) WHERE element_id(n)='1' RETURN pG006Graph pattern KEEP clause: path mode prefixmatch p=(n)<-[:HAS_CREATOR]-(:Comment)-[:REPLY_OF]->(s:Post) KEEP WALK WHERE element_id(n)='1' RETURN pG007Graph pattern KEEP clause: path search prefixmatch p=(n)<-[:HAS_CREATOR]-(:Comment)-[:REPLY_OF]->(s:Post) KEEP ANY SHORTEST 5 WHERE element_id(n)='1' RETURN p这里有一个shortest group的概念(G020),其含义是将不同长度的路径分组,例如SHORTEST 3 GROUP就是将所有最短路、所有次短路以及除了最短路和次短路外的最短路返回,相关概念neo4j也解释的很好,可以参阅Neo4j - Shortest paths。可变长路径与Cypher不同,GQL提供可不一样的变长路径参数,相关例句Neo4j官网也有解释:编号特性名例句G035Quantified pathsMATCH (:Station {name: 'Peckham Rye'})-[link:LINK]-(:Station {name: 'Clapham Junction'}){1,3} return count(*)G036Quantified edgesMATCH (:Station {name: 'Peckham Rye'})-[link:LINK]-{1,3}(:Station {name: 'Clapham Junction'}) return count(*)这里{1,3}还可以替换成*,{3},+,?这样其他的表达方式,分别表示不限定跳数、固定跳数,至少1跳,存在性验证这样的场景。这种改写方式,对时序图会更友好,想象一个列车中转的场景,要查询两班列车能否接续中转,可以用下列的写法:MATCH (:Station)<-[:CALLS_AT]-(d:Stop) ((s:Stop)-[:NEXT0]->(f:Stop) WHERE s.time0 > f.time0){1,3} (a:Stop)-[:CALLS_AT]->(:Station) RETURN d.departs AS departureTime, a.arrives AS arrivalTime这里通过嵌套路径(G038)的where s.time0 > f.time0表达了路径上节点关系之间的强时序约束,可以用于微博转发分析、疫情传播分析等场景。在具体实现时,相关约束可以下推到遍历过程中。如果cypher要表达类似的语义,至少需要在所有路径产生后,使用ListComprehension语义和range枚举下标结合才能完成,且难以下沉到遍历过程中完成。此外match语句的相关规则中,对无向边匹配也有相关描述,这里举个例子:MATCH (a:Station)~[:CALLS_AT]~(d:Stop WHERE d.time0 > a.time0) RETURN d MATCH (a:Station)~[:CALLS_AT]~>(d:Stop WHERE d.time0 > a.time0) RETURN d MATCH (a:Station)<~[:CALLS_AT]~(d:Stop WHERE d.time0 > a.time0) RETURN d此外还有些路径简写的例子,笔者没有做深入分析,只写几条通过了parser的示例语句:编号特性名例句G047Relaxed topological consistency: concise edge patternsMATCH p=<-[:R]--[:S]- RETURN pG044Basic abbreviated edge patternsMATCH (a:Station)->(d:Stop) return d路径拼接GQL还支持对路径进行拼接,以及路径中使用|,以及|+|操作符,这里直接给例子:G030 Path multiset alternation:MATCH (p:Person)-[:LIVES_IN]->(c:City)|+|(p:Person)-[:LOCATED_IN]->(c:Place) RETURN pGQ032 Path pattern union:MATCH (p:Person)-[:LIVES_IN]->(c:City)|(p:Person)-[:LOCATED_IN]->(c:Place) RETURN p以及查询结果的路径拼接(GE06):match p=(a)->(b),p1=(b)->(d) return p||p1 match (a)-[r]->(b),(b)-[r1]->(d) return PATH[a,r,b,r1,d] 点边pattern和label过滤在GQL中,点边的属性过滤支持两种模式:特性名例句element pattern where clauseMATCH (a:Station)<-[:CALLS_AT]-(d:Stop where d.time0 > 1) RETURN delement property specificationMATCH (a:Station)<-[:CALLS_AT]-(d:Stop{time0:1}) RETURN d显然使用where表达过滤,能力会更为强大,当然,相关过滤条件也可以写在后面的where子句中。此外label支持若干复杂的与或非操作,示例如下:MATCH (a:Station|Stop) return a MATCH (a:Station&Stop) return a MATCH (a:(Station&Stop)|OtherLabel) return a match (a:%) return a这里%是wildcard label(G074), match (a:%) return a其含义是匹配有label的点,也就是在G074特性启用后,支持判断点上是否有label。let子句Let子句(GQ09)与cypher的with子句类似,这里只给一个示例:MATCH (n) LET gender = n.gender RETURN genderfor子句for子句与cypher的unwind类似,表示遍历某个可以遍历的结构,与unwind不同的是,for提供了一种类似于python中enumerate函数的用法,可以同时给出元素下标和索引值。在官方文档中也同步说明,for语句中可以嵌套一个临时表结构,这里试着给出示例:GQ10 FOR statement: list value supportMATCH (n:LabelA) WHERE n.age < 10 LET v=collect_list(n) FOR s IN v RETURN s GQ24 FOR statement: WITH OFFSETMATCH (n:LabelA) WHERE n.age < 10 LET v=collect_list(n) FOR s IN v WITH OFFSET index RETURN index, s GQ23 FOR statement: binding table supportTABLE userActivity = { MATCH (u:User)-[a:ACTION]->() RETURN u.id AS userId, a.ts AS timestamps } FOR u IN userActivity RETURN u.userId, u.timestampsGQ11 FOR statement: WITH ORDINALITYTABLE userActivity = { MATCH (u:User)-[a:ACTION]->() RETURN u.id AS userId, a.ts AS timestamps } FOR u IN userActivity WITH ORDINALITY index RETURN index,userId, timestamps排序和分页子句排序和分页相关语义与Cypher差异不大,GQL支持了排序时定义null的优先级(GA03),这里给出一个示例:MATCH (n) RETURN n.name AS name ORDER BY name ASC NULLS FIRST, n.date0 DESC NULLS LAST 也同步给一个limit和offset的示例:MATCH (n) RETURN n SKIP 10 LIMIT 10 return子句GQL的结果返回子句中,除了支持order by之外,还可以支持group by子句(GQ15),其他和Cypher差别不大,这里试着写一个sql风格的group by:MATCH (n) LET type0=n.type0, name = n.name RETURN count(*) GROUP BY type0select子句在GQL文档中,提供了SQL风格的select子句,但是奇怪的是,select子句是在focused linear query statement语法规则下,而如果GQL没用启用GQ01 Use grpah相关的规则,focused linear query statement不应出现在GQL语句中,也就是说,select子句的特性是一组可选特性,但是select也可以有不显式声明使用某个graph的写法。另一个奇怪的点在于,GQL提供了临时表的语法,但是select的语法规则中,其后必须跟一个match子句或者是嵌套子查询,而不能用来操作临时表,这就和SQL+Graph的设计理念有一些冲突。先来看select的具体规则:<select statement> ::= SELECT [ <set quantifier> ] { <asterisk> | <select item list> } [ <select statement body> [ <where clause> ] [ <group by clause> ] [ <having clause> ] [ <order by clause> ] [ <offset clause> ] [ <limit clause> ] ] <select item list> ::= <select item> [ { <comma> <select item> }... ] <select item> ::= <aggregating value expression> [ <select item alias> ] <select item alias> ::= AS <identifier> <having clause> ::= HAVING <search condition> <select statement body> ::= FROM { <select graph match list> | <select query specification> } <select graph match list> ::= <select graph match> [ { <comma> <select graph match> }... ] <select graph match> ::= <graph expression> <match statement> <select query specification> ::= <nested query specification> | <graph expression> <nested query specification> 这样很容易写出一条符合语法规则的语句:SELECT n.name AS name, n.age AS age FROM CURRENT_GRAPH MATCH (n:LabelA) WHERE n.score > 10 ORDER BY age OFFSET 10 LIMIT 10 但是如果用来操作临时表,就得这么写:TABLE userActivity = { MATCH (u:User)-[a:ACTION]->() RETURN u.id AS userId, a.ts AS timestamps } SELECT userId, ts FROM { FOR u IN userActivity RETURN u.userId AS userId, u.timestamps AS ts } where ts > 2 可以看到显得有些鸡肋,虽然下面的写法也能parse成功,但是总感觉少了些什么:TABLE userActivity = { MATCH (u:User)-[a:ACTION]->() RETURN u.id AS userId, a.ts AS timestamps } SELECT userId, ts FROM { FOR u IN userActivity } where ts > 2 此外select也能使用group by和having,这里给个例子,做例子时没有做到只在group by子句中出现group key,可能在具体语义上会有些问题:SELECT n.gender AS gender,count(*) AS count0 FROM CURRENT_GRAPH MATCH (n:LabelA) WHERE n.score > 10 GROUP BY gender having count0 > 10 表达式GQL的表达式主要在第20章,总体上继承了Cypher的风格,但是在一些表现形式上有差异。与Cypher相比,其做的好的点有:支持判断值的类型支持显示类型转换支持一系列值拼接语法,内置了丰富的谓词感觉缺失的点有:缺少访问列表下标相关语义(也可能是笔者没读到,毕竟collect_first函数、for子句都有了)缺少一些原生的、与返回格式相关的关键字(毕竟都有table、graph这样的显式值类型了)表达式就不展开描述了,只讲下做的好的几个点:值类型判断GQL中支持判断值类型(GA06),相关的类型有15种,这里给出14种类型判断的语句,至于判断某个类型是否为Graph,笔者实在是不知道怎么用GQL构造一个语义上很清晰的Graph对象,暂时不给出示例。类型关联特性编号示例语句BOOL/BOOLEANVALUE a = true RETURN a IS TYPED BOOL, a IS ::BOOLString/CHAR/VARCHARVALUE a = 'hello' RETURN a IS TYPED STRING, a IS ::VARCHARBYTES/BINARY/VARBINARYGV35VALUE v::BYTES=X'AFDEAA' RETURN v IS TYPED BYTES, v IS::VARBINARYNumericVALUE a = 1 RETURN a IS TYPED INT32, a IS ::INT64TemporalGV39VALUE a = date('2025-03-28') RETURN a IS ::DATEDurationGV41VALUE a = DURATION({hours:1, minutes:1,seconds:1}) RETURN a IS ::DURATION(YEAR TO MONTH), a IS ::DURATION(DAY TO SECOND)ImmaterialGV71,GV72VALUE a = null RETURN a IS ::NOTHING, a IS ::NULLNodeMATCH (n) RETURN n IS ::NODE, n IS ::VERTEXEdgeMATCH (n)-[r]->() RETURN r IS ::EDGE, r IS ::RELATIONSHIPPathGV55MATCH p=(a)->(b) RETURN p IS ::PATHListGV50VALUE a = [1,2,3,4] RETURN a IS ::LIST, a IS ::List<int>, a IS ::ARRAYRecordGV45VALUE s = {name:'s', age:10} RETURN s IS ::RECORD,s IS ::RECORD{name::STRING, age::INTEGER}Dynamic Union Type(GV67):VALUE a = 1 RETURN a is ::int|bool Binding Table Type(GV61,GE02):TABLE userActivity = { MATCH (u:User)-[a:ACTION]->() RETURN u.id AS userId, a.ts AS timestamps } RETURN userActivity IS ::BINDING Table{userId::INT, timestamps::TIMESTAMP}支持显示类型转换GQL支持下列语法,可以显示转换类型:RETURN CAST(1 as INT64) 支持一系列拼接语法,内置丰富的谓词GQL支持字符串、list和path等对象的拼接,如:RETURN "aaa"||"bbb" MATCH p=(a)->(b),p1=(b)->(d) RETURN p||p1 MATCH [1,2,3,4]||[5,6,7,8] 也内置了一系列丰富的谓词,如:MATCH (n)-[r]->(m) RETURN n IS SOURCE OF r,m IS DESTINATION OF r MATCH (n) WHERE n.id=1 LET a=n WHERE EXISTS{(a)-->()} RETURN a 其他资料和困惑除了上述介绍的语法特征,GQL还支持事务管理、session管理,语句状态和诊断,以及将子查询的结果绑定到某个表或者某个变量上(GQ18),这些后面再聊。有点疑惑的点在于,GQL支持表和图作为变量的类型,但是有下面的矛盾:文档中给出了定义临时表的语法,处理临时表的语法却很少(只有一个for语句)文档中给出了处理图的语法,但是没有给出定义一个临时图的语法也就是说文档里没有显示的标明,在Binding Variable Definition Block中,如何通过子查询来定义一个图,而不是表。虽然可以用value关键字将某个子查询绑定到图上,但是这种绑定依赖的是等号左边声明的图类型,而非语句本身结果就是一个图,例如下列语句能过GQL的解析器,但是却不一定有实际含义,等号右侧不一定是一个图,如果右侧可以是一个图,也是因为等号左侧声明了GRAPH userActivity:GRAPH userActivity = value { MATCH (u:User)-[a:ACTION]->() RETURN u,a } return userActivity 相反表的定义就很自然,毕竟处理结果总是能整理成一张表的,不过GQL中处理表的文法就有点鸡肋了,select后的from子句还无法直接跟表的名称。这块等后面有时间再讨论。在github OPENGQL 代码仓中,给出了GQL的文法,以及解析工具,还有railroad图,本文的所有示例都是在代码仓的GQL Editor中解析通过的,毕竟解析通过的,才至少"看起来是正确的”。另外,文中所有的GV45,GC02,G002这样的编号,都是GQL官方文档中可选特性的特性编号,可以直接对号入座方便了解和学习。参考文档:LDBC open-source GQL tools: https://ldbcouncil.org/pages/opengql-announce/ISO/IEC 39075:2024 Information technology — Database languages — GQL: https://www.iso.org/standard/76120.htmlNeo4j GQL conformance: https://neo4j.com/docs/cypher-manual/current/appendix/gql-conformance/Neo4j - Shortest paths: cid:link_1GQL overview: https://cloud.google.com/spanner/docs/reference/standard-sql/graph-introWhat is the database language GQL?: https://www.linkedin.com/posts/jtc1news_gql-database-language-standard-isoiec-39075-activity-7186398287963279360-E6rV?utm_source=share&utm_medium=member_desktopOpen GQL:cid:link_4
  • [问题求助] BUG求助
    在关系标签下,如rate标签,选择标签的属性进行画布展示,但却没有变化,而在其他节点标签中却可以实现,这是一个BUG吗,要如何解决。
  • [问题求助] 基于图数据库的OneID实现思路
    传统车企,公司想做B、C端用户数据的融合,基于OneID技术实现对用户的统一管理,有大侠在这方面做个探索吗?也就是说,基于图数据库如何实现OneID的存储和计算,以便更高效的对OneID进行算法升级等工作
  • [热门活动] 资讯|中国图数据库,领导者!
    近日 ,全球领先的IT市场研究和咨询公司IDC发布《IDC MarketScape: 中国图数据库市场厂商评估,2023》报告华为云GES(图引擎服务)凭借多年的技术积累和丰富的行业实践经验位居领导者类别IDC MarketScape报告中指出:“华为云GES作为拥有自主知识产权的国产分布式原生图数据库和图引擎产品,提供一站式的图存储、图查询和图计算能力,支持30+高性能算法,覆盖多场景分析计算,具备10+图神经网络和图嵌入算法。广泛应用于互联网、政务、安平、税务、电力等行业,为客户的辅助决策、降本增效等提供专业、高效的服务。”深耕技术,让GES应用更高效随着数字化转型的加速,企业的业务数据已经出现多源异构、关联复杂的特点,Graph(图)技术作为处理异构数据的技术应运而生。图数据分析平台将作为大数据、数仓的加持,成为企业数据分析的重要基础平台华为云GES集图数据库和图分析引擎一体化,具备完善的图分析、图查询、图可视化能力,作为中国首个商用的、拥有自主知识产权的原生图产品,具备以下几个优势:分析查询一体化一份数据做两件事:查询和分析一体化, 提供丰富的图分析算法,为关系分析、路径规划、精准营销等业务提供多样的分析能力;支持Cypher和Gremlin两种主流图查询语言,兼容客户的使用习惯;内置30+高性能算法,覆盖多场景分析计算,具备10+图神经网络和图嵌入算法大规模、高性能得益于高效的数据组织,华为云GES可以让客户有效地对百亿节点千亿边规模的数据进行查询和分析;深度优化的分布式图计算引擎,可以为客户提供高并发、秒级多跳的实时查询能力,查询和算法性能业界领先,6跳查询秒级响应。简单易用,支持no-code可视化分析华为云GES支持no-code图建模、实体的下钻、筛选展示以及算法调参等功能,可实现0代码完成图的构建和分析;除此之外,它还提供0门槛创建和使用图的功能,还可以针对部分场景,支持built-in操作,即开即用。合作共赢,让GES应用更全面依托于强大的科研能力,华为云GES已经在政务、金融、互联网等多个行业实践并取得明显应用效果,并已服务超过50家客户。可广泛应用于社交关系分析、营销推荐、数据血缘、信息传播、团伙挖掘、金融风控、工业互联网等具有丰富关系数据的场景。政务行业多地政府部门早已实现“数字化”办公,但是原有平台无法及时处理海量、复杂的业务诉求。以某市12345热线为例,平均一个月需要处理60多万单市民诉求,热线响应速度慢,工单处置能力有限。通过和华为云GES合作,借助大数据AI手段使得工单处理效率提升60%,热线接通率提升20%,市民满意度提升至87%,大大提升了政府服务水平。金融行业随着金融数字化进程,电子支付已是大势所趋,但同时也存在薅羊毛、恶意套现等欺诈手段,如何在海量数据中甄别异常用卡、异常套现,已是金融卡风控的重要一环,业界图数据库产品,大多数不提供内置算法。华为云GES内置了30+算法,能快速帮助金融分析师,从海量数据中快速甄别异常用卡情况例如在金融反欺诈场景中,利用GES内置的丰富算法,可实现业务6跳查询秒级响应,环路检测分钟级响应,将担保风险感知由原先几十分钟降为1分钟左右,帮助客户挖掘出潜在的风险,为客户保驾护航。互联网行业客户面临自建基础设施成本高、应用上线周期长、已有方案效率低以及推荐效果不佳等挑战。基于华为云GES,可实现好友、商品或者咨询的个性化推荐,准确率高达80%;通过对用户画像、行为相似度或者好友关系等,进行用户分群,实现用户群体分析管理,效率提升高达90%;通过企业投资、控股等关联关系,提供企业间深度追溯的能力,实现了原先开源图数据库不能满足“一键穿透公司股权全景”的功能。 持续创新,让GES能力更专业在华为开发者大会2023(Cloud)上,华为云盘古大模型3.0重磅发布,华为云GES依托盘古大模型的多模态知识图谱和图嵌入的知识表征,可支持图神经网络算法,提升识别效果,辅助客户决策。未来,华为云GES将持续加大与数据分析平台以及盘古大模型的深度融合,继续坚持技术创新,为企业提供更灵活、更融合、更智能的服务,加速企业数字化转型,共创数字化新未来。关于IDC MarketScape:IDC MarketScape厂商评估模型旨在为特定市场中信息和通信技术(ICT)厂商的竞争力提供一个概述。研究方法采用严格的定性和定量的标准的评分方法,以单一的图形说明每个厂商在特定市场中的位置。IDC MarketScape提供了一个清晰的框架,在其中可以对IT和信息通信技术厂商的产品、服务、能力和策略以及当前和未来的市场成功因素进行有意义的比较。该框架还为技术买家提供了针对当前或潜在厂商的360度优劣势评估,为技术买家提供参考。
  • [问题求助] Node2Vec各个参数说明
    我想请问下,和这个“映射维度”可以解释详细一下么,一般怎么去确定这个范围x
  • [通用服务] 【AI使能】GES图引擎
    图引擎服务(Graph Engine Service,简称GES),使用华为自研的EYWA内核,是针对以“关系”为基础的“图”结构数据,进行查询、分析的服务。广泛应用于社交关系分析、营销推荐、舆情及社会化聆听、信息传播、防欺诈等具有丰富关系数据的场景。分类文档链接备注最新动态cid:link_5 特性清单cid:link_2 原子APIcid:link_3 FAQcid:link_4 华为云在线课程(免费)图引擎服务https://education.huaweicloud.com/courses/course-v1:HuaweiX+CBUCNXE008+Self-paced/about?isAuth=0&cfrom=hwc图引擎服务(Graph Engine Service),是针对以“关系”为基础的“图”结构数据,进行查询、分析的服务。广泛应用于社交关系分析、推荐、精准营销、舆情及社会化聆听、信息传播、防欺诈等具有丰富关系数据的场景。华为云培训服务(收费)基于图引擎的医药查询系统cid:link_1如何在当前琳琅满目的药品中选取合适自己病症的良药呢?在人工智能发展迅猛的当下,让AI为选取药品贡献一份力量吧。华为云开发者网GES开放能力cid:link_6 
  • 持续更新!图引擎服务GES优质文章精选汇总!
     华为GES图引擎服务优质博文精选图数据基础知识 & GES介绍人人都在谈的图数据库到底是个啥? 作者:你好_TT, 2021-05-08从零开始学Graph Database:什么是图 作者:弓乙, 2022-10-08画张图,就能秒级洞察千亿复杂关系 2021-01-12华为云新一代黑科技核心算法揭秘 作者:mr.FangYang, 2018-08-21云图说-解析华为云”黑科技“---图计算技术 作者:阅识风云, 2018-07-10聊聊图的相似性 作者:你好_TT, 2021-12-25图算法实践之k-hop 作者:你好_TT, 2021-05-12GES容灾介绍  2023-07-22【干货】华为云图数据库GES技术演进 作者:Chenyi, 2023-08-23华为云GES:十年磨一剑,打造业界一流的云原生分布式图数据库 2023-08-24GES使用指引 &图进阶学习华为云图引擎服务 GES 实战——创图 作者:你好_TT, 2021-08-22调用 GES 服务业务面 API 相关参数的获取 作者:你好_TT, 2021-08-23图数据库对 NULL 属性值支持情况 作者:你好_TT, 2021-06-18Gremlin语言学习系列链接汇总 作者:你好_TT, 2021-02-05如何使用GES进行社交关系考据?---GES查询能力介绍 作者:弓乙, 2021-10-19聊聊图上超级快的多跳过滤查询 作者:弓乙, 2023-4-12使用GES4Jupyter连接GES并进行图探索 作者:蜉蝣与海, 2022-06-25使用参数化查询提高Cypher查询的性能 作者:蜉蝣与海, 2022-04-10记一次图引擎GES cypher慢查询的分析与优化作者:蜉蝣与海, 2022-05-08GES对图细粒度权限控制的支持 作者:你好_TT, 2021-12-29使用Cypher子查询进行图探索 作者:蜉蝣与海, 2023-05-10华为云图引擎服务GES属性管理进阶 2024-01-15GES场景化应用全链路数据血缘在满帮的实践 作者:你好_TT, 2021-12-09GES与Flink的对接 作者:你好_TT, 2021-12-29要想推荐系统做的好,图技术少不了 作者:你好_TT, 2021-12-27图计算助力智慧金融 作者:你好_TT, 2020-04-18扒一扒GES如何赋能互联网电商风控 作者:Dr Thunder, 2021-04-23使用GES处理金融风控场景示例一 作者:图森破, 2020-05-19基于人货场的电商知识图谱的构建 作者:某某人, 2020-07-04采用GES构建锅炉仿真系统的关系图谱 作者:犀牛, 2020-06-28基于GES图数据库的网络架构模型构建 作者:左手看星星, 2020-07-29华为云ModelArts与图引擎联手打造,图深度学习强势落地! 作者:我们都是云专家, 2019-08-08基于GES图数据库的大规模数据追溯服务优化 2021-03-03618 技术特辑(一)不知不觉超预算3倍,你为何买买买停不下来? 作者:技术火炬手, 2021-06-11云图说-复杂网络的破解之道,图引擎带你径直迈向成功作者:阅识风云, 2019-10-16云图说-互联网应用的关系分析利器——企业EI的百晓生“图引擎”作者:阅识风云, 2019-04-19爱库存X华为云:乘“云”破浪,逐梦新电商: 外部链接 云社区链接构建站点数字孪生,支撑确定性运维:华为云九洲云图CloudMap 作者:HWCloudAI 2023-03-315分钟迁移关系型数据库到图数据库 作者:RiverSide 2023-07-17HyG超大规模图计算引擎专题GES图计算引擎HyG揭秘之图切分 作者:π, 2022-06-23CSR格式如何更新? GES图计算引擎HyG揭秘之数据更新 作者:π, 2023-06-15图神经网络 & 图深度学习专题图嵌入&知识表征の初体验 作者:图森破, 2020-05-15在OCR场景使用GCN图卷积 作者:图森破, 2020-06-11风控领域图深度学习算法思考 作者:图森破, 2020-07-14知识图谱trans系列算法介绍 作者:图森破, 2021-06-29图嵌入算法介绍 作者: 图森破, 2021-06-29图神经网络!打开企业盈利的下一个风口 作者:chenyi, 2019-12-28图卷积神经网络初探 作者:chenyi, 2019-11-29图技术漫谈Neo4j闭源转商,成为强大图计算平台还需要几步?作者:chenyi, 2019-12-28Freebase Data Dump结构初探, 作者:蜉蝣与海, 2021-07-27带你认识图数据库性能和场景测试利器LDBC SNB, 作者: 闹闹与球球, 2022-06-24从图引擎平台技术,看华为云EI的决心和野心作者:chenyi, 2018-01-29使用Jupyter可视化图查询语言Cypher语法树 作者:蜉蝣与海, 2022-08-22Euler浅析 作者:弓乙, 2019-01-23从两个开源图数据库PR看查询执行时的编码设计问题 作者:蜉蝣与海, 2022-05-03Notebook案例精选图数据库实践-新冠患者轨迹追溯电商风控案例教育知识图谱利用图数据库研究COVID-19论文数据集基于图引擎的医药查询系统Koolab新冠患者轨迹追溯端边云Serverless大数据湖解决方案附录:GES首页:cid:link_21GES最新动态:cid:link_17AI训练营图数据库系列 作者:Ray博士第一讲第二讲第三讲开发者中心-图引擎服务GES开发指南:cid:link_0GES帮助文档:cid:link_9GES算法API参数参考:cid:link_11
  • [技术干货] 图数据库实践--COVID-19患者轨迹追溯
    图数据库实践--COVID-19患者轨迹追溯背景COVID-19 大流行的形势依然很严峻,新冠疫情确诊患者的轨迹信息成为疫情发展过程中大众关注的焦点,政府部门也陆续公开了部分确诊患者的非隐私信息,这部分数据为相关研究人员研究疫情的传播与防控提供了重要的数据支撑。 然而,公布的数据多为文本等非结构化数据,而且极其分散,难以直接为后续研究提供深度的支撑。患者的轨迹信息蕴含居家、出行、餐饮等丰富的接触关系,在新冠病毒人传人的特性下,如果能直观地展示这些接触信息,相信能对疫情的研究提供很大的帮助。基于此,我们利用相关技术手段从公开的病患轨迹数据抽取了病患相关的基本信息(年龄、性别等)、轨迹、病患关系等数据,并利用图数据库技术对其进行研究,尝试为政府相关部门对疫情的传播与防控工作提供有效的决策支撑。数据建模首先我们需要构建新冠疫情轨迹数据的图数据模型。图数据模型中的实体包括患者、轨迹点、交通工具等,实体关系包括居住于、逗留过等,具体的图模型见下图: 图模型中包含了四类点和六类边,元信息说明如下: 数据集我们采集了近期“旅行团疫情”公开的部分轨迹文本数据,根据设计的图模型对数据格式进行转换,转换后的数据可从此处下载。这份数据包含800+个点,1300+条边。创图我们下面将使用华为云图数据库 GES 对以上数据集进行探索和演示,我们需要先在 GES 中创图并将以上数据集导入,详细指导流程可参见华为图引擎文档-快速入门和华为云图引擎服务 GES 实战——创图。使用 GES 查询的预置条件首先通过moxing包从对象存储服务obs中下载ges4jupyter。ges4jupyter是jupyter连接GES服务的工具文件。文件中封装了使用 GES 查询的预置条件,包括配置相关参数和对所调用 API 接口的封装,如果你对这些不感兴趣,可直接运行而不需要了解细节,这对理解后续具体查询没有影响。import moxing as mox mox.file.copy('obs://obs-aigallery-zc/GES/ges4jupyter/beta/ges4jupyter.py', 'ges4jupyter.py') mox.file.copy('obs://obs-aigallery-zc/GES/ges4jupyter/beta/ges4jupyter.html', 'ges4jupyter.html')GESConfig的参数都是与调用 GES 服务有关的参数,依次为“公网访问地址”、“项目ID”、“图名”、“终端节点”、“IAM 用户名”、“IAM 用户密码”、“IAM 用户所属账户名”、“所属项目”,其获取方式可参考调用 GES 服务业务面 API 相关参数的获取。这里通过read_csv_config方法从配置文件中读取这些信息。如果没有配置文件,可以根据自己的需要补充下列字段。对于开启了https安全模式的图实例,参数port的值为443。from ges4jupyter import GESConfig, GES4Jupyter, read_csv_config eip = '' project_id = '' graph_name = '' iam_url = '' user_name = '' password = '' domain_name = '' project_name = '' port = 80 eip, project_id, graph_name, iam_url, user_name, password, domain_name, project_name, port = read_csv_config('cn_north_4_graph.csv') config = GESConfig(eip, project_id, graph_name, iam_url = iam_url, user_name = user_name, password = password, domain_name = domain_name, project_name = project_name, port = port) ges_util = GES4Jupyter(config, True);创建好图之后,就可以对其进行查询和分析了。GES 支持 cypher和gremlin 两种查询语言,这里的查询示例以cypher举例。 在使用 cypher 查询之前,我们先创建点索引和边索引(可直接点击GES画布右下角“创建索引”按钮)。print('开始创建点索引:') job_id = ges_util.build_vertex_index() job_result = ges_util.get_job(job_id) if 'errorCode' not in job_result: for i in range(100): if job_result['status'] == 'success': break else: time.sleep(1) job_result = ges_util.get_job(job_id) print('点索引创建完成')print('开始创建边索引:') job_id = ges_util.build_edge_index() job_result = ges_util.get_job(job_id) if 'errorCode' not in job_result: for i in range(100): if job_result['status'] == 'success': break else: time.sleep(1) job_result = ges_util.get_job(job_id) print('边索引创建完成')查询演示首先查询这份图数据的统计信息,对全图的点边数目有所了解:import json print('统计信息:') result = ges_util.summary() format_result = json.dumps(result, indent=4) print(format_result)使用查询语言获取前10条边以及其顶点,对数据有个粗略的了解(建议使用nodebook打开并连接GES服务,可以看到运行后数据可视化的效果):cypher_result = ges_util.cypher_query("match (n)-[r]->(m) return n,r,m limit 10",formats=['row','graph']); ges_util.format_cypher_result(cypher_result)对于数据中的城市,采集了部分城市的防疫政策。了解其他城市的防疫政策,有助于规划出行。print('查看各个城市的防疫政策:') statement = "match (m:city) return id(m), m.防疫政策" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result在现实中,随着采集到更多的数据,不免对原有的数据进行更新,比如新发现关联患者,就涉及到节点和边的增加操作。假设北京新增病例40,其与原有北京病例39是亲属关系。print('数据更新:') print('先查询点是否存在:') vertex_id = '北京病例40' statement = "match (p) where id(p) ="' + vertex_id + '" return p" result = ges_util.cypher_query(statement) if len(result) == 0: print('该节点不存在,增加该节点:') label = 'patient' property_name = '性别' value = '男' ges_util.add_vertex(vertex_id, label, property_name, value) print('再次查询该节点:') statement = "match (p) where id(p) ='" + vertex_id + "' return p" result = ges_util.cypher_query(statement) format_vertex_detail = json.dumps(result[0]['data'], indent=4, ensure_ascii=False) print(format_vertex_detail) print('增加关联边:') source = '北京病例39' target = '北京病例40' label = 'relation' property_name = '类型' value = '家人' ges_util.add_edge(source, target, label, property_name, value) statement = "match (p)-[r]-(m) where id(p)='" + source + "' and id(m)='" + target + "' return r" result = ges_util.cypher_query(statement) format_edge_detail = json.dumps(result[0]['data'], indent=4, ensure_ascii=False) print(format_edge_detail) else: print('该节点已存在')对数据做宏观把控,往往有助于判断疫情趋势。可以对数据从不同维度做统计。 下面会从空间(城市地区风险)、时间(增长趋势)、年龄(患者年龄分布)等维度做统计。首先通过查询语句查看本轮疫情已经波及到的城市及每个城市疫情的严重程度。print('查看有确诊病例的城市并按确诊人数排序:') statement = "match (m:city)<-[r:comfirm]-(p:patient) with m, count(p) as patientNum return id(m), patientNum order by patientNum desc" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result目前中高风险区域的判断,是以区域确诊病例数目划分的。统计不同地域的确诊病例数目,可以判断各个区域的风险。print('将图数据库中的轨迹点,按涉及的确诊病例数目多少进行统计,并从按病例数目从多到少排序:') statement = "match (m:place)<-[s:stay]-(p:patient) with m, count(p) as patientNum return id(m), patientNum order by patientNum desc limit 10" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result此外对此轮疫情患者的年龄做一个调查,以了解此轮疫情中病毒传播的特点。print('查看全国患者年龄构成并按人数排序:') statement = "match (p:patient) where p.年龄 is not null return p.年龄, count(*) as m order by m desc limit 10" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result从时间维度统计确诊病例数目,往往会反映疫情的趋势,疫情是否得到了有效的控制。city_id = '石家庄市' print('查看{}每天确诊人数:'.format(city_id)) statement = "match (p:patient)-[r:comfirm]->(m:city) where id(m)='" + city_id + "' return r.时间, count(*) order by r.时间 asc" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result除了宏观上的统计,我们会关注重点区域(如景区、机场)等,是否和确诊病例发生交集。statement = "match (n)-[r]-(m) where id(n)='胡杨林景区' return n,r,m" result = ges_util.cypher_query(statement,formats=['row','graph']) ges_util.format_cypher_result(result)此外,我们可以对重点人员进行探索,分析其接触史,查询与该患者有直接关联关系的其他患者或地点等,探查可能的传播路径和圈定可疑人群。statement = "match (n)-[r]-(m) where id(n)='额济纳旗病例1' return n,r,m" result = ges_util.cypher_query(statement,formats=['row','graph']) ges_util.format_cypher_result(result)patient_id = '额济纳旗病例1' print('查看{}的活动轨迹:'.format(patient_id)) statement = "match (p:patient)-[r]->(m:place) where id(p)='" + patient_id + "' return id(p), r.时间, type(r), id(m) order by r.时间 asc" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_resultpatient_id = '额济纳旗病例1' print('查看与{}有直接关联的患者:'.format(patient_id)) print('要么同时到过某地点:') statement = "match path = (p:patient)-[r1]-(m:place)-[r2]-(n:patient) where id(p)='" + patient_id + "' and id(p) <> id(n) and r1.时间 = r2.时间 return id(p), id(m), id(n), r1.时间, path" result = ges_util.cypher_query(statement,formats=['row','graph']) format_result = ges_util.format_cypher_result(result) format_resultprint('要么是某种亲密关系:') patient_id = '额济纳旗病例1' statement = "match path = (p:patient)-[r]-(n:patient) where id(p)='" + patient_id + "' return id(p), id(n), r.类型, path" result = ges_util.cypher_query(statement,formats=['row','graph']) format_result = ges_util.format_cypher_result(result) format_result此轮疫情源头是在额济纳旗,那么疫情又是怎么传到北京的呢?statement = "match path=(p:patient)-[]-(m:city) where id(m)='额济纳旗' and not (tostring(id(p)) contains '额济纳旗') return path" statement += " union match path=(p:patient)-[]-(m:city) where id(m)='北京' and not (tostring(id(p)) contains '北京') return path" result = ges_util.cypher_query(statement,formats=['row','graph']) ges_util.format_cypher_result(result)可以枚举所有在北京确诊的患者,通过最短路算法,检查其与额济纳旗之间的关系。import copy statement = "match (p:patient)-[r:comfirm]-(m:city) where id(m)='北京' return collect(distinct id(p))" result = ges_util.cypher_query(statement) print('北京所有的患者:') vertex_list = result["results"][0]['data'][0]['row'][0] print(vertex_list) print('查看此轮疫情入京可能的传播链:') source = '额济纳旗' chain_vid = [] for vid in vertex_list: target = vid avoid_ids = copy.deepcopy(vertex_list) avoid_ids.remove(vid) result = ges_util.filtered_shortest_path(source, target, avoid_ids) if len(result) != 0: chain_vid.append(target) path = '' for vtx_id in result: path = path + vtx_id + '-' print(path[:-1])可以对可能传播链上的节点进行一跳查询,分析传播链之间的关系。result = ges_util.cypher_query('match p=(n)--(m) where id(n) in $idlist and not (m:city) and not(m:patient and not(id(m) in $idlist)) return id(n),p',formats=['row','graph'], param={"idlist":chain_vid}) ges_util.format_cypher_result(result)我们分析了此轮疫情入京可能的传播链,会发现病例24和25是一同去额济纳旗旅游,而病例33、34、35、36和37也是一起去额济纳旗旅游的,另外病例39是因为和外省确诊患者同乘车被感染的,我们等于得到了三条独立的传播链。我们已经知道了疫情的源头(额济纳旗),通过路径搜索功能,可以探查某个患者可能的感染路径。vertex_id = '北京病例2' print('查看{}可能的感染路径:'.format(vertex_id)) result = ges_util.path_query({ "repeat": [ { "operator": "bothV", "vertex_filter": { "property_filter": { "leftvalue": { "id": "" }, "predicate": "NOTIN", "rightvalue": { "value": ["北京"] } } } } ], "until": [ { "vertex_filter": { "property_filter": { "leftvalue": { "id": "" }, "predicate": "=", "rightvalue": { "value": ["额济纳旗"] } } } } ], "times": 5, "queryType": "Tree", "vertices": ["北京病例2"] }) ges_util.format_path_query(result)import time print('连通性分析:') job_id = ges_util.connected_component() result = ges_util.get_job(job_id) if 'errorCode' not in result: for i in range(1000): if result['status'] == 'success': break else: time.sleep(1) result = ges_util.get_job(job_id) com_dict = {} for v_dict in result['data']['outputs']['community']: for key, value in v_dict.items(): statement = "match (p) where id(p)='" + key + "' return labels(p)" v_label = ges_util.cypher_query(statement)['results'][0]['data'][0]['row'][0] if v_label in ['city', 'patient']: com_dict.setdefault(value, []).append(key) print('连通分支个数 : {}'.format(len(com_dict))) for key, value in com_dict.items(): print('连通分支 ' + key + ' 中的点(仅关注城市和患者):') print(value)通过连通性分析,我们暂未发现此轮大连的疫情与额济纳旗有什么关联,可能来自不同的源头。
  • [技术干货] 利用图数据库研究 COVID-19 论文数据集
    利用图数据库研究 COVID-19 论文数据集COVID-19 大流行的形势依然很严峻,为应对 COVID-19 的传播及其对我们的影响,AI2等提供了一份 COVID-19 开放研究数据集(CORD-19)。CORD-19 数据集是关于冠状病毒的文献集,提供了超过50万篇学术论文的相关信息。我们研究了这份数据集,并用图数据库去组织和挖掘这份数据集蕴含的信息。针对 CORD-19,我们设计了以下的图模型。图模型中包含了两类点,分别是 paper 和 author,也包含了两类边,分别是 write 和 reference,下面的表格说明了它们的详细情况。数据集原始数据集位于COVID-19 开放研究数据集,根据我们设计的图模型重新组织的数据可从此处下载,这份图数据包含70万+ paper 点,173万+ author 点,67万+ reference 边,443万+ write 边。创图利用华为云图数据库 GES 对以上数据集进行探索,需要先在 GES 上创图,GES 创图的详细流程见华为云图引擎服务 GES 实战——创图。使用 GES 查询的预置条件下面封装了使用 GES 查询的预置条件,包括配置相关参数和对所调用 API 接口的封装,如果你对这些不感兴趣,可直接运行而不需要了解细节,这对理解后续具体查询没有影响。配置相关参数的定义。import requests import json import time import pandas as pd pd.set_option('display.max_rows', None) pd.set_option('display.max_columns', None) pd.set_option('display.width', None) pd.set_option('display.max_colwidth', -1) class config(object): def __init__(self, iam_url, user_name, password, domain_name, project_name, eip, project_id, graph_name): self.iam_url = iam_url self.user_name = user_name self.password = password self.domain_name = domain_name self.project_name = project_name self.eip = eip self.project_id = project_id self.graph_name = graph_name self.token = self.get_token() def get_token(self): url = ('https://{}/v3/auth/tokens').format(self.iam_url) headers = {'Content-Type': 'application/json;charset=utf8'} body = json.dumps({"auth": { "identity": { "methods": ["password"], "password": { "user": { "name": self.user_name, "password": self.password, "domain": { "name": self.domain_name } } } }, "scope": { "project": { "name": self.project_name } } } }) r = requests.post(url=url, data=body, verify=False, headers=headers) return r.headers['x-subject-token']config类的参数都是与调用 GES 服务有关的参数,依次为“终端节点”、“IAM 用户名”、“IAM 用户密码”、“IAM 用户所属账户名”、“项目名称”、“公网访问地址”、“项目ID”、“token值”,其获取方式可参考调用 GES 服务业务面 API 相关参数的获取。下面定义了与查询相关的接口。class GESFunc(object): def __init__(self, eip, project_id, graph_name, token): self.eip = eip self.project_id = project_id self.graph_name = graph_name self.headers = {'X-Auth-Token': token, 'Content-Type': 'application/json'} def build_vertex_index(self): url = ('http://{}/ges/v1.0/{}/graphs/{}/indices').format(self.eip, self.project_id, self.graph_name) body = json.dumps({ "indexName": "cypher_vertex_index", "indexType": "GlobalCompositeVertexIndex", "hasLabel": "true", "indexProperty": [] }) r = requests.post(url=url, data=body, headers=self.headers) return r.json()['jobId'] def build_edge_index(self): url = ('http://{}/ges/v1.0/{}/graphs/{}/indices').format(self.eip, self.project_id, self.graph_name) body = json.dumps({ "indexName": "cypher_edge_index", "indexType": "GlobalCompositeEdgeIndex", "hasLabel": "true", "indexProperty": [] }) r = requests.post(url=url, data=body, headers=self.headers) return r.json()['jobId'] def get_job(self, job_id): url = ('http://{}/ges/v1.0/{}/graphs/{}/jobs/{}/status').format(self.eip, self.project_id, self.graph_name, job_id) r = requests.get(url=url, headers=self.headers) output = r.json() return output def summary(self): url = ('http://{}/ges/v1.0/{}/graphs/{}/summary?label_details=true').format(self.eip, self.project_id, self.graph_name) r = requests.get(url=url, headers=self.headers) output = r.json()['data'] return output def cypher_query(self, statement): url = ('http://{}/ges/v1.0/{}/graphs/{}/action?action_id=execute-cypher-query').format(self.eip, self.project_id, self.graph_name) body = json.dumps({ "statements": [ { "statement": statement, "parameters": {}, "resultDataContents": [ "row" ], "includeStats": False } ] }) r = requests.post(url=url, data=body, headers=self.headers) output = r.json()['results'] return output def format_cypher_result(self, json_obj): for x in json_obj: columns = x["columns"] data = x["data"] rows = [] for y in data: rows.append(y["row"]) return pd.DataFrame(rows, columns=columns)创建好图之后,就可以对其进行查询和分析了。# 需填入参数 iam_url = '' user_name = '' password = '' domain_name = '' project_name = '' eip = '' project_id = '' graph_name = '' config = config(iam_url, user_name, password, domain_name, project_name, eip, project_id, graph_name) ges_util = GESFunc(config.eip, config.project_id, config.graph_name, config.token)/home/ma-user/anaconda3/lib/python3.6/site-packages/urllib3/connectionpool.py:858: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings InsecureRequestWarning)GES 支持 cypher 查询语言,后续的查询示例使用的是 cypher 查询语言。在使用 cypher 查询之前,我们先创建点索引和边索引。print('开始创建点索引:') job_id = ges_util.build_vertex_index() job_result = ges_util.get_job(job_id) if 'errorCode' not in job_result: for i in range(100): if job_result['status'] == 'success': break else: time.sleep(1) job_result = ges_util.get_job(job_id) print('点索引创建完成')开始创建点索引: 点索引创建完成print('开始创建边索引:') job_id = ges_util.build_edge_index() job_result = ges_util.get_job(job_id) if 'errorCode' not in job_result: for i in range(100): if job_result['status'] == 'success': break else: time.sleep(1) job_result = ges_util.get_job(job_id) print('边索引创建完成') 开始创建边索引: 边索引创建完成查询演示下面,我们可以书写并运行标准的 cypher 语言,对这份图数据进行探索了。以下是部分图数据的可视化展示。查询这份图数据的统计信息:print('统计信息:') result = ges_util.summary() res_str = json.dumps(result, indent=4) print(res_str)统计信息: { "vertexNum": 2449792, "labelDetails": { "labelInVertex": { "paper": 712464, "author": 1737328 }, "labelInEdge": { "reference": 670627, "write": 4434198 } }, "edgeNum": 5104825 }列举若干 paper 的信息:print('查询 papers:') statement = "match (n:paper) return n.title limit 5" paper_result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(paper_result) format_result查询 papers:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }n.title0Clinical features of culture-proven Mycoplasma pneumoniae infections at King Abdulaziz University Hospital, Jeddah, Saudi Arabia1Nitric oxide: a pro-inflammatory mediator in lung disease?2Surfactant protein-D and pulmonary host defense3Role of endothelin-1 in lung disease4Gene expression in epithelial cells in response to pneumovirus infection查询某 paper 的 authors:print('查询某 paper 的 authors:') paper = paper_result[0]['data'][1]['row'][0] statement = "match (n:paper)<--(m:author) WHERE n.title = '" + paper + "' return id(m)" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询某 paper 的 authors:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }id(m)0Vliet Albert van der1Eiserich Jason P2Cross Carroll E查询某 paper 被引用的次数:print('查询某 paper 被引用次数:') paper = paper_result[0]['data'][2]['row'][0] statement = "match (n:paper)<--(m:paper) WHERE n.title = '" + paper + "' return count(m)" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询某 paper 被引用次数:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }count(m)05查询某 paper 与哪些 paper 被联合引用及其次数:print('查询某 paper 与哪些 paper 被联合引用及其次数:') paper = paper_result[0]['data'][3]['row'][0] statement = "match (n:paper)<--(p:paper)-->(m:paper) WHERE n.title = '" + paper + "' return m.title, count(*) as p order by p desc limit 5" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询某 paper 与哪些 paper 被联合引用及其次数:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }m.titlep0Clinical course and risk factors for mortality of adult inpatients with COVID-19 in Wuhan, China: a retrospective cohort study21The use of corticosteroid as treatment in SARS was associated with adverse outcomes: a retrospective cohort study12Covid-19: ibuprofen should not be used for managing symptoms, say doctors and scientists13SARS-CoV2: should inhibitors of the renin-angiotensin system be withdrawn in patients with COVID-19?14Network-based drug repurposing for novel coronavirus 2019-nCoV/SARS-CoV-21查询标题带关键字"Virus"的 paper 数量:print('查询标题带关键字"Virus"的 paper 数量:') statement = "MATCH (p:paper) WHERE p.title IS NOT NULL AND p.title CONTAINS('Virus') RETURN count(p)" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询标题带关键字"Virus"的 paper 数量:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }count(p)08354查询标题带关键字"Virus"的 paper,并按发表日期排序:print('查询标题带关键字"Virus"的 paper,并按发表日期排序:') statement = "MATCH (p:paper) WHERE p.publish_time IS NOT NULL AND p.title IS NOT NULL AND p.title " \ "CONTAINS('Virus') RETURN p.title, p.publish_time ORDER BY p.publish_time DESC LIMIT 5" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询标题带关键字"Virus"的 paper,并按发表日期排序:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }p.titlep.publish_time0Rates of Coinfection Between SARS-CoV-2 and Other Respiratory Viruses in Korea20221Predict Mortality in Patients Infected with COVID-19 Virus Based on Observed Characteristics of the Patient using Logistic Regression2021-12-312P140 TeCC (TeleMedicine, Cystic Fibrosis, Corona-Virus) study in a previous telemedicine-naive centre: clinical challenges, outcomes, and user experience in the first six months of a global pandemic2021-12-313Virus structure and structure-based antivirals2021-12-314Virus-associated ribozymes and nano carriers against COVID-19.2021-12-01查询 paper 被引用的次数并以此排序:print('查询 paper 被引用的次数并以此排序:') statement = "match (m:paper)<--(p:paper) with m, count(p) as citedNum return m.title, citedNum order by citedNum desc limit 5" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询 paper 被引用的次数并以此排序:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }m.titlecitedNum0Publisher's Note54361Clinical course and risk factors for mortality of adult inpatients with COVID-19 in Wuhan, China: a retrospective cohort study38242A pneumonia outbreak associated with a new coronavirus of probable bat origin36773Epidemiological and clinical characteristics of 99 cases of 2019 novel coronavirus pneumonia in Wuhan, China: a descriptive study30914A new coronavirus associated with human respiratory disease in China1975列举若干 author 的信息:print('查询 authors:') statement = "match (n:author) return id(n) limit 5" author_result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(author_result) format_result查询 authors:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }id(n)0Madani Tariq A1Al-Ghamdi Aisha A2Vliet Albert van der3Eiserich Jason P4Cross Carroll E查询某 author 的合作者及合作次数:print('查询某 author 的合作者及合作次数:') author = author_result[0]['data'][0]['row'][0] statement = "match (n:author)-->(p:paper)<--(m:author) WHERE id(n) = '" + author + "' return id(m), count(*) as p order by p desc limit 5" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询某 author 的合作者及合作次数:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }id(m)p0Petersen Eskild41Zumla Alimuddin42Drosten Christian43Hui David S44I Azhar Esam4查询某 author 论文被引用次数:print('查询某 author 论文被引用次数:') author = author_result[0]['data'][0]['row'][0] statement = "match (m:author)-->(p:paper)<--(n:paper) where id(m) = '" + author + "' return count(n)" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询某 author 论文被引用次数:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }count(n)09查询论文互相引用情况:print('查询论文互相引用情况:') statement = "match (m:paper)-->(p:paper)-->(n:paper) where id(m) <> id(p) and id(m) = id(n) return m,p limit 10" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询论文互相引用情况:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }mp0{'journal': 'Rev Mal Respir', 'publish_time': '2008-01-03', 'title': 'Chronique pour une pandémie grippale annoncée'}{'journal': 'Revue des Maladies Respiratoires', 'publish_time': '2004-12-31', 'title': 'La communication sur le SRAS : un outil essentiel de santé publique'}1{'journal': 'Intensive Care Med', 'publish_time': '2020-03-23', 'title': 'Acute respiratory distress syndrome-attributable mortality in critically ill patients with sepsis'}{'journal': 'J Biomed Sci', 'publish_time': '2003-10-18', 'title': 'Acute respiratory distress syndrome'}2{'journal': None, 'publish_time': '2020-11-19', 'title': 'Curvature domains in V4 of macaque monkey'}{'journal': None, 'publish_time': '2020-11-19', 'title': 'Curvature-processing domains in primate V4'}3{'journal': None, 'publish_time': '2020-11-19', 'title': 'Curvature-processing domains in primate V4'}{'journal': None, 'publish_time': '2020-11-19', 'title': 'Curvature domains in V4 of macaque monkey'}4{'journal': 'Theor Med Bioeth', 'publish_time': '2020-12-17', 'title': 'Philosophical investigations into the essence of pediatric suffering'}{'journal': 'Theor Med Bioeth', 'publish_time': '2021-01-05', 'title': 'What we talk about when we talk about pediatric suffering'}5{'journal': 'Theor Med Bioeth', 'publish_time': '2020-12-17', 'title': 'Philosophical investigations into the essence of pediatric suffering'}{'journal': 'Theor Med Bioeth', 'publish_time': '2020-12-07', 'title': 'Relational suffering and the moral authority of love and care'}6{'journal': 'Theor Med Bioeth', 'publish_time': '2020-12-17', 'title': 'Philosophical investigations into the essence of pediatric suffering'}{'journal': 'Theor Med Bioeth', 'publish_time': '2020-12-07', 'title': 'Our suffering and the suffering of our time'}7{'journal': 'PLoS One', 'publish_time': '2021-04-01', 'title': 'Prevalence of depression, anxiety and associated factors among school going adolescents in Bangladesh: Findings from a cross-sectional study'}{'journal': 'BMC Psychiatry', 'publish_time': '2021-05-25', 'title': 'Prevalence and correlates of anxiety and depression in frontline healthcare workers treating people with COVID-19 in Bangladesh'}8{'journal': 'PLoS Comput Biol', 'publish_time': '2020-09-21', 'title': 'Fast estimation of time-varying infectious disease transmission rates'}{'journal': 'PLoS Biol', 'publish_time': '2020-12-21', 'title': 'Patterns of smallpox mortality in London, England, over three centuries'}9{'journal': 'Commun Biol', 'publish_time': '2021-04-22', 'title': 'Structure and assembly of double-headed Sendai virus nucleocapsids'}{'journal': 'PLoS Pathog', 'publish_time': '2021-07-16', 'title': 'CryoEM structure of the Nipah virus nucleocapsid assembly'}
  • [技术干货] 电商风控图模板
    电商风控图模板互联网电商为扩张业务、推广产品,会采用奖励、返点、打折等手段实现用户和商户裂变。例如,商品的链接在商户或客户间转发时将会附带一标识ID,若商品被购买,具有该标识ID的商户或用户均会得到一笔奖励。而在裂变过程中,存在薅羊毛、虚开账号等损害平台或商家利益的行为。基于图模型的互联网电商风控,将提供客群管控、羊毛党发现等解决方案,有效帮助客户降低损失。 图模型以下是电商风控图模型元数据。 创建图引擎实例创建图引擎实例的详细流程可参考链接:cid:link_1Notebook源码配置参数的定义。config类的属性依次为“终端节点”、“IAM 用户名”、“IAM 用户密码”、“IAM 用户所属账户名”、“项目名称”、“公网访问地址”、“项目ID”、“token值”,其获取方式可参考链接:https://bbs.huaweicloud.com/blogs/296930。import requests import json import time class config(object): def __init__(self, iam_url, user_name, password, domain_name, project_name, eip, project_id, graph_name): self.iam_url = iam_url self.user_name = user_name self.password = password self.domain_name = domain_name self.project_name = project_name self.eip = eip self.project_id = project_id self.graph_name = graph_name self.token = self.get_token() def get_token(self): url = ('https://{}/v3/auth/tokens').format(self.iam_url) headers = {'Content-Type': 'application/json;charset=utf8'} body = json.dumps({"auth": { \ "identity": { \ "methods": ["password"], \ "password": { \ "user": { \ "name": self.user_name, \ "password": self.password, \ "domain": { \ "name": self.domain_name \ } \ } \ } \ }, \ "scope": { \ "project": { \ "name": self.project_name \ } \ } \ } \ }) r = requests.post(url=url, data=body, verify=False, headers=headers) return r.headers['x-subject-token']调用API接口的定义。class GESFunc(object): def __init__(self, eip, project_id, graph_name, token): self.graph_name = graph_name self.headers = {'X-Auth-Token': token, 'Content-Type': 'application/json'} self.project_id = project_id self.eip = eip def connected_component(self): url = ('http://{}/ges/v1.0/{}/graphs/{}/subgraphs/action?action_id=execute-algorithm').format(self.eip, self.project_id, self.graph_name) subgraph_creator = { "name": "filtered", "parameters": { "vertex_filter": { "property_filter": { "leftvalue": { "label_name": "labelName" }, "predicate": "=", "rightvalue": { "value": "store" } } } }} parameters = {} body = json.dumps({"algorithmName": "connected_component", "graphName": self.graph_name, "subgraphCreator": subgraph_creator, "parameters": parameters}) r = requests.post(url=url, data=body, headers=self.headers) return r.json()['jobId'] def get_job(self, job_id): url = ('http://{}/ges/v1.0/{}/graphs/{}/jobs/{}/status').format(self.eip, self.project_id, self.graph_name, job_id) r = requests.get(url=url, headers=self.headers) output = r.json()['data']['outputs'] return output def delete_edge(self, source_vertex, target_vertex): url = ('http://{}/ges/v1.0/{}/graphs/{}/edges?source={}&target={}').format(self.eip, self.project_id, self.graph_name, source_vertex, target_vertex) r = requests.delete(url=url, headers=self.headers) output = r.json()['result'] return output def add_edge(self, source_vertex, target_vertex, label): url = ('http://{}/ges/v1.0/{}/graphs/{}/edges').format(self.eip, self.project_id, self.graph_name) body = json.dumps({"source": source_vertex, "target": target_vertex, "label": label }) time.sleep(2) r = requests.post(url=url, data=body, headers=self.headers) time.sleep(2) output = r.json()['result'] return output def get_cypher(self, statement, idList): url = ('http://{}/ges/v1.0/{}/graphs/{}/action?action_id=execute-cypher-query').format(self.eip, self.project_id, self.graph_name) statements = [{ "statement": statement, "resultDataContents": ["row"], "parameters": {"idList": idList}, "includeStats": False }] body = json.dumps({"statements": statements}) r = requests.post(url=url, data=body, headers=self.headers) output = r.json()['results'] return output def get_community_info(self, output): community_list = output['community'] community_dict = {} for community in community_list: vertex_id = list(community.keys())[0] community_id = community[vertex_id] if community_id not in community_dict: component = set() component.add(vertex_id) community_dict[community_id] = component else: community_dict[community_id].add(vertex_id) return community_dict def community_change_info(self, monitor_id, old_community_info, new_community_info): monitor_set = set() new_monitor_set = set() change_id_list = [] for community_id in old_community_info: if monitor_id in old_community_info[community_id]: monitor_set = old_community_info[community_id] for community_id in new_community_info: if monitor_id in new_community_info[community_id]: new_monitor_set = new_community_info[community_id] for id in monitor_set: if id not in new_monitor_set: change_id_list.append(id) return change_id_list创建好图之后,就可以对其进行查询和分析了。config = config('', '', '', '', '', '', '', '') test = GESFunc(config.eip, config.project_id, config.graph_name, config.token)羊毛党发现在电商中,可能存在客户“薅羊毛”的行为。例如,电商平台为用户裂变,对新注册账号会有一些奖励活动,用户为获得更多奖励会在平台上注册多个账号。然而,过多的虚假账号会造成电商平台成本上升,利润下降。在本案例中,首先,通过电话号码(1XX725080460)和地址ID(0f1061e6-e903-11eb-8084-643e8cb8138c)找出马甲客户,该客户为给定电话号码和地址ID的共同邻居,共找出两个节点VRG 7和VRG 48。然后,通过VRG 7和VRG 48反方向查询可找到客户节点VRG 8和VRG 49。通过对VRG 7、VRG 48、VRG 8和VRG 49相互关联性的分析,可研判VRG 8和VRG 49是否是马甲客户的帮凶,还是普通受欺诈的无辜百姓。idList = []statement = "match (a)-[r1]->(common)<-[r2]-(b) where id(a)='0f1061e6-e903-11eb-8084-643e8cb8138c' and id(b)='1XX725080460' return common"result = test.get_cypher(statement, idList)通过地址和电话信息发现羊毛党可能为:print(result)[{'columns': ['common'], 'data': [{'row': [{'billing_street': '李新庄镇', 'billing_province': '山东省', 'billing_city': '菏泽市', 'email_address': 'anonymized@corporation.com', 'identity_id': '623062109986XXXX', 'last_activity_date': '2018-10-24', 'customer_name': 'VRG 7', 'billing_district': '单县', 'created_date': '2018-10-24'}], 'meta': [{'id': '0011R00001z10NgQAI', 'type': 'node', 'labels': ['customer']}]}, {'row': [{'billing_street': '李新庄镇', 'billing_province': '山东省', 'billing_city': '菏泽市', 'email_address': 'anonymized@corporation.com', 'identity_id': '623062109986XXXX', 'last_activity_date': '2018-10-24', 'customer_name': 'VRG 48', 'billing_district': '单县', 'created_date': '2018-10-24'}], 'meta': [{'id': '0013600000FSw9KAAT', 'type': 'node', 'labels': ['customer']}]}]}]statement = "match (a)-[r1]->(common)<-[r2]-(b), (common)-[r3]->(c) where id(a)='0f1061e6-e903-11eb-8084-643e8cb8138c' and id(b)='1XX725080460' return c"result = test.get_cypher(statement, idList)与该羊毛党具有较大相关性的客户为:print(result)[{'columns': ['c'], 'data': [{'row': [{'billing_street': '东二营镇', 'billing_province': '天津市', 'billing_city': '市辖区', 'email_address': 'anonymized@corporation.com', 'identity_id': '623062573189XXXX', 'last_activity_date': '2018-10-24', 'customer_name': 'VRG 8', 'billing_district': '蓟州区', 'created_date': '2018-10-24'}], 'meta': [{'id': '0011R00001z1109QAA', 'type': 'node', 'labels': ['customer']}]}, {'row': [{'billing_street': '四平市铁东区城东乡', 'billing_province': '吉林省', 'billing_city': '四平市', 'email_address': 'anonymized@corporation.com', 'identity_id': '6226632105938XXXX', 'last_activity_date': '2018-08-02', 'customer_name': 'VRG 49', 'billing_district': '铁东区', 'created_date': '2016-04-10'}], 'meta': [{'id': '0013600000FSwALAA1', 'type': 'node', 'labels': ['customer']}]}]}]time.sleep(5)社群管控商户的裂变呈现树状结构,较为顶层的商户往往体量较大,收益较高。然而,底层商户的频繁变动可能对顶层商户的收益产生影响。平台希望管控好底层商户从而实现顶层商户的稳定。本案例中,针对给定的顶层商户(0031R00001vSBTkQAO),监控其发展的商户是否较为稳定。首先,检测出给定商户所在的社群,然后,通过删边操作模拟随时间变化的下线流失,并再次检测给定商户社群,最后,给出变化的商户的名单。通过检测发现,0031R00001vRlHbQAK、0031R00001vRlYiQAK、0031R00001uqjUQQAY、0031R00001uoQr5QAE、0031R00001vS9WcQAK、0031R00001vSB7DQAW这六个下线脱离了与给定顶层商户的联系并自立门户,将对给定顶层商户产生一定的经济损失。job_id = test.connected_component()result = test.get_job(job_id)old_community_info = test.get_community_info(result)monitor_id = '0031R00001vSBTkQAO'删边模拟顶层客户下线的流失:result = test.delete_edge('Samantha-Mars', '0031R00001uoQr5QAE')job_id = test.connected_component()result = test.get_job(job_id)test.add_edge('Samantha-Mars', '0031R00001uoQr5QAE', 'transfer')new_community_info = test.get_community_info(result)change_id_list = test.community_change_info(monitor_id, old_community_info, new_community_info)变化的商户为:print(change_id_list)['0031R00001vRlYiQAK', '0031R00001vSB7DQAW', '0031R00001uqjUQQAY', '0031R00001vS9WcQAK', '0031R00001vRlHbQAK', '0031R00001uoQr5QAE']
  • [技术干货] 教育知识图谱
    教育知识图谱背景为了解决教师在教学过程中知识框架梳理、组卷策略等问题, 以及学生在学习过程中认知过载、学习迷航等问题,知识图谱被引入教育行业。针对这些需求,本文以高中数学学科知识图谱为例,提供了教育图谱在知识导航、组卷策略等方面的应用示例。数据建模首先我们需要构建教育图谱的图数据模型。图数据模型中的实体包括知识点和题目,实体关系包括知识点之间的包含关系以及知识点和题目之间的关联关系,具体的图模型见下图:图模型中包含了两类点和两类边,元信息说明如下: 数据集本文采集了高中数学几乎所有的知识点和部分题目信息,根据设计的图模型对数据格式进行转换,转换后的数据可从此处下载。这份数据包含7W+个点,14W+条边。创图本文将使用华为云图数据库 GES 对以上数据集进行探索和演示,我们需要先在 GES 中创图并将以上数据集导入,详细指导流程可参见华为云图引擎服务 GES 实战——创图。使用 GES 查询的预置条件下面文件封装了使用 GES 查询的预置条件,包括配置相关参数和对所调用 API 接口的封装。import moxing as mox mox.file.copy('obs://obs-aigallery-zc/GES/edu_knowledge_graph/config.py', 'config.py')config类的参数都是与调用 GES 服务有关的参数,依次为“终端节点”、“IAM 用户名”、“IAM 用户密码”、“IAM 用户所属账户名”、“项目名称”、“公网访问地址”、“项目ID”、“token值”,其获取方式可参考调用 GES 服务业务面 API 相关参数的获取。下面文件封装了与查询相关的接口。mox.file.copy('obs://obs-aigallery-zc/GES/edu_knowledge_graph/ges_func.py','ges_func.py')创建好图之后,就可以对其进行查询和分析了。import time import json from config import config from ges_func import GESFunc # 需填入参数 iam_url = '' user_name = '' password = '' domain_name = '' project_name = '' eip = '' project_id = '' graph_name = '' config = config(iam_url, user_name, password, domain_name, project_name, eip, project_id, graph_name) ges_util = GESFunc(config.eip, config.project_id, config.graph_name, config.token)GES 支持 cypher 查询语言,后续的查询示例使用到 cypher 查询语言。在使用 cypher 查询之前,我们先创建点索引和边索引(可直接点击GES画布右下角“创建索引”按钮)。print('开始创建点索引:') job_id = ges_util.build_vertex_index() job_result = ges_util.get_job(job_id) if 'errorCode' not in job_result: for i in range(100): if job_result['status'] == 'success': break else: time.sleep(1) job_result = ges_util.get_job(job_id) print('点索引创建完成')print('开始创建边索引:') job_id = ges_util.build_edge_index() job_result = ges_util.get_job(job_id) if 'errorCode' not in job_result: for i in range(100): if job_result['status'] == 'success': break else: time.sleep(1) job_result = ges_util.get_job(job_id) print('边索引创建完成')查询演示查询这份图数据的统计信息:print('统计信息:') result = ges_util.summary() format_result = json.dumps(result, indent=4) print(format_result)统计信息: { "vertexNum": 70528, "labelDetails": { "labelInVertex": { "knowledge_point": 2163, "exercises": 68365 }, "labelInEdge": { "include": 2145, "related": 138742 } }, "edgeNum": 140887 }可以看到,这份知识图谱包含2000多个知识点和接近七万的题目。知识点图谱输入某个知识点,将展示该知识点所有下游知识点。knowledge_point = '集合的基本运算' statement = "g.V('{}').repeat(outE().otherV().hasLabel('knowledge_point')).emit().path()".format(knowledge_point) result = ges_util.gremlin_query(statement)['results'] format_result = json.dumps(result, indent=4, ensure_ascii=False) # print(format_result)题目知识点追踪输入题目,将追踪到该题目考察的所有知识点。exercises_id = '1' statement = "g.V('{}').repeat(inE().otherV().hasLabel('knowledge_point')).emit().path()".format(exercises_id) result = ges_util.gremlin_query(statement)['results'] format_result = json.dumps(result, indent=4, ensure_ascii=False) # print(format_result)知识点关系查询knowledge_point_1 = "集合" knowledge_point_2 = "空集" result = ges_util.n_paths(knowledge_point_1, knowledge_point_2) format_result = json.dumps(result, indent=4, ensure_ascii=False) # print(format_result)某知识点包含哪些知识点knowledge_point = "集合" statement = "match (n)-->(m:knowledge_point) where id(n) = '{}' return id(m)".format(knowledge_point) result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) print(format_result)与某知识点相关的题目的数量knowledge_point = "描述法表示集合" statement = "match (n)-->(m:exercises) where id(n) = '{}' return count(m)".format(knowledge_point) result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) print(format_result)包含某知识点的题型分布knowledge_point = "描述法表示集合" statement = "match (n)-->(m:exercises) where id(n) = '{}' return m.type, count(*)".format(knowledge_point) result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) print(format_result) m.type count(*) 0 解答题 127 1 单选题 277 2 多选题 15 3 填空题 118 4 双空题 13 5 概念填空 4某知识点会和哪些其他知识点一起考察knowledge_point = "描述法表示集合" statement = "match (n)-->(m:exercises)<--(p:knowledge_point) where id(n) = '{}' return id(p), count(*)".format(knowledge_point) result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) print(format_result)自动组卷为了考察学生的学习情况,老师需要组织一套试卷,该例子中,限制了考察的知识点范围,以及各个题型的题目数量,skip的含义是在所有满足条件的试卷中选择某一套试卷。knowledge_point = "描述法表示集合" single_choice_num = 4 multiple_choice_num = 2 gap_filling_num = 2 free_response_question_num = 2 skip = 1 exercises_list = ges_util.test_paper_composition(single_choice_num, multiple_choice_num, gap_filling_num, free_response_question_num, knowledge_point, skip) format_result = ges_util.format_cypher_result(exercises_list) print(format_result)拿到一份试卷后,我们可以看看这套试卷整体的难易程度,当然也可以将试卷的难易程度作为组织试卷的限制条件。print('试题的平均通过率(难度):') ave_pass_rate = ges_util.ave_pass_rate(exercises_list, 10) print(ave_pass_rate)
  • [技术干货] 电网供电管理
    电网供电管理介绍配电网是电力系统中起重新分配电能作用的网络,直接面向电力用户。据统计,超过85%的故障停电是由于配电网故障造成的。所以需要加强配电网的管理,从而最大程度减少电网故障对用户的影响。在发生停电之前,提高供电的可靠性,当不可避免地发生停电时,需要一个有效的方式尽快恢复供电。配电网是一个天然的图数据模型----导电设备、设备容器、物理连接节点等可以作为节点,线路、连接关系可以看作边。 我们通过一个案例介绍华为图引擎GES在配电网几个典型场景上的应用。图模型以下是配电网的图数据模型。 下面详细列出了用到的样例数据的点类、边类及其附带的属性。 说明:此处基于电网CIM/E模型设计配电网图数据模型,通过联结节点将导电设备连接起来,导电设备包括busbarsection(母线))、energyconsumer(用户点)、fuse(熔断器)等,aclinesegment(导线段)连接的是两个不同的联结节点,resistancefreeline(无电阻导线)连接的是导电设备和联结节点,另外,导电设备包含设备类型(PSRType)、电压等级(BaseVoltage)、开关状态(status)、所属设备容器(container)等属性。样例数据集可点击此处下载原始数据集。样例数据集中提供了两条线路,其中一条线路的一部分可视化展示如下:创图创图的详细流程见华为云图引擎服务 GES 实战——创图。源码配置参数的定义。import requests import json import time class config(object): def __init__(self, iam_url, user_name, password, domain_name, project_name, eip, project_id, graph_name): self.iam_url = iam_url self.user_name = user_name self.password = password self.domain_name = domain_name self.project_name = project_name self.eip = eip self.project_id = project_id self.graph_name = graph_name self.token = self.get_token() def get_token(self): url = ('https://{}/v3/auth/tokens').format(self.iam_url) headers = {'Content-Type': 'application/json;charset=utf8'} body = json.dumps({"auth": { \ "identity": { \ "methods": ["password"], \ "password": { \ "user": { \ "name": self.user_name, \ "password": self.password, \ "domain": { \ "name": self.domain_name \ } \ } \ } \ }, \ "scope": { \ "project": { \ "name": self.project_name \ } \ } \ } \ }) r = requests.post(url=url, data=body, verify=False, headers=headers) return r.headers['x-subject-token']config类的属性依次为“终端节点”、“IAM 用户名”、“IAM 用户密码”、“IAM 用户所属账户名”、“项目名称”、“公网访问地址”、“项目ID”、“token值”,其获取方式可参考调用 GES 服务业务面 API 相关参数的获取。调用API接口的定义。class GESFunc(object): def __init__(self, eip, project_id, graph_name, token): self.eip = eip self.project_id = project_id self.graph_name = graph_name self.headers = {'X-Auth-Token': token, 'Content-Type': 'application/json'} def filter_query_vertex(self): url = ('http://{}/ges/v1.0/{}/graphs/{}/action?action_id=execute-gremlin-query').format(self.eip, self.project_id, self.graph_name) command = "g.V().has('container','变电站')" body = json.dumps({"command": command}) r = requests.post(url=url, data=body, headers=self.headers) output = r.json()['data'] return output def query_scope(self, id): url = ('http://{}/ges/v1.0/{}/graphs/{}/action?action_id=execute-algorithm').format(self.eip, self.project_id, self.graph_name) body = json.dumps({"executionMode": "sync", "algorithmName": "k_hop", "parameters": {"k": 100, "source": id, "mode": "ALL"}}) r = requests.post(url=url, data=body, headers=self.headers) return r.json()['data']['outputs'] def query_consumer(self, id_list): url = ('http://{}/ges/v1.0/{}/graphs/{}/action?action_id=filtered-query').format(self.eip, self.project_id, self.graph_name) body = json.dumps({"executionMode": "sync", "filters": [ { "operator": "vertex", "vertex_filter": { "property_filter": { "leftvalue": { "label_name": "labelName" }, "predicate": "=", "rightvalue": { "value": "energyconsumer" } } } } ], "by": [ { "id": True, "properties": True, "selectedProperties": [ "name" ] } ], "full_path": False, "vertices": id_list}) r = requests.post(url=url, data=body, headers=self.headers) return r.json()['data'] def outage_analysis(self, id): url = ('http://{}/ges/v1.0/{}/graphs/{}/action?action_id=path-query').format(self.eip, self.project_id, self.graph_name) body = json.dumps( { "repeat": [ { "operator": "bothV" } ], "until": [ { "vertex_filter": { "property_filter": { "leftvalue": { "property_filter": { "leftvalue": { "label_name": "labelName" }, "predicate": "=", "rightvalue": { "value": "fuse" } } }, "predicate": "&", "rightvalue": { "property_filter": { "leftvalue": { "property_name": "status" }, "predicate": "=", "rightvalue": { "value": "open" } } } } } } ], "queryType": "Tree", "emit": False, "times": 20, "by": [ { "properties": True, "selectedProperties": ["status"] } ], "vertices": [ id ] } ) r = requests.post(url=url, data=body, headers=self.headers) return r.json()['data'] def query_vertex(self, id): url = ('http://{}/ges/v1.0/{}/graphs/{}/action?action_id=execute-gremlin-query').format(self.eip, self.project_id, self.graph_name) command = "g.V(" + id + ")" body = json.dumps({"command": command}) r = requests.post(url=url, data=body, headers=self.headers) output = r.json()['data'] return output def annular_electric_supply(self): url = ('http://{}/ges/v1.0/{}/graphs/{}/action?action_id=execute-algorithm').format(self.eip, self.project_id, self.graph_name) body = json.dumps({ "algorithmName": "circle_detection", "parameters": { "directed": False, "max_length": 15 }}) r = requests.post(url=url, data=body, headers=self.headers) return r.json()['jobId'] def get_job(self, job_id): url = ('http://{}/ges/v1.0/{}/graphs/{}/jobs/{}/status').format(self.eip, self.project_id, self.graph_name, job_id) r = requests.get(url=url, headers=self.headers) output = r.json()['data']['outputs'] return output def query_interconnection_switch(self, id_list): url = ('http://{}/ges/v1.0/{}/graphs/{}/action?action_id=filtered-query').format(self.eip, self.project_id, self.graph_name) body = json.dumps({ "executionMode": "sync", "filters": [ { "operator": "vertex", "vertex_filter": { "property_filter": { "leftvalue": { "property_name": "name" }, "predicate": "SUBSTRING", "rightvalue": { "value": "联络开关" } } } } ], "by": [ { "id": True, "properties": True, "selectedProperties": [ "name" ] } ], "full_path": False, "vertices": id_list}) r = requests.post(url=url, data=body, headers=self.headers) return r.json()['data']创建好图之后,就可以对其进行查询和分析了。# 需填入参数 config = config(" ", " ", " ", " ", " ", " ", " ", " ") test = GESFunc(config.eip, config.project_id, config.graph_name, config.token)场景演示供电范围查询指查询指定设备所承担供电范围内的其他设备,这个需要查询的设备可以是站内变压器,也可以是变电站。这有助于设备管理,也能够清楚地知道由于变电站的故障所造成的停电范围,早作防范。一般更关心的是变电站供电的用户。以上图例中,如果断开间隔开关1,其下游所有的用户都会处于停电状态。下面是一个具体的操作示例。首先查找位于变电站中的母线。(母线是各级电压配电装置的中间环节,它的作用是汇集、分配和传送电能。)result = test.filter_query_vertex() print('the busbarsections in substations:') print(result)从结果看,我们得到了两条母线,选取其中一条母线,查看其供电范围。result = test.query_scope("1157") print('the equipments in the scope of supply of "1157":') print(result)得到的结果包含供电范围内所有设备的ID。 我们比较关心的是供电范围内涉及的用户。id_list = result["vertices"] result = test.query_consumer(id_list) print('the consumers in the scope of supply of "1157":') print(result)另外,如果该供电范围内有重点用户,就需要检查、调整供配电方案,例如对重点用户提供市电和发电机供电切换供电方案。停电故障分析关于停电,如果是非计划停电,我们需要知道的是故障出在哪里,尽快去修复。GES无论是在故障定位还是抢修路径设计方面都能发挥作用。图例中,当标红用户点出现停电时,可以回溯供电路径,查找故障原因。下面是一个具体的操作示例。本场景中我们假设由于设备故障造成某用户节点(节点ID为74)停电,我们要做的是通过路径回溯定位到故障点(假设故障原因为设备故障而非线路故障)。result = test.outage_analysis("74") print('the cause of the problem:') print(result)进一步查询id为55的节点详情。result = test.query_vertex("55") print('the details of faulty equipment:') print(result)查询到的结果显示是因为节点ID为55的熔断器发生熔断导致的节点ID为74的用户接入点出现停电现象。环形供电查询环形供电是指电源和负荷点通过线路联结成环形的供电方式。环形供电能提高供电的可靠性,当环内任一段线路出现故障时,通过开关的切换能保证负荷点的正常供电。 图例中四条单路进线两两相连形成环形供电网络,当本段进线出现供电中断后,能从其他进线侧获取电能。下面是一个具体的操作示例。本场景演示了利用GES搜索配电网中的环形供电线路。result = test.annular_electric_supply() job_id = result time.sleep(1) result = test.get_job(job_id) print('the id list of annular_electric_supply:') print(result)the id list of annular_electric_supply: {'data_return_size': 1, 'min_length': 3, 'runtime': 0.0019060000000000001, 'circles': [['1250', '1251', '1249', '1054', '1203', '1035', '1202', '1036', '1204', '1049', '1237', '1046', '1274', '1250']], 'data_offset': 0, 'n': 100, 'data_total_size': 1, 'max_length': 15}可以进一步确定环形供电路径中联络开关的位置,联络开关的常规状态是open,只有在需要的时候才会close。(为了能尽可能的减少停电的影响,我们通过将两条配电线路通过开关连接起来,以便能实现负荷之间的转供,这个用来连接两条配电线路的开关就叫“联络开关”。)for i in range(len(result['circles'])): id_list = result['circles'][i] res = test.query_interconnection_switch(id_list) print('interconnection switch:') print(res)interconnection switch: {'vertices': [{'id': '1046', 'properties': {'name': ['南线小渔村委会支-临湖线联络开关']}}]}基于查询到的结果,我们可以知道环形路径上任何设备或线路出现故障都不会影响整体的供电情况,只需要切换联络开关即可,而我们也已经知道了联络开关的位置。
  • [云服务] 【数据使能】图引擎服务GES能力项
    分类文档链接备注最新动态cid:link_5特性清单cid:link_2原子APIcid:link_3FAQcid:link_4华为云在线课程(免费)图引擎服务https://education.huaweicloud.com/courses/course-v1:HuaweiX+CBUCNXE008+Self-paced/about?isAuth=0&cfrom=hwc图引擎服务(Graph Engine Service),是针对以“关系”为基础的“图”结构数据,进行查询、分析的服务。广泛应用于社交关系分析、推荐、精准营销、舆情及社会化聆听、信息传播、防欺诈等具有丰富关系数据的场景。华为云培训服务(收费)基于图引擎的医药查询系统cid:link_1如何在当前琳琅满目的药品中选取合适自己病症的良药呢?在人工智能发展迅猛的当下,让AI为选取药品贡献一份力量吧。华为云开发者网GES开放能力cid:link_6
  • [教程] 从零开始学Graph Database:什么是图
    传统上我们使用图来解决一些数学问题。比如图论起源于著名的柯尼斯堡七桥问题, 该问题被欧拉推广为:怎样判断是否存在着一个恰好包含了所有边,且没有重复的路径。即一笔画问题。当然了,图并非只能解决这类图论经典问题(如 四色问题,马的遍历问题,邮递员问题, 网络流问题 ),只要能够将研究对象表示为图结构,就能利用图的特点来解决问题,甚至大部分情况下,并不需要使用到多么高深的算法。 详情参见:《从零开始学Graph Database(1)》
  • [技术干货] 使用Jupyter可视化查询语句的语法树--以图查询语言Cypher为例
    “语法解析”和“词法解析”是计算机理解查询语句的重要一环。而词法和语法的解析依赖于一定的文法规则,有诸多网站可以可视化文法规则,但是对于根据文法规则生成的语法树进行可视化的文章却比较少。对文法规则生成的语法树进行可视化,可以降低查询语言的理解成本。本文以华为图引擎使用的cypher查询语言为例,将查询语句的解析结果(语法树)在jupyterLab上可视化。案例中使用的工具不仅可以可视化cypher语言的语法树结构,对其他antlr生成的抽象语法树同样适用。详情参见:cid:link_1AI Gallery案例地址为:cid:link_0
总条数:57 到第
上滑加载中