原文:ArangoDB High-level operations
以下介绍以下高级操作:
- FOR:迭代数组的所有元素。
- RETURN:生成查询结果。
- FILTER:将结果限制为与任意逻辑条件匹配的元素。
- SORT:强制排序一系列已经生成的中间结果。
- LIMIT:将结果中的元素数量减少到最多指定的数字,可选择跳过元素(分页)。
- LET:为变量分配任意值。
- COLLECT:按一个或多个组标准对数组进行分组。也可以统计和聚合。
- REMOVE:从集合中删除文档。
- UPDATE:部分更新集合中的文档。
- REPLACE:完全替换集合中的文档。
- INSERT:将新文档插入到集合中。
- UPSERT:更新/替换现有文档,或在不存在的情况下创建它。
- WITH:指定查询中使用的集合(仅在查询时开始)。
FOR
在FOR关键字可以是迭代数组的所有元素。一般语法是:
FOR variableName IN expression
图形遍历还有一个特殊的变体:
FOR vertexVariableName,edgeVariableName,pathVariableName IN traversalExpression
对于这种特殊情况,请参见图形遍历章节。对于所有其他情况,请阅读:
表达式返回的每个数组元素都被访问了一次。需要表达式在所有情况下返回一个数组。空数组也是允许的。当前的数组元素可用于在variableName指定的变量中进一步处理。
FOR u IN users RETURN u
这将遍历数组用户的所有元素(注意:在这种情况下,该数组由名为“users”的集合的所有文档组成),并使变量u中的当前数组元素可用。您在本示例中未修改,但仅使用RETURN关键字推送到结果中。
注意:如下所示,对基于集合的数组进行迭代时,文档的顺序是未定义的,除非使用SORT语句定义了明确的排序顺序。
通过引入的可变FOR可用直到范围FOR被放置在被关闭。
另一个使用静态声明的值数组来迭代的例子:
FOR year IN [ 2011,2012,2013 ] RETURN { "year" : year,"isLeapYear" : year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) }
也允许嵌套多个FOR语句。当FOR语句嵌套时,将创建由各个FOR语句返回的数组元素的交叉乘积。
FOR u IN users FOR l IN locations RETURN { "user" : u,"location" : l }
在这个例子中,有两个数组迭代:数组用户的外部迭代加上数组位置的内部迭代。内部数组遍历外部数组中的元素多次。对于每次迭代,用户和位置的当前值可用于变量u和l中的进一步处理。
=====================================================================================
RETURN
该RETURN语句可以用于生成查询的结果。必须在数据选择查询中的每个块的末尾指定一个RETURN语句,否则查询结果将是未定义的。在数据修改查询的主级使用RETURN是可选的。
RETURN的一般语法是:
RETURN expression
的表达通过返回RETURN产生在块中的每个迭代RETURN语句被放置在,这意味着一个的结果RETURN语句是总是一个数组。如果没有与查询匹配的文档和一个返回值作为数组返回一个元素,则包含一个空数组。
要从当前迭代的数组中返回所有元素,而不需要修改,可以使用以下简单的形式:
FOR variableName IN expression RETURN variableName
由于RETURN允许指定表达式,因此可以执行任意计算来计算结果元素。在RETURN放置的范围内有效的任何变量都可用于计算。
要遍历称为用户的一个集合的所有文档并返回完整的文档,您可以写:
FOR u IN users RETURN u
在for循环的每次迭代中,将用户集合的文档分配给变量u,并在此示例中未修改返回。要只返回每个文档的一个属性,您可以使用不同的返回表达式:
FOR u IN users RETURN u.name
或者返回多个属性,一个对象可以这样构造:
FOR u IN users RETURN { name: u.name,age: u.age }
注意:RETURN将关闭当前作用域并消除其中的所有局部变量。在使用子查询时记住这一点很重要。
FOR u IN users RETURN { [ u._id ]: u.age }
[
{
"users/9883": 32
},
{
"users/9915": 27
},
{
"users/10074": 69
}
]
结果每个用户包含一个对象,每个对象具有单个键/值对。这通常是不需要的。对于单个对象,将用户ID映射到年龄,需要将各个结果合并并返回给另一个对象RETURN
:
RETURN MERGE( FOR u IN users RETURN { [ u._id ]: u.age } )
[
{
"users/10074": 69,"users/9883": 32,"users/9915": 27
}
]
请记住,如果键表达式多次计算相同的值,则只有一个具有重复名称的键/值对可以在MERGE()中存活。为了避免这种情况,您可以不使用动态属性名称,而是使用静态名称,并将所有文档属性作为属性值返回:
FOR u IN users RETURN { name: u.name,age: u.age }
[
{
"name": "John Smith","age": 32
},
{
"name": "James Hendrix","age": 69
},
{
"name": "Katie Foster","age": 27
}
]
返回列表
由于ArangoDB 2.7,RETURN可以后跟DISTINCT关键字。该DISTINCT关键字将确保返回值的唯一RETURN语句:
FOR variableName IN expression RETURN DISTINCT expression
如果DISTINCT应用于本身是数组或子查询的表达式,则DISTINCT不会使每个数组或子查询结果中的值唯一,而是确保结果仅包含不同的数组或子查询结果。要使数组或子查询的结果唯一,只需对数组或子查询应用DISTINCT。
例如,以下查询将对其子查询结果应用DISTINCT,但不在子查询中:
FOR what IN 1..2 RETURN DISTINCT ( FOR i IN [ 1,2,3,4,1,3 ] RETURN i )
这里我们将有一个两个迭代的FOR循环,每个循环都执行一个子查询。这里的DISTINCT应用于两个子查询结果。两个子查询返回相同的结果值(即[1,2,3,4,1,3]),所以在DISTINCT之后,只有一个值[1,2,3,4,1,3]剩下:
[ [ 1,3 ] ]
如果目标是将DISTINCT应用到子查询中,则需要将其移动到:
FOR what IN 1..2 LET sub = ( FOR i IN [ 1,3 ] RETURN DISTINCT i ) RETURN sub
在上述情况下,DISTINCT将使子查询结果唯一,以便每个子查询将返回唯一的值数组([1,2,3,4])。由于子查询执行两次,顶层没有DISTINCT,该数组将返回两次:
[ [ 1,4 ], [ 1,4 ] ]
注意:结果的顺序未定义为RETURN DISTINCT。
注意:如果查询的顶层没有FOR循环,则不允许返回DISTINCT。
=====================================================================================
FILTER
该FILTER语句可以被用来限制的结果相匹配的任意的逻辑条件的元素。
一般语法
FILTER condition
条件必须是一个评估为false或true的条件。如果条件结果为假,则跳过当前元素,因此不会进一步处理,而不是结果的一部分。如果条件为真,则不跳过当前元素,并可进一步处理。有关可以在条件下使用的比较运算符,逻辑运算符等的列表,请参阅运算符。
FOR u IN users FILTER u.active == true && u.age < 39 RETURN u
允许在查询中指定多个FILTER语句,即使在同一个块中。如果使用多个FILTER语句,它们的结果将与逻辑AND组合,这意味着包含一个元素的所有过滤条件必须为true。
FOR u IN users FILTER u.active == true FILTER u.age < 39 RETURN u
在上面的例子中,所有数组元素的用户具有的属性活性值为真,并且具有属性年龄与值小于39(包括空的)将被包含在结果中。用户的所有其他元素将被跳过,不包括在RETURN生成的结果中。有关不存在或空属性的影响的描述,可参考“从集合访问数据”一章。
操作顺序
请注意,FILTER语句的位置可能会影响查询的结果。测试数据中有16个活跃用户,例如:
FOR u IN users
FILTER u.active == true
RETURN u
我们最多可以将结果集限制在5个用户:
FOR u IN users
FILTER u.active == true
LIMIT 5
RETURN u
这可能会返回Jim,Diego,Anthony,Michael和Chloe的用户文档。返回的是未定义的,因为没有SORT语句来确保特定的顺序。如果我们添加第二个FILTER语句只返回女性...
FOR u IN users
FILTER u.active == true
LIMIT 5
FILTER u.gender == "f"
RETURN u
...它可能只是返回Chloe文档,因为LIMIT被应用在第二个FILTER之前。不超过5个文件到达第二个FILTER区块,并不是所有这些都符合性别标准,尽管收集中有超过5名活跃的女性用户。通过添加SORT块可以获得更确定的结果:
FOR u IN users
FILTER u.active == true
SORT u.age ASC
LIMIT 5
FILTER u.gender == "f"
RETURN u
这将返回用户Mariah和Mary。如果按照DESC顺序按年龄排序,则返回Sophia,Emma和Madison文档。一个过滤器一后LIMIT不是很常见不过了,你可能想这样的查询,而不是:
FOR u IN users
FILTER u.active == true AND u.gender == "f"
SORT u.age ASC
LIMIT 5
RETURN u
放置FILTER块的重要性允许这个单个关键字可以承担两个sql关键字WHERE以及HAVING的角色。因此,AQL的FILTER与COLLECT聚合一起使用与任何其他中间结果,文档属性等相同。
=====================================================================================SORT
所述SORT语句将强制在当前块中已经产生的中间结果的数组进行排序。SORT允许指定一个或多个排序标准和方向。一般语法是:
SORT expression direction
指定方向是可选的。排序的默认(隐式)方向是升序。要明确指定排序方向,可以使用关键字ASC(升序)和DESC。可以使用逗号分隔多个排序标准。
注意:在迭代基于集合的数组时,除非使用SORT定义明确的排序顺序,否则始终未定义文档的顺序。
FOR u IN users SORT u.lastName,u.firstName,u.id DESC RETURN u
请注意,常量SORT表达式可用于指示不需要特定排序顺序。在优化过程中,AQL优化程序将优化常量SORT表达式,但是如果优化程序不需要考虑任何特定的排序顺序,则可以显式指定它们可以进一步优化。
LIMIT
LIMIT语句允许切片使用偏移和计数结果数组。它将结果中的元素数量减少到最多指定的数字。遵循LIMIT的两种一般形式:
LIMIT count LIMIT offset,count
第一种形式允许仅指定计数值,而第二种形式允许指定偏移量和计数值。第一种形式与使用偏移值为0的第二种形式相同。
FOR u IN users
LIMIT 5
RETURN u
以上查询返回用户集合的前五个文档。也可以写出LIMIT 0,5
相同的结果。实际返回的文件是相当随意的,因为没有指定明确的排序顺序。因此,通常需要一个SORT
操作。
该偏移值指定从结果许多元素将被跳过。它必须为0或更大。该计值指定有多少元素应该至多包含在结果中。
FOR u IN users
SORT u.firstName,u.lastName,u.id DESC
LIMIT 2,5
RETURN u
在上面的示例中,用户的文档被排序,前两个结果被跳过,并返回下一个五个用户文档。
请注意,变量和表达式不能用于偏移和计数。它们的值在查询编译时必须知道,这意味着您只能使用数字文字和绑定参数。
如果使用LIMIT与查询中的其他操作有关。特别是FILTER之前的LIMIT操作可以显着地改变结果,因为操作按照它们在查询中写入的顺序执行。有关详细示例,请参阅FILTER。
该LET语句可用于分配给一个变量的任意值。然后在LET语句放置的范围中引入该变量。
一般语法是:
LET variableName = expression
变量在AQL中是不可变的,这意味着它们不能被重新分配:
LET a = [1,2,3] // initial assignment
a = PUSH(a,4) // Syntax error,unexpected identifier
LET a = PUSH(a,4) // parsing error,variable 'a' is assigned multiple times
LET b = PUSH(a,4) // allowed,result: [1,4]
LET语句主要用于声明复杂计算,并避免在查询的多个部分重复计算相同的值。
FOR u IN users LET numRecommendations = LENGTH(u.recommendations) RETURN { "user" : u, "numRecommendations" : numRecommendations, "isPowerUser" : numRecommendations >= 10 }
在上述示例中,使用LET语句计算推荐数量,从而避免在RETURN语句中计算两次值。
LET的另一个用例是在子查询中声明一个复杂的计算,使整个查询更易读。
FOR u IN users LET friends = ( FOR f IN friends FILTER u.id == f.userId RETURN f ) LET memberships = ( FOR m IN memberships FILTER u.id == m.userId RETURN m ) RETURN { "user" : u, "friends" : friends, "numFriends" : LENGTH(friends), "memberShips" : memberships }========================================================================================
COLLECT
所述COLLECT关键字可以由一个或多个基团的标准被用于组的阵列。
该COLLECT语句将消除当前范围内的所有局部变量。在COLLECT之后,只有COLLECT本身引入的变量才可用。
COLLECT的一般语法是:
COLLECT variableName = expression options COLLECT variableName = expression INTO groupsVariable options COLLECT variableName = expression INTO groupsVariable = projectionExpression options COLLECT variableName = expression INTO groupsVariable KEEP keepVariable options COLLECT variableName = expression WITH COUNT INTO countVariable options COLLECT variableName = expression AGGREGATE variableName = aggregateExpression options COLLECT AGGREGATE variableName = aggregateExpression options COLLECT WITH COUNT INTO countVariable options
options
在所有变体中都是可选的。
分组语法
COLLECT的第一个语法形式只能通过表达式中指定的定义组标准对结果进行分组。为了进一步处理COLLECT生成的结果,引入了一个新的变量(由variableName指定)。此变量包含组值。
以下是一个示例查询,可以在u.city中找到不同的值,并使其在可变城市中可用:
FOR u IN users COLLECT city = u.city RETURN { "city" : city }
第二种形式与第一种形式相同,但另外还引入了一个变量(由groupsVariable指定),该变量包含落入组中的所有元素。这样做的工作如下:groupsVariable变量是一个数组,包含与组中所有元素一样多的数组。该数组的每个成员都是一个JSON对象,其中在AQL查询中定义的每个变量的值都绑定到相应的属性。请注意,它考虑在COLLECT语句之前定义的所有变量,但不考虑顶级(在任何FOR之外)中的所有变量,除非COLLECT语句本身在顶级,在这种情况下所有变量都被采用。
FOR u IN users COLLECT city = u.city INTO groups RETURN { "city" : city, "usersInCity" : groups }
在上面的例子中,数组用户将按属性城市分组。结果是一个新的文档数组,每个不同的u.city值有一个元素。每个城市的原始阵列(这里:用户)的元素在变量组中可用。这是因为INTO条款。
COLLECT还允许指定多个组标准。个别组标准可以用逗号分隔:
FOR u IN users COLLECT country = u.country,city = u.city INTO groups RETURN { "country" : country, "city" : city, "usersInCity" : groups }
在上述示例中,阵列用户按国家/地区按城市分组,并且对于国家和城市的每个不同组合,将返回用户。
丢弃过时的变量
COLLECT的第三种形式允许使用任意的projectionExpression重写groupsVariable的内容:
FOR u IN users COLLECT country = u.country,city = u.city INTO groups = u.name RETURN { "country" : country, "userNames" : groups }
在上面的例子中,只有projectionExpression是u.name。因此,只有这个属性被复制到每个文档的groupsVariable中。这可能比将所有变量从范围复制到groupsVariable更有效,就像没有projectionExpression一样。
INTO中的表达式也可用于任意计算:
FOR u IN users COLLECT country = u.country,city = u.city INTO groups = { "name" : u.name, "isActive" : u.status == "active" } RETURN { "country" : country, "usersInCity" : groups }
COLLECT还提供了一个可选的KEEP子句,可用于控制哪些变量将被复制到创建的变量中INTO
。如果没有指定KEEP子句,则范围中的所有变量将作为子属性复制到groupsVariable中。这是安全的,但如果范围中有很多变量或变量包含大量数据,则可能会对性能产生负面影响。
以下示例将复制到groupsVariable中的变量限制为仅命名。范围内的变量u和someCalculation也不会被复制到groupsVariable,因为它们没有列在KEEP子句中:
FOR u IN users LET name = u.name LET someCalculation = u.value1 + u.value2 COLLECT city = u.city INTO groups KEEP name RETURN { "city" : city, "userNames" : groups[*].name }
KEEP仅适用于INTO。在KEEP子句中只能使用有效的变量名。KEEP支持多个变量名的规范。
组长度计算
COLLECT还提供了一个特殊的WITH COUNT子句,可以用来有效地确定组成员的数量。
最简单的形式只是返回将其转入COLLECT的项目数:
FOR u IN users COLLECT WITH COUNT INTO length RETURN length
以上相当于,但效率比:
RETURN LENGTH( FOR u IN users RETURN length )
在与COUNT子句也可以用来有效地计算每个组中的项的数目:
FOR u IN users COLLECT age = u.age WITH COUNT INTO length RETURN { "age" : age, "count" : length }
注意:WITH COUNT子句只能与INTO子句一起使用。
聚合
COLLECT
可以使用一个语句来执行每组数据的聚合。为了仅确定组长度,可以如前所述使用WITH COUNT INTO
变体COLLECT
。
对于其他聚合,可以对COLLECT
结果运行聚合函数:
FOR u IN users COLLECT ageGroup = FLOOR(u.age / 5) * 5 INTO g RETURN { "ageGroup" : ageGroup, "minAge" : MIN(g[*].u.age), "maxAge" : MAX(g[*].u.age) }
然而,上述要求在所有组的收集操作期间存储所有组值,这可能是低效的。
允许在收集操作期间逐渐建立聚合值的特殊AGGREGATE
变体,COLLECT
因此通常更有效。
使用AGGREGATE
变体,上述查询变为:
FOR u IN users COLLECT ageGroup = FLOOR(u.age / 5) * 5 AGGREGATE minAge = MIN(u.age),maxAge = MAX(u.age) RETURN { ageGroup, minAge, maxAge }
该AGGREGATE
关键字只能在COLLECT
关键字后使用。如果使用,它必须直接按照分组键的声明。如果没有使用分组键,则它必须COLLECT
直接按照关键字:
FOR u IN users COLLECT AGGREGATE minAge = MIN(u.age),maxAge = MAX(u.age) RETURN { minAge, maxAge }
每个AGGREGATE
作业的右侧只允许具体表达式:
-
在顶层,聚合表达式必须是所支持的聚合功能中的一个的呼叫
LENGTH
,MIN
,MAX
,SUM
,AVERAGE
,STDDEV_POPULATION
,STDDEV_SAMPLE
,VARIANCE_POPULATION
,或VARIANCE_SAMPLE
-
聚合表达式不得引用
COLLECT
自身引入的变量
收集变体
由于ArangoDB 2.6,优化器可以选择两种COLLECT变体:排序变体和散列变体。该散列变种只成为一个候选人COLLECT不使用的语句INTO子句。
优化器将始终生成采用排序方法的计划。已排序的方法要求其输入按照COLLECT子句中指定的组标准进行排序。为了确保结果的正确性,AQL优化程序将自动将一个SORT语句插入到COLLECT语句前面的查询中。如果组标准中存在排序的索引,则优化器可能能够稍后优化该SORT语句。
如果COLLECT有资格使用哈希变体,优化程序将在计划阶段开始时为其创建额外的计划。在此计划中,COLLECT前面不会添加额外的SORT语句。这是因为COLLECT的哈希变体不需要排序输入。相反,将在COLLECT之后添加一个SORT语句来对其输出进行排序。此SORT声明可能会在稍后的阶段再次优化。如果COLLECT的排序顺序与用户无关,
FOR u IN users COLLECT age = u.age SORT null /* note: will be optimized away */ RETURN age
优化器使用哪种COLLECT变体取决于优化器的成本估算。使用不同COLLECT变体创建的计划将通过常规优化流程发货。最终,优化者将按常规选择最低估计总成本的计划。
通常,在组标准上存在排序索引的情况下,COLLECT的排序变体应该是优选的。在这种情况下,优化器可以消除COLLECT之前的SORT语句,以便不会留下SORT。
如果组标准中没有可用的排序索引,排序变体所需的前期排序可能很昂贵。在这种情况下,优化器可能更喜欢COLLECT的哈希变体,它不需要对其输入进行排序。
COLLECT实际使用的变体可以通过查询查询的执行计划,特别是AggregateNode及其aggregationOptions属性来计算出来。
设置COLLECT选项
可以在COLLECT语句中使用选项来通知优化器有关首选COLLECT方法。当指定COLLECT语句的以下附录时,优化器将始终使用COLLECT的排序变体,甚至不使用哈希变体创建计划:
OPTIONS { method: "sorted" }
请注意,将hash指定为方法不会使优化器使用哈希变体。这是因为哈希变体不符合所有查询的资格。相反,如果在OPTIONS中没有指定选项或任何其他排序方法,优化器将使用其常规成本估算。
COLLECT与RETURN DISTINCT
为了使结果集成为唯一,可以使用COLLECT或RETURN DISTINCT。幕后,两个变体都可以通过创建一个AggregateNode来实现。对于这两种变体,优化器可以尝试COLLECT的排序和散列变体。因此,差异主要是语法,RETURN DISTINCT与等效的COLLECT相比,节省了一些打字的次数:
FOR u IN users RETURN DISTINCT u.age
FOR u IN users COLLECT age = u.age RETURN age
然而,COLLECT比RETURN DISTINCT灵活得多。此外,结果的顺序对于RETURN DISTINCT是未定义的,而对于COLLECT,结果将被排序。
REMOVE
该REMOVE关键字可以用来从集合中删除的文件。在单个服务器上,文档删除是以全或无任何方式事务执行的。对于分片集合,整个删除操作不是事务性的。
每个REMOVE操作仅限于一个集合,集合名称不能是动态的。每个AQL查询只允许每个集合单个REMOVE语句,并且访问相同集合的读操作,遍历操作或可以读取文档的AQL函数不能遵循。
删除操作的语法是:
REMOVE keyExpression IN collection options
集合必须包含从中删除文档的集合的名称。keyExpression必须是包含文档标识的表达式。这可以是一个字符串(必须包含文档键)或一个必须包含_key属性的文档。
以下查询因此是相当的:
FOR u IN users REMOVE { _key: u._key } IN users FOR u IN users REMOVE u._key IN users FOR u IN users REMOVE u IN users
注意:删除操作可以删除任意文档,并且文档不需要与前面的FOR语句生成的文档相同:
FOR i IN 1..1000 REMOVE { _key: CONCAT('test',i) } IN users FOR u IN users FILTER u.active == false REMOVE { _key: u._key } IN backup
设置查询选项
可以使用选项来抑制尝试删除不存在的文档时可能发生的查询错误。例如,如果要删除的文档之一不存在,以下查询将失败:
FOR i IN 1..1000 REMOVE { _key: CONCAT('test',i) } IN users
通过指定ignoreErrors查询选项,可以抑制这些错误,以便查询完成:
FOR i IN 1..1000 REMOVE { _key: CONCAT('test',i) } IN users OPTIONS { ignoreErrors: true }
为了确保在查询返回时将数据写入磁盘,有waitForSync查询选项:
FOR i IN 1..1000 REMOVE { _key: CONCAT('test',i) } IN users OPTIONS { waitForSync: true }
返回已移除的文档
删除的文档也可以由查询返回。在这种情况下,REMOVE
语句必须跟在一个RETURN
语句之后(LET
也允许使用中间语句)。REMOVE
引入伪值OLD
来引用已删除的文档:
REMOVE keyExpression IN collection options RETURN OLD
以下是使用名为removed
捕获已删除文档的变量的示例。对于每个删除的文档,将返回文档键。
FOR u IN users REMOVE u IN users LET removed = OLD RETURN removed._key======================================================================================================
在UPDATE关键字可以用来部分地更新集合中的文档。在单个服务器上,以全或无任何方式事务执行更新。对于分片集合,整个更新操作不是事务性的。
每个UPDATE操作都限于单个集合,集合名称不能是动态的。每个AQL查询只允许每个集合的单个UPDATE语句,并且访问相同集合的读取操作,遍历操作或可以读取文档的AQL函数不能遵循。系统属性_id,_key和_rev无法更新,_from和_to可以。
更新操作的两种语法如下:
UPDATE document IN collection options UPDATE keyExpression WITH document IN collection options
集合必须包含应更新文档的集合的名称。文档必须是包含要更新的属性和值的文档。当使用第一个语法时,文档还必须包含_key属性来标识要更新的文档。
FOR u IN users
UPDATE { _key: u._key,name: CONCAT(u.firstName," ",u.lastName) } IN users
以下查询无效,因为它不包含_key属性,因此无法确定要更新的文档:
FOR u IN users UPDATE { name: CONCAT(u.firstName,u.lastName) } IN users
当使用第二种语法时,keyExpression提供文档标识。这可以是一个字符串(必须包含文档键)或一个必须包含_key属性的文档。
以下查询是等效的:
FOR u IN users UPDATE u._key WITH { name: CONCAT(u.firstName,u.lastName) } IN users FOR u IN users UPDATE { _key: u._key } WITH { name: CONCAT(u.firstName,u.lastName) } IN users FOR u IN users UPDATE u WITH { name: CONCAT(u.firstName,u.lastName) } IN users
更新操作可以更新不需要与前面的FOR语句生成的文档相同的任意文档:
FOR i IN 1..1000
UPDATE CONCAT('test',i) WITH { foobar: true } IN users
FOR u IN users
FILTER u.active == false
UPDATE u WITH { status: 'inactive' } IN backup
使用文档属性的当前值
子句中OLD
不支持伪变量WITH
(以后可用UPDATE
)。要访问当前属性值,通常可以通过FOR
循环的变量来引用一个文档,该变量用于迭代集合:
FOR doc IN users UPDATE doc WITH { fullName: CONCAT(doc.firstName,doc.lastName) } IN users
如果没有循环,因为单个文档只是更新,那么可能没有像上面(doc
)一样的变量,这可以引用正在更新的文档:
UPDATE "users/john" WITH { ... } IN users
要在这种情况下访问当前值,必须先检索文档并将其存储在变量中:
LET doc = DOCUMENT("users/john")
UPDATE doc WITH {
fullName: CONCAT(doc.firstName,doc.lastName)
} IN users
UPDATE doc WITH {
karma: doc.karma + 1
} IN users
如果属性karma
不存在,doc.karma
则被评估为null。该表达式null + 1
导致将新属性karma
设置为1。如果属性确实存在,那么它将被增加1。
当然,数组也可以被突变:
UPDATE doc WITH {
hobbies: PUSH(doc.hobbies,"swimming")
} IN users
如果属性hobbies
不存在,则可以方便地初始化[ "swimming" ]
或扩展。
设置查询选项
可以使用选项来抑制尝试更新不存在的文档或违反唯一键约束时可能发生的查询错误:
FOR i IN 1..1000
UPDATE {
_key: CONCAT('test',i)
} WITH {
foobar: true
} IN users OPTIONS { ignoreErrors: true }
更新操作将仅更新文档中指定的属性,并保留其他属性。内部属性(如_id,_key,_rev,_from和_to)无法更新,并在文档中指定时被忽略。更新文档将使用服务器生成的值修改文档的修订版本号。
当使用空值更新属性时,ArangoDB不会从文档中删除属性,而是为其存储空值。为了摆脱更新操作中的属性,将它们设置为null并提供keepNull选项:
FOR u IN users
UPDATE u WITH {
foobar: true,
notNeeded: null
} IN users OPTIONS { keepNull: false }
上述查询将从文档中删除notNeeded属性,并正常更新foobar属性。
如果UPDATE查询和待更新文档中都存在对象属性,那么还可以使用mergeObjects选项来控制对象内容是否被合并。
以下查询将更新的文档的name属性设置为与查询中指定的完全相同的值。这是由于mergeObjects选项设置为false:
FOR u IN users
UPDATE u WITH {
name: { first: "foo",middle: "b.",last: "baz" }
} IN users OPTIONS { mergeObjects: false }
相反,以下查询将原始文档中的name属性的内容与查询中指定的值合并:
FOR u IN users
UPDATE u WITH {
name: { first: "foo",last: "baz" }
} IN users OPTIONS { mergeObjects: true }
现在将保留在待更新文档中但不在查询中的名称中的属性。两者中存在的属性将被查询中指定的值覆盖。
注:为默认值mergeObjects是真的,所以没有必要明确指定。
为了确保在更新查询返回时数据是持久的,有waitForSync查询选项:
FOR u IN users
UPDATE u WITH {
foobar: true
} IN users OPTIONS { waitForSync: true }
返回修改后的文档
修改后的文档也可以由查询返回。在这种情况下,UPDATE
语句需要遵循一个RETURN
语句(LET
也允许使用中间语句)。这些语句可以引用伪值OLD
和NEW
。该OLD
伪值是指文件修订更新之前,并NEW
涉及文件更新后的版本。
双方OLD
并NEW
会包含所有文档属性,即使是那些在更新的表达没有规定。
UPDATE document IN collection options RETURN OLD UPDATE document IN collection options RETURN NEW UPDATE keyExpression WITH document IN collection options RETURN OLD UPDATE keyExpression WITH document IN collection options RETURN NEW
以下是使用名称为变量prevIoUs
捕获原始文档的变量的示例。对于每个修改的文档,返回文档键。
FOR u IN users
UPDATE u WITH { value: "test" }
LET prevIoUs = OLD
RETURN prevIoUs._key
FOR u IN users
UPDATE u WITH { value: "test" }
LET updated = NEW
RETURN UNSET(updated,"_key","_id","_rev")
它也可以同时返回OLD
和NEW
:
FOR u IN users
UPDATE u WITH { value: "test" }
RETURN { before: OLD,after: NEW }
=================================================================================================
REPLACE
该REPLACE关键字可以用来完全取代集合中的文档。在单个服务器上,替换操作是以全或无任何方式事务执行的。对于分片集合,整个替换操作不是事务性的。
每个REPLACE操作仅限于单个集合,集合名称不能是动态的。每个AQL查询只允许每个集合使用一个REPLACE语句,并且不能遵循访问同一集合的读取操作,遍历操作或可以读取文档的AQL函数。系统属性_id,_key和_rev不能被替换,_from和_to可以。
替换操作的两种语法有:
REPLACE document IN collection options REPLACE keyExpression WITH document IN collection options
集合必须包含应替换文档的集合的名称。文件是替换文件。当使用第一种语法时,文档还必须包含_key属性来标识要替换的文档。
FOR u IN users REPLACE { _key: u._key,u.lastName),status: u.status } IN users
以下查询无效,因为它不包含_key属性,因此无法确定要替换的文档:
FOR u IN users REPLACE { name: CONCAT(u.firstName,status: u.status) } IN users
当使用第二种语法时,keyExpression提供文档标识。这可以是一个字符串(必须包含文档键)或一个必须包含_key属性的文档。
以下查询是等效的:
FOR u IN users REPLACE { _key: u._key,u.lastName) } IN users FOR u IN users REPLACE u._key WITH { name: CONCAT(u.firstName,u.lastName) } IN users FOR u IN users REPLACE { _key: u._key } WITH { name: CONCAT(u.firstName,u.lastName) } IN users FOR u IN users REPLACE u WITH { name: CONCAT(u.firstName,u.lastName) } IN users
替换将完全替换现有文档,但不会修改内部属性(如_id,_key,_from和_to)的值。更换文档将使用服务器生成的值修改文档的修订版本号。
替换操作可以更新不需要与前面的FOR语句生成的文档相同的任意文档:
FOR i IN 1..1000 REPLACE CONCAT('test',i) WITH { foobar: true } IN users FOR u IN users FILTER u.active == false REPLACE u WITH { status: 'inactive',name: u.name } IN backup
设置查询选项
选项可用于抑制尝试替换不存在的文档或违反唯一键约束时可能发生的查询错误:
FOR i IN 1..1000 REPLACE { _key: CONCAT('test',i) } WITH { foobar: true } IN users OPTIONS { ignoreErrors: true }
为了确保替换查询返回时数据是持久的,有waitForSync查询选项:
FOR i IN 1..1000 REPLACE { _key: CONCAT('test',i) } WITH { foobar: true } IN users OPTIONS { waitForSync: true }
返回修改后的文档
修改后的文档也可以由查询返回。在这种情况下,REPLACE
语句必须跟在一个RETURN
语句之后(LET
也允许使用中间语句)。所述OLD
伪值可以用于参考文档的修订前的替换,并NEW
涉及文件后的替换修改。
双方OLD
并NEW
会包含所有文档属性,即使是那些在替换表达没有规定。
REPLACE document IN collection options RETURN OLD REPLACE document IN collection options RETURN NEW REPLACE keyExpression WITH document IN collection options RETURN OLD REPLACE keyExpression WITH document IN collection options RETURN NEW
以下是使用名称为变量prevIoUs
返回原始文档的变量的示例。对于每个替换的文档,文档键将被返回:
FOR u IN users REPLACE u WITH { value: "test" } LET prevIoUs = OLD RETURN prevIoUs._key
以下查询使用NEW
伪值返回已替换的文档(不包含其某些系统属性):
FOR u IN users REPLACE u WITH { value: "test" } LET replaced = NEW RETURN UNSET(replaced,'_key','_id','_rev')================================================================================================
INSERT
在INSERT关键字可用于插入新的文件到一个集合。在单个服务器上,插入操作以全或无任何方式进行交易。对于分片集合,整个插入操作不是事务性的。
每个INSERT操作仅限于单个集合,集合名称不能是动态的。每个AQL查询只允许一个集合中的单个INSERT语句,而不能遵循访问相同集合的读取操作,遍历操作或可以读取文档的AQL函数。
插入操作的语法是:
INSERT document IN collection options
注意:INTO关键字也被允许在IN的地方。
集合必须包含要插入文档的集合的名称。document是要插入的文档,它可能包含也可能不包含_key属性。如果没有提供_key属性,ArangoDB将自动生成_key值的值。插入文档还会自动生成文档的文档版本号。
FOR i IN 1..100
INSERT { value: i } IN numbers
当插入到边集合中时,必须在文档中指定属性_from和_to:
FOR u IN users FOR p IN products FILTER u._key == p.recommendedBy INSERT { _from: u._id,_to: p._id } IN recommendations
设置查询选项
FOR i IN 1..1000
INSERT {
_key: CONCAT('test',i),
name: "test",
foobar: true
} INTO users OPTIONS { ignoreErrors: true }
为了确保在插入查询返回时数据是持久的,有waitForSync查询选项:
FOR i IN 1..1000
INSERT {
_key: CONCAT('test',
foobar: true
} INTO users OPTIONS { waitForSync: true }
返回插入的文档
200的X- 200 X- 200 200 X- 200 200 X- 200 200 X- 1992:在这种情况下,INSERT
语句可以是一个RETURN
语句(LET
也允许使用中间语句)。要引用插入的文档,该INSERT
语句引入了一个伪值NEW
。
中包含的文件NEW
将包含所有属性,即使是那些自动生成的由数据库(例如_id
,_key
,_rev
)。
INSERT document IN collection options RETURN NEW
以下是使用一个名为inserted
return的变量返回插入的文档的示例。新新新新200新新200新200新新200新200新200新200新新200新新200新200新新200新200新新200新200新新200新200新新200新新200新新新200新新200新
FOR i IN 1..100
INSERT { value: i }
LET inserted = NEW
RETURN inserted._key
==========================================================================================
UPSERT
该UPSERT关键字可用于检查是否存在某些文件,并更新/更换它们的情况下,它们的存在,或者创建他们的情况下,它们不存在。在单个服务器上,插件是以全或无任何方式交易执行的。对于分片集合,整个更新操作不是事务性的。
每个UPSERT操作仅限于一个集合,集合名称不能是动态的。每个AQL查询只允许每个集合的一个单独的UPSERT语句,并且不能遵循访问相同集合的读取操作,通过遍历操作或可以读取文档的AQL函数。
upsert操作的语法是:
UPSERT searchExpression INSERT insertExpression UPDATE updateExpression IN collection options UPSERT searchExpression INSERT insertExpression REPLACE updateExpression IN collection options
当使用upsert操作的UPDATE变体时,找到的文档将被部分更新,这意味着只有updateExpression中指定的属性将被更新或添加。当使用upsert的REPLACE变体时,现有文档将被updateExpression的上下文替换。
更新文档将使用服务器生成的值修改文档的修订版本号。系统属性_id,_key和_rev无法更新,_from和_to可以。
该searchExpression包含要寻找的文件。它必须是没有动态属性名称的对象文字。如果在集合中找不到这样的文档,那么将在insertExpression中指定的新文档中插入一个新的文档。
如果集合中至少有一个文档与searchExpression匹配,则将使用updateExpression进行更新。当集合中的多个文档与searchExpression匹配时,未定义哪些匹配文档将被更新。因此,通过其他方式(例如唯一索引,应用程序逻辑等)确保最多一个文档与searchExpression匹配,因此通常是合理的。
以下查询将在用户集合中查找具有特定名称属性值的文档。如果文档存在,其登录属性将增加1。如果不存在,将插入一个新的文档,其中包括属性名称,登录名和dateCreated:
UPSERT { name: 'superuser' } INSERT { name: 'superuser',logins: 1,dateCreated: DATE_NOW() } UPDATE { logins: OLD.logins + 1 } IN users
请注意,在UPDATE情况下,可以使用OLD伪值参考文档的先前版本。
设置查询选项
如上述几个示例中,ignoreErrors选项可用于抑制尝试违反唯一键约束时可能发生的查询错误。
当使用空值更新或替换属性时,ArangoDB不会从文档中删除该属性,而是为其存储一个空值。为了摆脱upsert操作中的属性,将它们设置为null并提供keepNull选项。
如果UPDATE查询和待更新文档中都存在对象属性,那么还可以使用mergeObjects选项来控制对象内容是否被合并。
注:为默认值mergeObjects是真的,所以没有必要明确指定。
为了确保在更新查询返回时数据是持久的,有waitForSync查询选项。
退回文件
UPSERT
语句可以选择返回数据。为此,他们需要跟着一个RETURN
声明(LET
也允许使用中间语句)。这些语句可以选择执行计算,并参考伪值OLD
和NEW
。如果upsert执行了插入操作,OLD
则值为null。如果upsert执行更新或替换操作,OLD
将在更新/替换之前包含该文档的先前版本。
NEW
将始终填充。它将包含插入的文档,以防起来执行插入,或更新/替换的文档,以防执行更新/替换。
这也可以用来检查upsert是否在内部执行了插入或更新:
UPSERT { name: 'superuser' } INSERT { name: 'superuser',dateCreated: DATE_NOW() } UPDATE { logins: OLD.logins + 1 } IN users RETURN { doc: NEW,type: OLD ? 'update' : 'insert' }===========================================================================================
WITH
AQL查询可以选择以WITH语句和查询使用的集合列表开头。除了查询使用的其他集合以及由AQL查询解析器检测到的其他集合之外,WITH中指定的所有集合都将在查询开始时被读取锁定。
在WITH中指定进一步的集合对于动态访问集合的查询(例如,通过遍历或通过动态文档访问功能DOCUMENT()
)可能很有用。在查询编译时,这样的集合可能对AQL查询解析器是不可见的,因此在查询开始时将不会自动被锁定。在这种情况下,AQL执行引擎将在使用它们时将这些集合懒惰地锁定,这可能导致与其他查询的死锁。在检测到这种死锁的情况下,查询将自动中止,更改将被回滚。在这种情况下,客户端应用程序可以尝试重新发送查询。但是,如果客户端应用程序使用WITH指定所有查询的已使用集合的列表,
WITH
为了避免死锁,从群集环境中遍历到ArangoDB 3.1起是必需的。
请注意,对于仅访问单个集合或在查询字符串中指定其他位置的所有集合名称的查询,不需要使用WITH。WITH仅在AQL查询解析器无法自动确定查询将使用哪些集合时有用。WITH仅对动态访问集合的查询有用,例如通过遍历,最短路径操作或DOCUMENT()函数。
WITH managers,usersHaveManagers FOR v,e,p IN OUTBOUND 'users/1' GRAPH 'userGraph' RETURN { v,p }
请注意,常量WITH也是在其他上下文中使用的关键字,例如在UPDATE语句中。如果使用WITH来指定集合的额外列表,则必须将其放置在查询字符串的最初位置。