跳至主要内容

pgUnionAll

如果您需要从多个表中提取数据,并且无法使用联接来完成,那么 pgUnionAll 就是您的朋友。

此步骤类使用 SQL UNION ALL 结构从一个或多个不同的表中选择一定数量(包括零,对于 GraphQL 联合来说这是一个很好的数字 😉)的字段,这些表可能都是 GraphQL 中同一个联合或接口的一部分。

您可以指定一个共享字段列表,如果这样做了,那么您可以按这些共享字段排序,或者对它们应用条件,我们将把这些排序和条件传递到各个表选择(作为 UNION ALL 的一部分)以确保我们以最有效的方式获得结果。当然,您仍然可以使用正常的 GraphQL 类型化片段扩展机制选择未共享的字段。

危险

目前,这些共享字段必须在联合中的每个源上完全匹配名称和类型(我们不检查它,但如果您不遵守它,则在运行时可能会出现意外错误)。绝对有空间放宽这些要求 - 如果您需要,请与我们联系。

pgUnionAll 具有多态能力(但它不必是多态的!)并且支持限制/偏移和游标分页。

pgUnionAll 函数

pgUnionAll 函数接受一个参数 - PgUnionAllStepConfig。此配置对象包含以下条目

  • resourceByTypeName - (必需)从 GraphQL 类型名称到相关 PgResource 的映射,从中可以获取匹配的记录。
  • members - (可选)一个包含要组合在 union all 语句中的源和关系路径详细信息的列表;members 中的每个条目将成为另一个 union all'd select 语句。如果未指定,我们将根据 resourceByTypeName 为您生成成员。
    • 待办事项:记录成员的子键
  • attributes - (可选)一个对象,定义可用的公共属性(如果有)作为从属性名称到规范对象的映射,该规范对象包含用于该属性的 codec;这通常与 GraphQL 接口一起使用
  • mode - (可选)normal 表示正常模式(默认),或 aggregate 表示执行聚合(例如 count(*)
注意

每个source必须具有相同的executor,无论该源是显式定义还是通过遵循给定的关系隐式定义。

每个最终source(在任何关系路径末尾找到的源)必须具有一个主键(source.uniques中的一个条目,其中isPrimary === true),该主键可用于获取与联合中条目匹配的结果记录。

应用条件

可以通过.where()方法将条件应用于结果步骤,该方法接受一个包含以下键的对象

  • attribute - 要针对其应用条件的pgUnionAllattributes中的属性(字符串)名称
  • callback - 回调函数,为每个联合源调用并传递该源的别名,该函数应返回一个表示条件的 SQL 片段。
注意

callback将为members中的每个条目调用,因为每个源负责添加自己的条件。

自定义排序

可以通过.orderBy()方法指定联合的顺序,该方法接受一个包含以下键的对象

  • attribute - 用于排序的pgUnionAllattributes中的属性(字符串)名称。
  • 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() 以确保稳定的顺序),并应用最终的限制/偏移量。

最后,“外部选择”选择我们需要的字段,并根据所涉及的编解码器对其进行转换。请注意,我们不能在之前进行转换,因为它们用于排序,将它们转换为文本(例如)可能会严重影响排序。