如何查找和查询数据#
AiiDA 数据库存储了一个连接实体的图形,可以用 QueryBuilder
类进行*查询。
在开始撰写查询之前,最好先
- 了解您要查询的内容。在数据库语言中,您需要告诉后端您要查找的 实体 ,以及您要 投影 的 实体 属性。例如,您可能对某个计算的标签及其所有输出的 PKs 感兴趣。
- 了解您感兴趣的实体之间的关系。AiiDA 图的 Node 个点(顶点)通过链接(边)相连。例如,一个 node 可以是另一个 node 的输入或输出,也可以是祖先或后代。
- 了解如何过滤查询结果。
一旦您清楚自己想要什么以及如何获得这些信息,QueryBuilder
就会为您创建一个 SQL 查询。
使用 QueryBuilder
有两种方法:
在 appender 方法中,您使用
QueryBuilder.append()
方法逐步构建查询。在 字典 方法中,您需要构建一个定义查询的字典,并将其传递给
QueryBuilder
。
这两种应用程序接口提供相同的功能–附加器方法可能更适合交互式使用,例如在 verdi shell
中,而字典方法在脚本中可能很有用。在本节中,我们将重点介绍附加器方法的基础知识。有关更高级的查询或查询字典的更多详情,请参阅 topics section on advanced querying。
选择实体#
使用 QueryBuilder
的 append()
方法,可以查询您感兴趣的实体。假设您想查询数据库中的计算作业 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
),这将自动为查询选择正确的实体。使用 CalcJobNode
或 CalcJob
将产生相同的查询结果。
检索结果#
将要查询的实体应用到 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
小技巧
如果您的查询只有一个投影,请在 first
和 all
方法中使用 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 中键值对的默认行为。如果希望所有计算作业的状态都是 finished
或 excepted
,也可以使用 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 |
.
运算符否定#
通过在运算符前添加感叹号 !
,可以将过滤器转换为相关的 否定 。因此,要查询所有非 finished
或 excepted
状态的计算作业,请执行以下操作
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'])
所有投影必须以数据库中实体的 列 之一开始,或使用 '*'
投射实例本身。目前我们遇到的列示例有 id
、uuid
和 attributes
。如果列是一个字典,则可以使用点号展开字典值,就像我们在前面的示例中获得 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.
输入和输出链接#
AiiDA 中的 provenance graph 是 directed graph。图中的顶点是 nodes ,连接它们的边称为 链接 。由于图是有向的,因此任何一个 node 都可以有连接到相邻 node 的 入链 和 出链 。
要查找指定 node 的邻居,可以使用 get_incoming()
和 get_outgoing()
方法。这两个方法的接口完全相同,但会分别返回与当前 node 相连并有链路进入或有链路出去的邻居。例如,对于给定的 node
,要检查所有有链接进入该 node
的邻接 node:
node.get_incoming()
这将返回一个 LinkManager
实例。您可以通过该管理器以特定格式请求结果。如果您只对相邻的 nodes 本身感兴趣,可以调用 all_nodes
方法:
node.get_incoming().all_nodes()
这将返回一个 Node
实例的列表,这些实例与 node
的 node 相邻,其中链接指向 node
。调用管理器的 all()
方法将返回一个名为 LinkTriple
的元组列表。这些图元除了包含邻接 node 外,还包含与原点 node
连接的链路标签和链路类型。例如,要列出 node 的所有邻接区,其中有一条链接来自该邻接区:
for link_triple in node.get_incoming().all():
print(link_triple.node, link_triple.link_type, link_triple.link_label)
请注意,LinkManager
提供了许多从邻近 node 获取信息的便捷方法,例如,如果您只需要链接标签列表,可使用 all_link_labels()
。
The get_incoming()
and get_outgoing()
methods accept various arguments that allow one to filter what neighboring nodes should be matched:
node_class
:接受Node
的子类,只返回与该类匹配的相邻 nodeslink_type
:接受LinkType
的值,只返回与此链接类型链接的邻接 nodelink_label_filter
:接受字符串表达式(可选通配符,使用 SQLLIKE
模式的语法,见下文),只返回链接标签与模式匹配的邻接 nodes
举个例子
node.get_incoming(node_class=Data, link_type=LinkType.INPUT_CALC, link_label_filter='output%node_').all_nodes()
将只返回通过 LinkType.INPUT_CALC
类型的链接连接到 node
的相邻数据 node,且链接标签与模式 'output%node_'
匹配。提醒注意 SQL LIKE 模式的语法: %
匹配任意0个或多个字符 _
字符正好匹配一个字符。这两个特殊字符可以通过在它们前面加上反斜杠来转义(注意,在 Python 字符串中加上反斜杠时,必须转义反斜杠本身,因此需要两个反斜杠:例如,要精确匹配链接标签 a_b
,需要传递 link_label_filter='a\\_b'
)。
流程 (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']
如果没有与给定链接标签相邻的输出,将分别出现 NotExistentAttributeError
或 NotExistentKeyError
。
备注
inputs
和 outputs
属性仅为 ProcessNode
定义。这意味着不能 chain 这些调用,因为进程 node 的输入或输出保证是 Data
node,而后者没有输入或输出。
创造者、召唤者和被召唤者#
与进程 node 的 inputs
和 outputs
属性类似,还有一些属性可以让我们更容易地探索 provenance graph:
called()
: defined forProcessNode
’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 forProcessNode
’s and returns the process node that called this node. If this node was not called by a process, this property returnsNone
.creator()
: defined forData
nodes and returns the process node that created it. If the node was not created by a process, this property returnsNone
.
备注
利用 creator
和 inputs
属性,我们可以轻松地在 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.