ArangoDB 入门指南:查询数据库

原创 arangodb

是时候通过 AQL(ArangoDB' query language) ArangoDB 查询语言来查看我们的文档了。我们可以直接通过我们创建的 _id 属性查找文档(当然我们还可以使用其它选项)。

点击 QUERIES 菜单来显示 query editor(查询编辑器),输入以下的内容(具体取决于你的 document ID):

RETURN DOCUMENT("users/9883")

然后点击 Execute 来启动查询,结果如下所示:

[
  {
    "_key": "9883",
    "_id": "users/9883",
    "_rev": "9883",
    "age": 32,
    "name": "John Smith"
  }
]

结果出现在编辑器下方。

如你所见,程序返回了整个文档,包含着系统属性。DOCUMENT() 函数会根据你提供的 _keys 或者 _ids 返回一系列或者单个文档。

我们管返回的结果叫做查询结果,它是一个数组,包含了我们的文档查询结果(我们可能会得到不只一个文档,但是即使只有一个文档结果,它仍然会返回最上层的数组)。

这种类型的查询称为数据访问查询(data access query)。这种查询不会创建、更改或删除数据。还有另一种类型的查询,称为数据修改查询(data modification query)。让我们使用修改查询插入第二个文档:

INSERT { name: "Katie Foster", age: 27 } INTO users

查询非常容易看懂: INSERT 关键词告诉 ArangoDB 我们想插入一些东西。后面紧跟着的是我们要插入的东西,在这个案例中是一个拥有两个属性的文档。 花括号 {} 表示文档或对象。我们所说的文档是指集合中的记录。当用 JSON 编码时,我们叫它对象。对象也可以嵌套。下面举个例子:

{
  "name": {
    "first": "Katie",
    "last": "Foster"
  }
}

INTO 必须跟在每一个 INSERT 操作后面,后面再接上我们储存文档的集合的名字。注意集合的名字不必加上引号。

如果你运行上面的查询语句,会返回一个空数组,因为你没有用 RETURN 关键词指定要返回的内容。 RETURN 关键词在修改查询中是可选项,但在数据访问查询中是必选项。就算用上 RETURN,返回值也可能是空数组,比如特殊文档无法找到的情况。 尽管结果为空,以上的查询仍然会创建 user 文档。你可以在文档浏览器中验证这一点。

这一次我们添加一个 user 文档,并且让新的结果返回。

INSERT { name: "James Hendrix", age: 69 } INTO users
RETURN NEW

NEW 是一个伪变量,指向 INSERT 语句新建的文档。查询结果如下:

[
  {
    "_key": "10074",
    "_id": "users/10074",
    "_rev": "10074",
    "age": 69,
    "name": "James Hendrix"
  }
]

现在我们一个有三个 user 了。如何用一条语句返回全部数据呢?下面的方法不起作用

RETURN DOCUMENT("users/9883")
RETURN DOCUMENT("users/9915")
RETURN DOCUMENT("users/10074")

这里仅有一条 RETURN 语句,如果你尝试执行,则会抛出系统错误。DOCUMENT() 函数提供了一个补充方法来指定多文档处理,所以我们可以这么做:

RETURN DOCUMENT( ["users/9883", "users/9915", "users/10074"] )

所有3个文档的带有 _ids 的数组会被传递给函数,数组通过方括号 [ ] 表示,而其元素使用逗号进行分隔。

但是如果我们添加更多 user 会怎样呢?我们同时需要修改查询来获取新添加的用户。关于我们的查询,我们希望表达的是::"对于 users 集合中的每一个用户,返回用户文档"。我们可以使用 FOR 循环表达该查询:

FOR user IN users
  RETURN user

它表达的是对 users 中的所有文档进行迭代并使用 user 作为变量名,从而我们可以用来指代当前用户文档。它可以被称为 docuahuacatlguacamole,这取决于你。然而建议使用一个简短并自描述的名字。

循环体告诉系统返回变量 user 的值,这是一个用户文档。可以像下面这样返回所有用户文档:

[
  {
    "_key": "9915",
    "_id": "users/9915",
    "_rev": "9915",
    "age": 27,
    "name": "Katie Foster"
  },
  {
    "_key": "9883",
    "_id": "users/9883",
    "_rev": "9883",
    "age": 32,
    "name": "John Smith"
  },
  {
    "_key": "10074",
    "_id": "users/10074",
    "_rev": "10074",
    "age": 69,
    "name": "James Hendrix"
  }
]

也许你已经注意到返回的文档顺序与插入顺序并不相同。ArangoDB 并不保证文档顺序,除非你显式对其进行排序。我们可以很容易添加了一个 SORT 操作:

FOR user IN users
  SORT user._key
  RETURN user

这依然不会返回预期的结果:James (10074) 会在 John (9883) 与 Katie (9915) 之前返回。

原因在于 _key 属性在 ArangoDB 中是一个字符串,而不是一个数字。字符串的单个字符会被进行比较。1 小于 9,因而结果是“正确”的。

如果我们希望使用数值作为 _key 属性的值,我们可以将字符串转换为数字并用其进行排序。然而这样做有一些影响。我们最好排序其他内容。年龄怎么样?以降序排列吗?

FOR user IN users
  SORT user.age DESC
  RETURN user

用户的数据会以如下的顺序返回:James (69), John (32), Katie (27)。与用 DESC 返回降序结果不同,ASC 返回升序结果。ASC 是默认的选项,可以省略。

我们可能需要根据用户的年龄返回一个子集。让我们返回 30 岁以上的用户的数据:

FOR user IN users
  FILTER user.age > 30
  SORT user.age
  RETURN user

这么做会按顺序返回 John 和 James。 Katie 的 age 的属性不满足三十岁以上的条件,她只有27岁,因此不在结果之中。我们可以修改她的年龄,使她重新包含在返回结果之中,使用如下的查询语句:

UPDATE "9915" WITH { age: 40 } IN users
RETURN NEW

UPDATE 允许部分编辑已存在的文档。另外有 REPLACE,会移除所有属性 (除了 _key_id 保持不变) 并且仅添加部分属性。另一方面 UPDATE 替换指定的属性而保持其他属性不变。

UPDATE 关键字后跟文档键 (或者带有 _key 属性的文档 / 对象) 来指定要修改的文档。要更新的属性作为对象写在 WITH 关键字后面。IN 表示在哪个集合中执行该操作,类似 INTO (这里两个关键字可以互换)。如果我们使用 NEW 伪变量则会返回应用修改的全部文档:

[
  {
    "_key": "9915",
    "_id": "users/9915",
    "_rev": "12864",
    "age": 40,
    "name": "Katie Foster"
  }
]

相反如果我们使用 REPLACEname 属性会丢失。使用 UPDATE,属性会被保留 (如果我们有其他的属性,也同样适用该规则)。

让我们再次运行 FILTER 查询,但是这一次仅返回用户名:

FOR user IN users
  FILTER user.age > 30
  SORT user.age
  RETURN user.name

这会返回所有3个用户的名字:

[
  "John Smith",
  "Katie Foster",
  "James Hendrix"
]

如果仅返回一个属性的子集,则将其称为投影(projection),另一种投影类型是改变结果的结构:

FOR user IN users
  RETURN { userName: user.name, age: user.age }

该查询为所有的用户文档定义了输出格式,用户名作为 userName 返回,而不是 name,在该示例中 age 与属性性键相同:

[
  {
    "userName": "James Hendrix",
    "age": 69
  },
  {
    "userName": "John Smith",
    "age": 32
  },
  {
    "userName": "Katie Foster",
    "age": 40
  }
]

也可以计算新值:

FOR user IN users
  RETURN CONCAT(user.name, "'s age is ", user.age)

CONCAT() 是一个将元素合并为字符串的函数。在这里我们用其为所有用户返回一个陈述。正如你看到的,结果集合并不总是一个对象数组:

[
  "James Hendrix's age is 69",
  "John Smith's age is 32",
  "Katie Foster's age is 40"
]

现在让我们来做一些疯狂的事情:对于用户集合中的所有文档,再次对所有用户文档进行迭代并返回用户组合,例如 John 与 Katie。对于该问题,我们可以在一个循环内部使用一个循环来获得叉积 (所有用户记录的所有可能组合,3 x 3 = 9)。 然而我们并不希望得到类似 John + John 的组合,所以让我们使用一个过滤器条件来去除类似的组合:

FOR user1 IN users
  FOR user2 IN users
    FILTER user1 != user2
    RETURN [user1.name, user2.name]

我们得到 6 对组合。类似 James + John 与 John + James 的组合是重复的,但是已足够好:

[
  [ "James Hendrix", "John Smith" ],
  [ "James Hendrix", "Katie Foster" ],
  [ "John Smith", "James Hendrix" ],
  [ "John Smith", "Katie Foster" ],
  [ "Katie Foster", "James Hendrix" ],
  [ "Katie Foster", "John Smith" ]
]

我们可以像下面这样计算两个年龄之和并计算一些新的内容:

FOR user1 IN users
  FOR user2 IN users
    FILTER user1 != user2
    RETURN {
        pair: [user1.name, user2.name],
        sumOfAges: user1.age + user2.age
    }

我们引入一个新的属性 sumOfAges 并将两个年龄相加作为其值:

[
  {
    "pair": [ "James Hendrix", "John Smith" ],
    "sumOfAges": 101
  },
  {
    "pair": [ "James Hendrix", "Katie Foster" ],
    "sumOfAges": 109
  },
  {
    "pair": [ "John Smith", "James Hendrix" ],
    "sumOfAges": 101
  },
  {
    "pair": [ "John Smith", "Katie Foster" ],
    "sumOfAges": 72
  },
  {
    "pair": [ "Katie Foster", "James Hendrix" ],
    "sumOfAges": 109
  },
  {
    "pair": [ "Katie Foster", "John Smith" ],
    "sumOfAges": 72
  }
]

如果我们希望过滤新属性来仅返回总和小于100的组合,我们应该定义一个变量来临时存储总和,从而我们可以在 FILTER 语句以及 RETURN 语句中使用:

FOR user1 IN users
  FOR user2 IN users
    FILTER user1 != user2
    LET sumOfAges = user1.age + user2.age
    FILTER sumOfAges < 100
    RETURN {
        pair: [user1.name, user2.name],
        sumOfAges: sumOfAges
    }

LET 关键字后跟指定的变量名 (sumOfAges),然后是 = 符号与值或表达式来定义变量的值。在这里我们重用我们的表达式来计算总和。然后我们使用另一个 FILTER 来略过不需要的组合并使用我们之前声明的变量。我们使用用户名与所计算的年龄值的数组返回一个投影,为此我们再次使用变量:

[
  {
    "pair": [ "John Smith", "Katie Foster" ],
    "sumOfAges": 72
  },
  {
    "pair": [ "Katie Foster", "John Smith" ],
    "sumOfAges": 72
  }
]

小贴士:当定义对象时,如果所要求的属性键与属性值所用的变量相同,你可以使用简写形式: { sumOfAges } 替代 { sumOfAges: sumOfAges }

最后,让我们删除一个用户文档:

REMOVE "9883" IN users

它会删除用户 John (_key: "9883")。我们也可以在循环中移除文档 (同样适用于 INSERTUPDATEREPLACE):

FOR user IN users
    FILTER user.age >= 30
    REMOVE user IN users

该查询会删除年龄大于等于 30 的所有用户。


ArangoDB 官方文档原文地址:Querying the Database

如果觉得这对你有用,请随意赞赏,给与作者支持
评论 0
最新评论