pgUnionAll
如果您需要从多个表中提取数据,并且无法使用联接来完成,那么 pgUnionAll
就是您的朋友。
此步骤类使用 SQL UNION ALL
结构从一个或多个不同的表中选择一定数量(包括零,对于 GraphQL 联合来说这是一个很好的数字 😉)的字段,这些表可能都是 GraphQL 中同一个联合或接口的一部分。
您可以指定一个共享字段列表,如果这样做了,那么您可以按这些共享字段排序,或者对它们应用条件,我们将把这些排序和条件传递到各个表选择(作为 UNION ALL
的一部分)以确保我们以最有效的方式获得结果。当然,您仍然可以使用正常的 GraphQL 类型化片段扩展机制选择未共享的字段。
目前,这些共享字段必须在联合中的每个源上完全匹配名称和类型(我们不检查它,但如果您不遵守它,则在运行时可能会出现意外错误)。绝对有空间放宽这些要求 - 如果您需要,请与我们联系。
pgUnionAll
具有多态能力(但它不必是多态的!)并且支持限制/偏移和游标分页。
pgUnionAll 函数
pgUnionAll
函数接受一个参数 - PgUnionAllStepConfig
。此配置对象包含以下条目
resourceByTypeName
- (必需)从 GraphQL 类型名称到相关PgResource
的映射,从中可以获取匹配的记录。members
- (可选)一个包含要组合在union all
语句中的源和关系路径详细信息的列表;members
中的每个条目将成为另一个union all
'dselect
语句。如果未指定,我们将根据resourceByTypeName
为您生成成员。- 待办事项:记录成员的子键
attributes
- (可选)一个对象,定义可用的公共属性(如果有)作为从属性名称到规范对象的映射,该规范对象包含用于该属性的codec
;这通常与 GraphQL 接口一起使用mode
- (可选)normal
表示正常模式(默认),或aggregate
表示执行聚合(例如count(*)
)
每个source
必须具有相同的executor
,无论该源是显式定义还是通过遵循给定的关系隐式定义。
每个最终source
(在任何关系路径末尾找到的源)必须具有一个主键(source.uniques
中的一个条目,其中isPrimary === true
),该主键可用于获取与联合中条目匹配的结果记录。
应用条件
可以通过.where()
方法将条件应用于结果步骤,该方法接受一个包含以下键的对象
attribute
- 要针对其应用条件的pgUnionAll
attributes
中的属性(字符串)名称callback
- 回调函数,为每个联合源调用并传递该源的别名,该函数应返回一个表示条件的 SQL 片段。
callback
将为members
中的每个条目调用,因为每个源负责添加自己的条件。
自定义排序
可以通过.orderBy()
方法指定联合的顺序,该方法接受一个包含以下键的对象
attribute
- 用于排序的pgUnionAll
attributes
中的属性(字符串)名称。direction
-ASC
表示升序,DESC
表示降序。所有其他值的结果未定义,可能会在补丁版本中更改。
members
中的每个条目都将被排序,并且union all
也将再次排序以确保稳定的排序结果。
分页
可以使用 .setFirst($n)
和 .setOffset($n)
实现限制/偏移分页。pgUnionAll
还实现了相关接口,以支持 connection
步进行游标分页。
示例
const $vulnerabilities = pgUnionAll({
executor: firstPartyVulnerabilitiesResource.executor,
resourceByTypeName: {
FirstPartyVulnerability: firstPartyVulnerabilitiesResource,
ThirdPartyVulnerability: thirdPartyVulnerabilitiesResource,
},
attributes: {
cvss_score: {
codec: TYPES.float,
},
},
});
$vulnerabilities.orderBy({
attribute: "cvss_score",
direction: "DESC",
});
$vulnerabilities.where({
attribute: "cvss_score",
callback: (alias) =>
sql`${alias} > ${$vulnerabilities.placeholder(constant(6), TYPES.float)}`,
});
$vulnerabilities.setFirst(2);
$vulnerabilities.setOffset(2);
pgUnionAll SQL 解释
虽然 UNION ALL
使 PostgreSQL 的规划和执行变得复杂,但我们已经努力构建最有效的 SQL 查询来解决这个问题,同时仍然支持分页、自定义条件和自定义排序。这确实会导致比您从该模块中习惯的更复杂的 SQL 查询。实际上,查询看起来像这样
-- OUTER SELECT
select
__union__."0"::text,
__union__."1"::text
from (
-- MIDDLE SELECT
select
__first_table__."0",
__first_table__."1",
__first_table__."2",
"n"
from (
-- INNER SELECT
select
__first_table__."column1" as "0",
__first_table__."id" as "1",
'FirstTable' as "2",
row_number() over (partition by 1) as "n"
from first_table as __first_table__
where ...
order by __first_table__."column1"
limit ...
)
-- Any number of additional "middle selects" from different tables
-- via 'union all'
union all
select
...
order by
"0" desc,
"n" asc,
"2" asc
limit ...
offset ...
) __union__
我们将有与联合中的表数量一样多的“内部选择”和“中间选择”片段。
每个“内部选择”负责从每个单独的表中选择必要的公共字段,应用任何条件(到 where
子句中),应用排序(order by
子句),并应用限制(这将是主要限制加上偏移量,以便我们可以为 union all
的限制/偏移量获取足够的行)。
中间选择的存在仅仅是因为 union all
只允许在语句末尾使用一个 order by
,而且出于某种原因,我们认为我们比 Postgres 更了解如何优化此查询……(时间会证明一切。)因此,中间选择只是重新选择相关的属性。
然后,union all
语句再次按相关属性排序(包括类型名称和 row_number()
以确保稳定的顺序),并应用最终的限制/偏移量。
最后,“外部选择”选择我们需要的字段,并根据所涉及的编解码器对其进行转换。请注意,我们不能在之前进行转换,因为它们用于排序,将它们转换为文本(例如)可能会严重影响排序。