ArangoDB AQL Join 教程

757 阅读4分钟

对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"
        }
    ]
})

如果此时要加入新语种的支持,或者修改某个字母代表的特征,则该过程会很费力,远不如把特征单独存储起来方便。两种方式的区别,如下图所示:

locations

建立单独的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']}

s

参考

[1]colab.research.google.com/github/aran…