数据修改语句

GSQL语言不仅仅是一种“查询”语言,它还提供了一整套数据修改语句,包括对顶点或边的插入、删除、属性更新等一系列操作。

每个查询都被视为一个事务(transaction)。 因此,在整个查询操作完成(确认完成)之前,对于图数据的修改不会生效。因此,某个查询中的数据更改操作不会影响同一查询中的任何其他语句。

查询主体层语句中的删除语句(DELETE)

查询主体层语句中的DELETE语句删除一组给定的边或顶点。 此语句只能用在查询主体层。 (DML子句中的删除操作有另一套DML子句中的 DELETE语句)

EBNF范式
QueryBodyDeleteStmt := DELETE name FROM ( edgeSet | vertexSet ) [whereClause]

FROM子句中的顶点集(vertexSet)和边集(edgeSet)与SELECT语句中的FROM子句遵循相同的规则。 WHERE子句可以过滤顶点集或边集中的项。 下面是两个示例,一个用于删除顶点,另一个用于删除边。

DELETE 语句示例
# Delete all "person" vertices with location equal to "us"
CREATE QUERY deleteEx() FOR GRAPH workNet {
 S = {person.*};
 DELETE s FROM S:s
   WHERE s.locationId == "us";
}
DELETE 语句示例2
# Delete all "worksFor" edges where the person's location is "us"
CREATE QUERY deleteEx2() FOR GRAPH workNet {
 S = {person.*};
 DELETE e FROM S:s -(worksFor:e)-> company:t
   WHERE s.locationId == "us";
}

下例中的查询可用于观察DELETE语句的效果。 它统计了在美国(即“us”)工作的人(即“person”),以及这些美国人为谁工作(即worksFor边)。 在初始化并加载workNet图的测试数据时,基于locationId=“us”的条件,则这个名叫worksAtUS的查询可以找到总共5个person,9条worksFor边。 如果执行deleteEx2,将找到5个person和0个worksFor边。如果再执行deleteEx,则worksAtUS将找到0个person和0个worksFor边。

deleteEx 和deleteEx2的结果
CREATE QUERY countAtLocation(STRING loc) FOR GRAPH workNet {
 SetAccum<EDGE> @@selEdge;
 Start = {person.*};
 
 SV = SELECT s FROM Start:s
   WHERE s.locationId == loc;
 PRINT SV.size() AS numVertices;
 
 SE = SELECT s FROM Start:s -(worksFor:e)-> company:t
   WHERE s.locationId == loc
   ACCUM @@selEdge += e;
 PRINT @@selEdge.size() AS numEdges;
}

例如,假设按照countAtLocation,deleteEx2和deleteEx的顺序执行查询

deleteEx.run
RUN QUERY countAtLocation("us")
RUN QUERY deleteEx2()
RUN QUERY countAtLocation("us")
RUN QUERY deleteEx()
RUN QUERY countAtLocation("us")

则会得到下面的结果

DeleteEx 示例的结果
# Before any deletions
{
 "error": false,
 "message": "",
 "version": {
   "schema": 0,
   "api": "v2"
 },
 "results": [
   {"numVertices": 5},
   {"numEdges": 9}
 ]
}
# Delete edges
{
 "error": false,
 "message": "",
 "version": {
   "schema": 0,
   "api": "v2"
 },
 "results": []
}
# After deleting edges
{
 "error": false,
 "message": "",
 "version": {
   "schema": 0,
   "api": "v2"
 },
 "results": [
   {"numVertices": 5},
   {"numEdges": 0}
 ]
}
# Delete vertices
{
 "error": false,
 "message": "",
 "version": {
   "schema": 0,
   "api": "v2"
 },
 "results": []
}
# After deleting vertices
{
 "error": false,
 "message": "",
 "version": {
   "schema": 0,
   "api": "v2"
 },
 "results": [
   {"numVertices": 0},
   {"numEdges": 0}
 ]
}

DML子句中的DELETE 语句

DML子句中的DELETE语句也是DML子句,每次调用它时都会删除一个顶点或边(查询主体语句中的DELETE语句请参阅前面一节)。实际操作中,此语句一般都位于SELECT ... ACCUM/POST-ACCUM子句内,因此对于选定的顶点集或边集内的每个成员来说,都会调用一遍这个语句。

由于ACCUM子句在边集合中迭代,且该边集可以多次遇到相同的顶点,所以,如果要删除某个顶点,则最好将DML子句中的DELETE语句放在POST-ACCUM子句中而不是ACCUM子句中。

EBNF范式:
DMLSubDeleteStmt := DELETE "(" name ")"

以下示例使用和修改了socialNet图形内的数据。

在ACCUM和 POST-ACCUM之内的DELETE语句比较
# Remove any post vertices posted by the given user
CREATE QUERY deletePosts(vertex<person> seed) FOR GRAPH socialNet {
start = {seed};
 
​# Best practice is to delete a vertex in a POST-ACCUM, which only
​# occurs once for each vertex v, guaranteeing that a vertex is not
​# deleted more than once
​postAccumDeletedPosts = SELECT v FROM start -(posted:e)-> post:v
​       ​ POST-ACCUM DELETE (v);

​# Possible, but not recommended as the DML-sub DELETE statement occurs
​# once for each edge of the vertex v
​accumDeletedPosts = SELECT v FROM start -(posted:e)-> post:v
​       ​ ACCUM DELETE (v);
}
 
# Need a separate query to display the results, because deletions don't take effect during the query.
CREATE QUERY selectUserPosts(vertex<person> seed) FOR GRAPH socialNet {
   start = {seed};
 
   userPosts = SELECT v FROM start -(posted:e)-> post:v;
   PRINT userPosts;
}

例如,如果顺序执行以下的selectUserPosts和deletePosts查询

deletePosts.run
RUN QUERY selectUserPosts("person3")
RUN QUERY deletePosts("person3")
RUN QUERY selectUserPosts("person3")

会得到以下结果:

DeletePosts 示例的结果
# Before the deletion
{
 "error": false,
 "message": "",
 "version": {
   "schema": 0,
   "api": "v2"
 },
 "results": [{"selectedPosts": [{
   "v_id": "2",
   "attributes": {
     "postTime": "2011-02-03 01:02:42",
     "subject": "query languages"
   },
   "v_type": "post"
 }]}]
}
# Deletion; no output results requested at this point
{
 "error": false,
 "message": "",
 "version": {
   "schema": 0,
   "api": "v2"
 },
 "results": []
}
# After the deletion
{
 "error": false,
 "message": "",
 "version": {
   "schema": 0,
   "api": "v2"
 },
 "results": [{"selectedPosts": []}]
}

INSERT INTO语句

INSERT INTO语句的功能是向图形中插入边或顶点。 但是,如果插入的顶点/边的ID值与现有顶点/边的ID值重复,则新值将覆盖旧值。 如果插入的是边,则插入的边所关联的顶点必须已经存在,无论是本查询执行之前就已经存在或者在本查询内插入边之前先插入顶点都可以。INSERT INTO语句可以用作查询主体层语句或DML子句。

EBNF范式
insertStmt := INSERT INTO name ["(" ( PRIMARY_ID | FROM "," TO ) ("," name)* ")"]
                    VALUES "(" ( "_" | expr ) [name]["," ( "_" | expr ) [name] ("," ("_" | expr))*] ")"
  1. 按照顶点类或边类的标准顺序为顶点ID和其每个属性逐次赋值。 此方式类似于LOAD语句的格式。 在这种情况下,因为我们已经默认按顺序引用每个属性了,所以没有必要明确地指明每个属性。

    INSERT 操作中不指明属性名称
    INSERT INTO name VALUES (full_list_of_parameter_values)
  2. 先指明需要修改的属性,然后提供对应的值列表。 这种情况下,属性可以是任何顺序,但ID必须排在首位。 也就是说,如果用户想要插入一个顶点,则列表中的第一个属性的名称必须是PRIMARY_ID。 若用户要插入的是边,则前两个属性名必须是FROM和TO

    INSERT 操作中指明属性名称
    INSERT INTO name (IDs, specified_attributes) VALUES (values_for_specified_attributes)

每个属性值都提供一个表达式expr或“_”(代表该属性类型的默认值)。 如果使用通配符的顶点类型定义了边类,则前两个(id)值后面的可选name参数代表了指定的源顶点类型和目标顶点类型。

查询主体层语句中的INSERT操作

这个名叫insertEx的查询展示了查询主体层语句中的INSERT语句:插入新的公司顶点(即“company”)并将workFor边插入到workNet图形中。

INSERT 语句的例子
CREATE QUERY insertEx(STRING name, STRING name2, STRING name3, STRING comp) FOR GRAPH workNet {
 # Vertex insertion
   # Adds 2 'company' vertices. One is located in the USA, and a sister company in Japan.
   INSERT INTO company VALUES ( comp, comp, "us" );
   INSERT INTO company (PRIMARY_ID, country) VALUES ( comp + "_jp", "jp" );
 
 # Edge insertion
   # Adds a 'worksFor' edge from person 'name' to the company 'comp', filling in default
   # values for startYear (0), startMonth (0), and fullTime (false).
   INSERT INTO worksFor VALUES (name person, comp company, _, _, _);
 
   # Adds a 'worksFor' edge from person 'name2' to the company 'comp', filling in default
   # values for startMonth (0), but specifying values for startYear and fullTime.
   INSERT INTO worksFor (FROM, TO, startYear, fullTime) VALUES (name2 person, comp company, 2017, true);
 
   # Adds a 'worksFor' edge from person 'name3' to the company 'comp', filling in default
   # values for startMonth (0), and fullTime (false) but specifying a value for startYear (2017).
   INSERT INTO worksFor (FROM, TO, startYear) VALUES (name3 person, comp company, 2000 + 17);
}

名叫whoWorksForCompany的查询可用于检查insertEx的效果。 在运行insertEx之前,运行whoWorksForCompany("gsql")将找到0个名为“gsql”的公司,和 0个连接到公司“gsql”的worksFor边。 在运行查询insertEx("tic", "tac", "toe", "gsql")之后再执行insertEx(“gsql”)就可以找到一个名为“gsql”的公司和另一个名为“gsql_jp”的公司。 同时,它还能找到3条边,分别为tic,tac和toe,这些边的startMonth,startYear和fullTime属性分别是不同的值。

检查insertEX的结果
CREATE QUERY whoWorksForCompany(STRING comp) FOR GRAPH workNet {
 SetAccum<EDGE> @@setEdge;
 
 Comps = {company.*};
 PRINT Comps[Comps.id];   # output api v2
 
 Pers = {person.*};
 S = SELECT s
   FROM Pers:s -(worksFor:e)-> :t
   WHERE t.id == comp
   ACCUM @@setEdge += e;
 PRINT @@setEdge;
}

DML子句中的INSERT语句

以下示例演示了DML子句中的INSERT语句。 由于该语句引用了所有公司(allCompanies),因此我们将插入多个顶点。

DML子句中的INSERT 语句
# Add a child company of a given company name. The new child company is in japan
CREATE QUERY addNewChildCompany(STRING name) FOR GRAPH workNet {
 allCompanies = {company.*};
 X = SELECT s
     FROM allCompanies:s
     WHERE s.id == name
     ACCUM  INSERT INTO company VALUES ( name + "_jp", name + "_jp", "jp" );
}
 
# Add separate query to list the companies, before and after the insertion
CREATE QUERY listCompanyNames(STRING countryFilter) FOR GRAPH workNet {
 allCompanies = {company.*};
 C = SELECT s
     FROM allCompanies:s
     WHERE s.country == countryFilter;
 
 PRINT C.size() AS numCompanies;
 PRINT C;
}

示例:为美国公司company3添加一个日本子公司。在插入子公司之前和之后,分别列出所有的日本公司以示区别。

addNewChildCompany.run
RUN QUERY listCompanyNames("jp")
RUN QUERY addNewChildCompany("company4")
RUN QUERY listCompanyNames("jp")
addNewChildCompany 示例的结果
# Before insertion
{
 "error": false,
 "message": "",
 "version": {
   "schema": 0,
   "api": "v2"
 },
 "results": [
   {"numCompanies": 1},
   {"C": [{
     "v_id": "company3",
     "attributes": {
       "country": "jp",
       "id": "company3"
     },
     "v_type": "company"
   }]}
 ]
}
# insert company "company4_jp"
{
 "error": false,
 "message": "",
 "version": {
   "schema": 0,
   "api": "v2"
 },
 "results": []
}
# after insertion
{
 "error": false,
 "message": "",
 "version": {
   "schema": 0,
   "api": "v2"
 },
 "results": [
   {"numCompanies": 2},
   {"C": [
     {
       "v_id": "company3",
       "attributes": {
         "country": "jp",
         "id": "company3"
       },
       "v_type": "company"
     },
     {
       "v_id": "company4_jp",
       "attributes": {
         "country": "jp",
         "id": "company4_jp"
       },
       "v_type": "company"
     }
   ]}
 ]
}

UPDATE语句

UPDATE语句用于更改顶点或边的属性。

EBNF范式:
updateStmt := UPDATE name FROM ( edgeSet | vertexSet ) SET DMLSubStmtList [whereClause]

要更新的顶点或边的集合在FROM子句中描述,与SELECT语句中的FROM子句遵循相同的规则。 在SET表达式中,DMLSubStmtList可以包含赋值语句,用于更新顶点或边的属性。 基本类属性和集合类型属性都可以被更新。 这些赋值语句使用在FROM子句中声明的顶点或边的别名。 我们还可以选择WHERE子句来筛选顶点集或边集中的项目。

UPDATE 语句的示例
# Change all "person" vertices with location equal to "us" to "USA"
CREATE QUERY updateEx() FOR GRAPH workNet  {
 S = {person.*};
 
 UPDATE s FROM S:s
 SET s.locationId = "USA",  # simple base type attribute
     s.skillList = [1,2,3]  # collection-type attribute
 WHERE s.locationId == "us";
 
 # The update cannot become effective within this query, so PRINT S still show "us".
 PRINT S;
}

UPDATE语句是只能用于查询主体层语句中。 但是,通过结合其他语句,我们仍可以进行DML子句中的数据更新操作。 我们可以使用赋值运算符(=)在SELECT语句的POST-ACCUM子句中更新顶点属性的值; 也可以使用赋值运算符在SELECT语句的ACCUM子句中更新边属性的值。UPDATE语句实际上等效于带有ACCUM子句和/或POST-ACCUM子句的SELECT操作。 以下是一个例子。

在ACCUM子句中不允许更新顶点的属性值,因为这样的更新动作是并行运行的,同一个值可能会多次更新,从而导致输出的结果不确定。 如果顶点属性值的更新取决于边的属性值,请使用附属于顶点的累加器来保存对应的值,然后在POST-ACCUM子句中更新顶点的属性值。

下面的查询中,我们使用了SELECT语句而没有使用UPDATE语句,但其功能类似于之前的查询。 示例中,查询updateEx2恢复了updateEx所做的对locationId的更改(即将位置从“USA”改成“us”)。

UPDATE 语句示例2
# The second example is equivalent to the above updateEx
CREATE QUERY updateEx2() FOR GRAPH workNet  {
 S = {person.*};
 
 X = SELECT s
     FROM S:s
     WHERE S.locationId == "USA"
     POST-ACCUM S.locationId = "us",
                S.skillList = [3,2,1];
 PRINT S;
}

下面的示例中,我们更改了一条边上的两个属性,包括一个累加更改(e.startYear = e.startYear + 1):

UPDATE 语句示例 3
CREATE QUERY updateEx3() FOR GRAPH workNet{
 S = {person.*};
 
 # update edge and target vertices' attribute
 UPDATE e FROM S:s - (worksFor:e) -> :t
 SET e.startYear = e.startYear + 1,
     e.fullTime = false
 WHERE s.locationId == "us";
 
 PRINT S;
}

其他更新数据的方式

除了上面的UPDATE语句和SELECT语句之外,如果一个顶点或边已经被赋值了一个变量或参数,则我们可以简单的使用在查询主体层语句中的赋值语句来更新这个顶点或边的属性值。

通过赋值来更新数据
# change the given person's new location
CREATE QUERY updateByAssignment(VERTEX<person> v, STRING newLocation) FOR GRAPH workNet{
 v.locationId = newLocation;
}

Last updated