如何查找和查询数据#

AiiDA 数据库存储了一个连接实体的图形,可以用 QueryBuilder 类进行*查询。

在开始撰写查询之前,最好先

  • 了解您要查询的内容。
    在数据库语言中,您需要告诉后端您要查找的 实体 ,以及您要 投影实体 属性。
    例如,您可能对某个计算的标签及其所有输出的 PKs 感兴趣。
  • 了解您感兴趣的实体之间的关系。
    AiiDA 图的 Node 个点(顶点)通过链接(边)相连。
    例如,一个 node 可以是另一个 node 的输入或输出,也可以是祖先或后代。
  • 了解如何过滤查询结果。

一旦您清楚自己想要什么以及如何获得这些信息,QueryBuilder 就会为您创建一个 SQL 查询。

使用 QueryBuilder 有两种方法:

  1. appender 方法中,您使用 QueryBuilder.append() 方法逐步构建查询。

  2. 字典 方法中,您需要构建一个定义查询的字典,并将其传递给 QueryBuilder

这两种应用程序接口提供相同的功能–附加器方法可能更适合交互式使用,例如在 verdi shell 中,而字典方法在脚本中可能很有用。在本节中,我们将重点介绍附加器方法的基础知识。有关更高级的查询或查询字典的更多详情,请参阅 topics section on advanced querying

选择实体#

使用 QueryBuilderappend() 方法,可以查询您感兴趣的实体。假设您想查询数据库中的计算作业 nodes:

from aiida.orm import QueryBuilder
qb = QueryBuilder()       # Instantiating instance. One instance -> one query
qb.append(CalcJobNode)    # Setting first vertex of path

如果您对不同类的实例感兴趣,也可以传递一个可迭代类。不过,它们必须是相同的 ORM 类型(例如,所有类都必须是 Node 的子类):

qb = QueryBuilder()       # Instantiating instance. One instance -> one query
qb.append([CalcJobNode, WorkChainNode]) # Setting first vertices of path, either WorkChainNode or Job.

备注

进程既有执行进程的运行时 Process ,也有在数据库中存储数据的 Node (详见 corresponding topics section 的解释)。 QueryBuilder 允许您传递 Node 类(如 CalcJobNode )或 Process 类(如 CalcJob ),这将自动为查询选择正确的实体。使用 CalcJobNodeCalcJob 将产生相同的查询结果。

检索结果#

将要查询的实体应用到 QueryBuilder 后,下一个问题就是如何获取查询结果。从查询中获取数据有几种方法:

qb = QueryBuilder()                 # Instantiating instance
qb.append(CalcJobNode)              # Setting first vertices of path

first_row = qb.first()              # Returns a list (!) of the results of the first row

all_results_d = qb.dict()           # Returns all results as a list of dictionaries

all_results_l = qb.all()            # Returns a list of lists

小技巧

如果您的查询只有一个投影,请在 firstall 方法中使用 flat=True 分别返回单个值或平面列表。

您也可以将查询作为生成器返回:

all_res_d_gen = qb.iterdict()       # Return a generator of dictionaries
all_res_l_gen = qb.iterall()        # Returns a generator of lists

这样就可以分批检索数据,在查询完全结束之前就可以开始处理数据。例如,可以在 for 循环中遍历查询结果:

for entry in qb.iterall():
    # do something with a single entry in the query result

重要

当循环查询结果时,使用 iterall (或 iterdict) 生成器而不是 all (或 dict)。这样可以避免将整个查询结果加载到内存中,也可以延迟提交循环中对 AiiDA 对象的修改,直到循环结束。如果在循环结束前出现异常,所有的修改都会被还原。

过滤器#

通常情况下,您并不想查询某一类别的 所有 实体,而是根据某些属性对结果进行*过滤。假设您不想要所有 CalcJobNode 数据,而只想要 finished 的数据:

qb = QueryBuilder()                 # Initialize a QueryBuilder instance
qb.append(
    CalcJobNode,                    # Append a CalcJobNode
    filters={                       # Specify the filters:
        'attributes.process_state': 'finished',  # the process is finished
    },
)

您可以在查询中对一个实体应用多个过滤器。例如,您想查询数据库中 finished exit_status == 0 的所有计算作业:

qb = QueryBuilder()                 # Initialize a QueryBuilder instance
qb.append(
    CalcJobNode,                    # Append a CalcJobNode
    filters={                       # Specify the filters:
        'attributes.process_state': 'finished',     # the process is finished AND
        'attributes.exit_status': 0                 # has exit_status == 0
    },
)

如果要查询满足这些条件之一的计算作业,可以使用 or 操作符:

qb = QueryBuilder()
qb.append(
    CalcJobNode,
    filters={
        'or':[
            {'attributes.process_state': 'finished'},
            {'attributes.exit_status': 0}
        ]
    },
)

如果我们在上面的示例中写的是 and,而不是 or,我们就会执行与前面的查询完全相同的查询,因为 and 是向 filters 参数提供多个过滤器作为 dictionary 中键值对的默认行为。如果希望所有计算作业的状态都是 finishedexcepted,也可以使用 in 操作符:

qb = QueryBuilder()
qb.append(
    CalcJobNode,
    filters={
        'attributes.process_state': {'in': ['finished', 'excepted']}
    },
)

Programmatic syntax for filters#

Added in version 2.6.

Filter keys may be defined programmatically, providing in modern IDEs (including AiiDA’s verdi shell) autocompletion of fields and operators. For example, the above query may be given as

qb = QueryBuilder()
qb.append(
    CalcJobNode,
    filters={
        CalcJobNode.fields.process_state: {'in': ['finished', 'excepted']},
    },
)

In this approach, CalcJobNode.fields. will suggest (autocomplete) the queryable fields of CalcJobNode allowing the user to explore the node’s attributes directly while constructing the query.

Alternatively, the entire filtering expression may be provided programmatically as logical expressions:

qb = QueryBuilder()
qb.append(
    CalcJobNode,
    filters=CalcJobNode.fields.process_state.in_(['finished', 'excepted']),
)

备注

Logical operations are distributed by type. As such, Node.fields.<some_field>. will only provide the supported operations for the type of some_field, in this case ==, in_, like, and ilike, for type str.

Logical expressions may be strung together with & and | to construct complex queries.

filters=(
    (Node.fields.ctime < datetime(2030, 1, 1))
    & (
        (Node.fields.pk.in_([4, 8, 15, 16, 23, 42]))
        | (Node.fields.label.like("%some_label%"))
    )
    & (Node.fields.extras.has_key("some_key"))
)

小技巧

() may be used to override the natural precedence of |.

运算符否定#

通过在运算符前添加感叹号 !,可以将过滤器转换为相关的 否定 。因此,要查询所有非 finishedexcepted 状态的计算作业,请执行以下操作

qb = QueryBuilder()
qb.append(
    CalcJobNode,
    filters={
        'attributes.process_state': {'!in': ['finished', 'excepted']}
    },
)

备注

上述规则适用于所有运算符。例如,您可以用 !== 检查非相等运算符,因为它是带否定前置的相等运算符 (==)。

所有可用操作符的完整列表可在 advanced querying section 中找到。

Added in version 2.6: Programamtic filter negation

In the new logical expression syntax, negation can be achieved by prepending ~ to any expression. For example ~(Int.fields.value < 5) is equivalent to Int.fields.value >= 5.

关系#

可以根据数据与数据库中另一个实体的关系来查询数据。试想一下,您对计算作业本身不感兴趣,但对它们创建的某个输出结果感兴趣。您可以利用输出与查询第一步的关系,对数据库中的所有 CalcJobNode 进行初步查询:

qb = QueryBuilder()
qb.append(CalcJobNode, tag='calcjob')
qb.append(Int, with_incoming='calcjob')

在第一个 append 调用中,我们查询数据库中的所有 CalcJobNode,并在此步骤中 标记 唯一*标识符 'calcjob'。接下来,我们使用 with_incoming 关系参数查找所有 Int node 文件,它们是第一步中找到的 CalcJobNode 的输出。Int node 是由 CalcJobNode 创建的,因此有一个 * 传入 * 创建链接。

在我们查询的上下文中,我们正在构建一条 路径 ,它由 顶点 (即我们查询的实体)和 组成, 由它们之间的关系定义。您可以在 advanced querying section 中找到所有可能的查询关系及其连接的实体的完整集合。

备注

tag 标识符可以是任何字母数字字符串,它只是一个标签,用于在定义关系时引用查询路径上的前一个顶点。

预测#

默认情况下,QueryBuilder 会返回与查询路径的最后附加部分相对应的实体实例。例如

qb = QueryBuilder()
qb.append(CalcJobNode, tag='calcjob')
qb.append(Int, with_incoming='calcjob')

上述代码片段将返回任何 CalcJobNode 输出的所有 Int node。不过,您也可以通过在相应的 append() 调用中添加 project='*'project 路径中的其他实体:

qb = QueryBuilder()
qb.append(CalcJobNode, tag='calcjob', project='*')
qb.append(Int, with_incoming='calcjob')

这将返回所有具有 Int 输出 node 的 CalcJobNode

不过,在很多情况下,我们感兴趣的不是实体本身,而是实体的 PK、UUID、 属性 或实体存储的其他信息。这可以通过向 project 关键字参数提供相应的 来实现:

qb = QueryBuilder()
qb.append(CalcJobNode, tag='calcjob')
qb.append(Int, with_incoming='calcjob', project='id')

在上例中,执行查询会返回 Int node 的所有 PK’s ,这些 PK’s 是数据库中所有 CalcJobNode 的输出。此外,您还可以通过提供一个列表,为一个顶点预测多个信息:

qb = QueryBuilder()
qb.append(CalcJobNode, tag='calcjob')
qb.append(Int, with_incoming='calcjob', project=['id', 'attributes.value'])

对于上述查询,qb.all() 将返回一个列表,其中每个元素对应一个实体,并包含两个项目:Int 的 PK node 及其值。最后,您可以沿查询路径投影多个顶点的信息:

qb = QueryBuilder()
qb.append(CalcJobNode, tag='calcjob', project='*')
qb.append(Int, with_incoming='calcjob', project=['id', 'attributes.value'])

所有投影必须以数据库中实体的 之一开始,或使用 '*' 投射实例本身。目前我们遇到的列示例有 iduuidattributes 。如果列是一个字典,则可以使用点号展开字典值,就像我们在前面的示例中获得 attributes.value 一样。这也可用于投影嵌套字典的值。

备注

请注意,为了保持一致性,QueryBuilder.all() / iterall() 总是返回一个列表,即使您只预测了单个实体的一个属性。在这种情况下,请使用 QueryBuilder.all(flat=True) 以平面列表的形式返回查询结果。

Added in version 2.6: Programmatic syntax for projections

Similar to filters, projections may also be provided programmatically, leveraging the autocompletion feature of modern IDEs.

qb = QueryBuilder()
qb.append(
    Int,
    project=[
        Int.fields.pk,
        Int.fields.value,
    ],
)

如开头所述,本节仅简要介绍 QueryBuilder 的基本功能。要了解更多高级查询,请参阅 the corresponding topics section

捷径#

The QueryBuilder is the generic way of querying for data in AiiDA. For certain common queries, shortcuts have been added to the AiiDA python API to save you a couple of lines of code.

流程 (processes) 的输入和输出#

The get_incoming() and get_outgoing() methods, described in the previous section, can be used to access all neighbors from a certain node and provide advanced filtering options. However, often one doesn’t need this expressivity and simply wants to retrieve all neighboring nodes with a syntax that is as succint as possible. A prime example is to retrieve the inputs or outputs of a process. Instead of using get_incoming() and get_outgoing(), to get the inputs and outputs of a process_node one can do:

inputs = process_node.inputs
outputs = process_node.outputs

这些属性不会直接返回实际的输入和输出,而是返回 NodeLinksManager 的实例。原因是通过管理器,输入或输出可以通过其链接标签(对于流程的输入和输出,链接标签是唯一的)进行访问,并可以用制表符完成。例如,如果 process_node 有一个标签为 result 的输出,则可以按以下方式检索:

process_node.outputs.result

输入或输出也可以通过键的反向引用来访问:

process_node.outputs['result']

如果没有与给定链接标签相邻的输出,将分别出现 NotExistentAttributeErrorNotExistentKeyError

备注

inputsoutputs 属性仅为 ProcessNode 定义。这意味着不能 chain 这些调用,因为进程 node 的输入或输出保证是 Data node,而后者没有输入或输出。

创造者、召唤者和被召唤者#

与进程 node 的 inputsoutputs 属性类似,还有一些属性可以让我们更容易地探索 provenance graph:

  • called(): defined for ProcessNode’s and returns the list of process nodes called by this node. If this process node did not call any other processes, this property returns an empty list.

  • caller(): defined for ProcessNode’s and returns the process node that called this node. If this node was not called by a process, this property returns None.

  • creator(): defined for Data nodes and returns the process node that created it. If the node was not created by a process, this property returns None.

备注

利用 creatorinputs 属性,我们可以轻松地在 provenance graph 上**移动。例如,从表示长 workflow 结果的数据 node 开始,可以向上移动 provenance graph 以找到感兴趣的初始输入 node:result.creator.inputs.some_input.creator.inputs.initial_input

计算工作结果#

CalcJobNode’s provide the res() property, that can give easy access to the results of the calculation job. The requirement is that the CalcJob class that produced the node, defines a default output node in its spec. This node should be a Dict output that will always be created. An example is the TemplatereplacerCalculation plugin, that has the output_parameters output that is specified as its default output node.

The res() property will give direct easy access to all the keys within this dictionary output. For example, the following:

list(node.res)

将返回输出 node 中所有键的列表。然后就可以通过属性引用访问各个键:

node.res.some_key

在交互式 shell 中,可用按键也是以 tab 键完成的。如果输入 node.res.,然后按两次制表符键,就会打印出可用按键的列表。

备注

The res() property is really just a shortcut to quickly and easily access an attribute of the default output node of a calculation job. For example, if the default output node link label is output_parameters, then node.res.some_key is exactly equivalent to node.outputs.output_parameters.dict.some_key. That is to say, when using res, one is accessing attributes of one of the output nodes, and not of the calculation job node itself.