PostgreSQL / TypeORM:在数组列中搜索数组 - return 仅最高数组的交集
PostgreSQL / TypeORM: search array in array column - return only the highest arrays' intersection
假设我们在一个图中有 2 条边,每条边上都有许多观察到的事件,每个事件都有一个或多个关联的标签:
假设第一个边缘有 8 个带有这些标签的事件:ABC ABC AC BC A A B。
第二个边缘有 3 个事件:BC、BC、C。
我们希望用户能够搜索
- 每条边上发生了多少事件
- 通过一组给定的标签,这些标签既不互斥,也不具有严格的层次关系。
我们用 2 个预聚合的 tables 表示这个模式:
边 table:
+----+
| id |
+----+
| 1 |
| 2 |
+----+
EdgeStats table(包含通过 tag_id 与边 table 的关系):
+------+---------+-----------+---------------+
| id | edge_id | tags | metric_amount |
+------+---------+-----------+---------------+
| 1 | 1 | [A, B, C] | 7 |
| 2 | 1 | [A, B] | 7 |
| 3 | 1 | [B, C] | 5 |
| 4 | 1 | [A, C] | 6 |
| 5 | 1 | [A] | 5 |
| 6 | 1 | [B] | 4 |
| 7 | 1 | [C] | 4 |
| 8 | 1 | null | 7 | //null represents aggregated stats for given edge, not important here.
| 9 | 2 | [B, C] | 3 |
| 10 | 2 | [B] | 2 |
| 11 | 2 | [C] | 3 |
| 12 | 2 | null | 3 |
+------+---------+-----------+---------------+
请注意,例如,当 table 具有标签 [A, B] 时,它表示与此标签之一相关联的事件数量。所以 A OR B,或两者兼而有之。
因为用户可以通过这些标签的任意组合进行过滤,所以 DataTeam 填充了 EdgeStats table,每个给定边观察到标签的所有排列(边完全相互独立,但是我正在寻找查询的方法一个查询的所有边缘)。
我需要通过用户选择的标签过滤此 table,比方说 [A、C、D]。问题是我们在数据中没有标签 D。预期的 return 是:
+------+---------+-----------+---------------+
| id | edge_id | tags | metric_amount |
+------+---------+-----------+---------------+
| 4 | 1 | [A, C] | 6 |
| 11 | 2 | [C] | 3 |
+------+---------+-----------+---------------+
即对于每条边,用户搜索的内容和我们在标签列中的内容之间的最高匹配子集。 ID 为 5 和 7 的行未被 return 编辑,因为有关它们的信息已包含在第 4 行中。
为什么 return 使用 [A, C] 进行 [A, C, D] 搜索?因为边 1 上没有带标签 D 的数据,所以 [A, C] 的度量值等于 [A, C, D] 的度量值。
如何向 return 写入查询?
如果你能回答上面的问题,你可以忽略下面的内容:
如果我需要按 [A]、[B] 或 [A, B] 进行过滤,问题就很简单了 - 我可以只搜索精确的数组匹配:
query.where("edge_stats.tags = :filter",
{
filter: [A, B],
}
)
但是在 EdgeStats table 我没有所有用户可以搜索的标签组合(因为它太多了),所以我需要找到更聪明的解决方案。
这里列出了一些可能的解决方案,但都不完美:
- 尝试对用户搜索词的所有子集进行完全匹配 - 因此,如果用户按标签 [A、C、D] 进行搜索,首先尝试查询 [A、C、D],如果没有完全匹配,则尝试 [ C, D], [A, D], [A, C] 瞧,我们匹配成功了!
- 使用@> operator:
.where(
"edge_stats.tags <@ :tags",
{
tags:[A, C, D],
}
)
这将 return 所有包含 A、C 或 D 的行,即第 1、2、3、4、5、7、11、13 行。然后就可以过滤掉代码中除最高子集匹配之外的所有匹配项。但是使用这种方法,我们不能使用 SUM 和类似的函数,而且 return 行数太多不是好的做法。
- 基于 2) 并受 this answer 启发的方法:
.where(
"edge_stats.tags <@ :tags",
{
tags: [A, C, D],
}
)
.addOrderBy("edge.id")
.addOrderBy("CARDINALITY(edge_stats.tags)", "DESC")
.distinctOn(["edge.id"]);
它所做的是针对每条边,找到所有包含 A、C 或 D 的标签,并获得最高匹配(数组最长时高)(感谢按基数排序并仅选择一个)。
所以 returned 行确实是 4、11。
这种方法很棒,但是当我将其用作更大查询的一个过滤部分时,我需要添加一堆 groupBy 语句,实际上它增加了比我想要的更多的复杂性。
我想知道是否有更简单的方法,即在查询参数中获取 table 列中数组与数组的最高匹配?
您的方法 #3 应该没问题,尤其是当您在 CARDINALITY(edge_stats.tags)
上有索引时。然而,
DataTeam populated EdgeStats table with all permutations of tags observed per given edge
如果您使用 pre-aggregation 方法而不是 运行 您对原始数据的查询,我建议还记录“每个给定边缘观察到的标签",在边缘 table.
这样你就可以
SELECT s.edge_id, s.tags, s.metric_amount
FROM "EdgeStats" s
JOIN "Edges" e ON s.edge_id = e.id
WHERE s.tags = array_intersect(e.observed_tags, )
使用 here 中的 array_intersect
函数。
假设我们在一个图中有 2 条边,每条边上都有许多观察到的事件,每个事件都有一个或多个关联的标签:
假设第一个边缘有 8 个带有这些标签的事件:ABC ABC AC BC A A B。
第二个边缘有 3 个事件:BC、BC、C。
我们希望用户能够搜索
- 每条边上发生了多少事件
- 通过一组给定的标签,这些标签既不互斥,也不具有严格的层次关系。
我们用 2 个预聚合的 tables 表示这个模式:
边 table:
+----+
| id |
+----+
| 1 |
| 2 |
+----+
EdgeStats table(包含通过 tag_id 与边 table 的关系):
+------+---------+-----------+---------------+
| id | edge_id | tags | metric_amount |
+------+---------+-----------+---------------+
| 1 | 1 | [A, B, C] | 7 |
| 2 | 1 | [A, B] | 7 |
| 3 | 1 | [B, C] | 5 |
| 4 | 1 | [A, C] | 6 |
| 5 | 1 | [A] | 5 |
| 6 | 1 | [B] | 4 |
| 7 | 1 | [C] | 4 |
| 8 | 1 | null | 7 | //null represents aggregated stats for given edge, not important here.
| 9 | 2 | [B, C] | 3 |
| 10 | 2 | [B] | 2 |
| 11 | 2 | [C] | 3 |
| 12 | 2 | null | 3 |
+------+---------+-----------+---------------+
请注意,例如,当 table 具有标签 [A, B] 时,它表示与此标签之一相关联的事件数量。所以 A OR B,或两者兼而有之。
因为用户可以通过这些标签的任意组合进行过滤,所以 DataTeam 填充了 EdgeStats table,每个给定边观察到标签的所有排列(边完全相互独立,但是我正在寻找查询的方法一个查询的所有边缘)。
我需要通过用户选择的标签过滤此 table,比方说 [A、C、D]。问题是我们在数据中没有标签 D。预期的 return 是:
+------+---------+-----------+---------------+
| id | edge_id | tags | metric_amount |
+------+---------+-----------+---------------+
| 4 | 1 | [A, C] | 6 |
| 11 | 2 | [C] | 3 |
+------+---------+-----------+---------------+
即对于每条边,用户搜索的内容和我们在标签列中的内容之间的最高匹配子集。 ID 为 5 和 7 的行未被 return 编辑,因为有关它们的信息已包含在第 4 行中。
为什么 return 使用 [A, C] 进行 [A, C, D] 搜索?因为边 1 上没有带标签 D 的数据,所以 [A, C] 的度量值等于 [A, C, D] 的度量值。
如何向 return 写入查询?
如果你能回答上面的问题,你可以忽略下面的内容:
如果我需要按 [A]、[B] 或 [A, B] 进行过滤,问题就很简单了 - 我可以只搜索精确的数组匹配:
query.where("edge_stats.tags = :filter",
{
filter: [A, B],
}
)
但是在 EdgeStats table 我没有所有用户可以搜索的标签组合(因为它太多了),所以我需要找到更聪明的解决方案。
这里列出了一些可能的解决方案,但都不完美:
- 尝试对用户搜索词的所有子集进行完全匹配 - 因此,如果用户按标签 [A、C、D] 进行搜索,首先尝试查询 [A、C、D],如果没有完全匹配,则尝试 [ C, D], [A, D], [A, C] 瞧,我们匹配成功了!
- 使用@> operator:
.where(
"edge_stats.tags <@ :tags",
{
tags:[A, C, D],
}
)
这将 return 所有包含 A、C 或 D 的行,即第 1、2、3、4、5、7、11、13 行。然后就可以过滤掉代码中除最高子集匹配之外的所有匹配项。但是使用这种方法,我们不能使用 SUM 和类似的函数,而且 return 行数太多不是好的做法。
- 基于 2) 并受 this answer 启发的方法:
.where(
"edge_stats.tags <@ :tags",
{
tags: [A, C, D],
}
)
.addOrderBy("edge.id")
.addOrderBy("CARDINALITY(edge_stats.tags)", "DESC")
.distinctOn(["edge.id"]);
它所做的是针对每条边,找到所有包含 A、C 或 D 的标签,并获得最高匹配(数组最长时高)(感谢按基数排序并仅选择一个)。
所以 returned 行确实是 4、11。
这种方法很棒,但是当我将其用作更大查询的一个过滤部分时,我需要添加一堆 groupBy 语句,实际上它增加了比我想要的更多的复杂性。
我想知道是否有更简单的方法,即在查询参数中获取 table 列中数组与数组的最高匹配?
您的方法 #3 应该没问题,尤其是当您在 CARDINALITY(edge_stats.tags)
上有索引时。然而,
DataTeam populated EdgeStats table with all permutations of tags observed per given edge
如果您使用 pre-aggregation 方法而不是 运行 您对原始数据的查询,我建议还记录“每个给定边缘观察到的标签",在边缘 table.
这样你就可以
SELECT s.edge_id, s.tags, s.metric_amount
FROM "EdgeStats" s
JOIN "Edges" e ON s.edge_id = e.id
WHERE s.tags = array_intersect(e.observed_tags, )
使用 here 中的 array_intersect
函数。