对AQL中的CRUD以及 LIMIT, SORT, FILTER语法有认识之后,将介绍如何将多个 Documents中的信息连接在一起。
本文以Python接口为例,但这些AQL语句均可直接通过ArangoDB的Web界面执行。
准备
数据库环境
from pyArango.connection import *
conn = Connection(username="root", password="") # 默认连接到本机的8529端口
db = conn['school'] # 连接到之前文章建立的school数据库
db.createCollection(name="Characters") # 为本文示例创建新Collection, 存放电影中的人物角色
数据
插入43个文档数据,插入后可通过ArangoDB的Web界面验证,或通过AQL查询验证
insert_query = """
LET data = [
{ "name": "Ned", "surname": "Stark", "alive": true, "age": 41, "traits": ["A","H","C","N","P"] },
{ "name": "Robert", "surname": "Baratheon", "alive": false, "traits": ["A","H","C"] },
{ "name": "Jaime", "surname": "Lannister", "alive": true, "age": 36, "traits": ["A","F","B"] },
{ "name": "Catelyn", "surname": "Stark", "alive": false, "age": 40, "traits": ["D","H","C"] },
{ "name": "Cersei", "surname": "Lannister", "alive": true, "age": 36, "traits": ["H","E","F"] },
{ "name": "Daenerys", "surname": "Targaryen", "alive": true, "age": 16, "traits": ["D","H","C"] },
{ "name": "Jorah", "surname": "Mormont", "alive": false, "traits": ["A","B","C","F"] },
{ "name": "Petyr", "surname": "Baelish", "alive": false, "traits": ["E","G","F"] },
{ "name": "Viserys", "surname": "Targaryen", "alive": false, "traits": ["O","L","N"] },
{ "name": "Jon", "surname": "Snow", "alive": true, "age": 16, "traits": ["A","B","C","F"] },
{ "name": "Sansa", "surname": "Stark", "alive": true, "age": 13, "traits": ["D","I","J"] },
{ "name": "Arya", "surname": "Stark", "alive": true, "age": 11, "traits": ["C","K","L"] },
{ "name": "Robb", "surname": "Stark", "alive": false, "traits": ["A","B","C","K"] },
{ "name": "Theon", "surname": "Greyjoy", "alive": true, "age": 16, "traits": ["E","R","K"] },
{ "name": "Bran", "surname": "Stark", "alive": true, "age": 10, "traits": ["L","J"] },
{ "name": "Joffrey", "surname": "Baratheon", "alive": false, "age": 19, "traits": ["I","L","O"] },
{ "name": "Sandor", "surname": "Clegane", "alive": true, "traits": ["A","P","K","F"] },
{ "name": "Tyrion", "surname": "Lannister", "alive": true, "age": 32, "traits": ["F","K","M","N"] },
{ "name": "Khal", "surname": "Drogo", "alive": false, "traits": ["A","C","O","P"] },
{ "name": "Tywin", "surname": "Lannister", "alive": false, "traits": ["O","M","H","F"] },
{ "name": "Davos", "surname": "Seaworth", "alive": true, "age": 49, "traits": ["C","K","P","F"] },
{ "name": "Samwell", "surname": "Tarly", "alive": true, "age": 17, "traits": ["C","L","I"] },
{ "name": "Stannis", "surname": "Baratheon", "alive": false, "traits": ["H","O","P","M"] },
{ "name": "Melisandre", "alive": true, "traits": ["G","E","H"] },
{ "name": "Margaery", "surname": "Tyrell", "alive": false, "traits": ["M","D","B"] },
{ "name": "Jeor", "surname": "Mormont", "alive": false, "traits": ["C","H","M","P"] },
{ "name": "Bronn", "alive": true, "traits": ["K","E","C"] },
{ "name": "Varys", "alive": true, "traits": ["M","F","N","E"] },
{ "name": "Shae", "alive": false, "traits": ["M","D","G"] },
{ "name": "Talisa", "surname": "Maegyr", "alive": false, "traits": ["D","C","B"] },
{ "name": "Gendry", "alive": false, "traits": ["K","C","A"] },
{ "name": "Ygritte", "alive": false, "traits": ["A","P","K"] },
{ "name": "Tormund", "surname": "Giantsbane", "alive": true, "traits": ["C","P","A","I"] },
{ "name": "Gilly", "alive": true, "traits": ["L","J"] },
{ "name": "Brienne", "surname": "Tarth", "alive": true, "age": 32, "traits": ["P","C","A","K"] },
{ "name": "Ramsay", "surname": "Bolton", "alive": true, "traits": ["E","O","G","A"] },
{ "name": "Ellaria", "surname": "Sand", "alive": true, "traits": ["P","O","A","E"] },
{ "name": "Daario", "surname": "Naharis", "alive": true, "traits": ["K","P","A"] },
{ "name": "Missandei", "alive": true, "traits": ["D","L","C","M"] },
{ "name": "Tommen", "surname": "Baratheon", "alive": true, "traits": ["I","L","B"] },
{ "name": "Jaqen", "surname": "H'ghar", "alive": true, "traits": ["H","F","K"] },
{ "name": "Roose", "surname": "Bolton", "alive": true, "traits": ["H","E","F","A"] },
{ "name": "The High Sparrow", "alive": true, "traits": ["H","M","F","O"] }
]
FOR d IN data
INSERT d INTO Characters
"""
db.AQLQuery(insert_query)
# 通过AQL验证已插入的数据
all_characters_names = """
FOR c IN Characters
RETURN c.name
"""
query_result = db.AQLQuery(all_characters_names, rawResults=True)
for doc in query_result:
print(doc)
Join 连接
插入的数据中,每个角色有个traits属性,它是一个字符串数组,而没有直接存储每个角色的特征:
find_ned_query = """
FOR c IN Characters
FILTER c.name == "Ned"
RETURN {"Name": c.name, "Traits": c.traits}
"""
query_result = db.AQLQuery(find_ned_query, rawResults=True)
for doc in query_result:
print(doc)
# 输出:
{'Name': 'Ned', 'Traits': ['A', 'H', 'C', 'N', 'P']}
此处把角色的特征存储为字母的数组,而不是有实际含义的具体特征,有如下考虑:使用另一个单独的Collection专门单独管理全部的特征,其中每一个文档代表一个具体特征,今后可以方便地集中管理,比如给每个特征加入其他语种的描述, 而在Characters集合中,存储的是特征文档的_key,可以很方便得解析到具体的特征.
反之,如果我们直接把特征嵌入到Characters集合中,如下所示:
JSON({
"Name": "Ned",
"Traits": [
{
"de": "stark",
"en": "strong"
},
{
"de": "einflussreich",
"en": "powerful"
},
{
"de": "loyal",
"en": "loyal"
},
{
"de": "rational",
"en": "rational"
},
{
"de": "mutig",
"en": "brave"
}
]
})
如果此时要加入新语种的支持,或者修改某个字母代表的特征,则该过程会很费力,远不如把特征单独存储起来方便。两种方式的区别,如下图所示:

建立单独的Collection
# 建立 Traits 集合
db.createCollection(name="Traits")
# 插入 trait Documents
insert_query = """
LET data = [
{ "_key": "A", "en": "strong", "de": "stark" },
{ "_key": "B", "en": "polite", "de": "freundlich" },
{ "_key": "C", "en": "loyal", "de": "loyal" },
{ "_key": "D", "en": "beautiful", "de": "schön" },
{ "_key": "E", "en": "sneaky", "de": "hinterlistig" },
{ "_key": "F", "en": "experienced", "de": "erfahren" },
{ "_key": "G", "en": "corrupt", "de": "korrupt" },
{ "_key": "H", "en": "powerful", "de": "einflussreich" },
{ "_key": "I", "en": "naive", "de": "naiv" },
{ "_key": "J", "en": "unmarried", "de": "unverheiratet" },
{ "_key": "K", "en": "skillful", "de": "geschickt" },
{ "_key": "L", "en": "young", "de": "jung" },
{ "_key": "M", "en": "smart", "de": "klug" },
{ "_key": "N", "en": "rational", "de": "rational" },
{ "_key": "O", "en": "ruthless", "de": "skrupellos" },
{ "_key": "P", "en": "brave", "de": "mutig" },
{ "_key": "Q", "en": "mighty", "de": "mächtig" },
{ "_key": "R", "en": "weak", "de": "schwach" }
]
FOR d IN data
INSERT d INTO Traits
"""
db.AQLQuery(insert_query)
# 插入后,快速检查
all_traits = """
FOR t IN Traits
RETURN t
"""
query_result = db.AQLQuery(all_traits, rawResults=True)
for doc in query_result:
print(doc)
连接Traits集合
前面提到过,我们的Characters集合中,没有直接存储特征,而是存储了特征的_key作为一个数组,使用ArangoDB中的 DOCUMENT()函数,可以将两个集合进行连接,DOCUMENT()的第一个参数,要填去哪里找真正的特征,本例中,我们存储在 Traits集合中;第二个参数,是_key的数组,用于去Traits集合中根据唯一标识符_key寻找对应的文档.
# 只返回英文版的traits
all_characters_traits = """
FOR c IN Characters
LIMIT 5
RETURN {"name": c.name, "traits": DOCUMENT("Traits", c.traits)[*].en}
"""
query_result = db.AQLQuery(all_characters_traits, rawResults=True)
for doc in query_result:
print(doc)
# 输出结果:
{'name': 'Ned', 'traits': ['strong', 'powerful', 'loyal', 'rational', 'brave']}
{'name': 'Robert', 'traits': ['strong', 'powerful', 'loyal']}
{'name': 'Jaime', 'traits': ['strong', 'experienced', 'polite']}
{'name': 'Catelyn', 'traits': ['beautiful', 'powerful', 'loyal']}
{'name': 'Cersei', 'traits': ['powerful', 'sneaky', 'experienced']}