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 我没有所有用户可以搜索的标签组合(因为它太多了),所以我需要找到更聪明的解决方案。

这里列出了一些可能的解决方案,但都不完美:

  1. 尝试对用户搜索词的所有子集进行完全匹配 - 因此,如果用户按标签 [A、C、D] 进行搜索,首先尝试查询 [A、C、D],如果没有完全匹配,则尝试 [ C, D], [A, D], [A, C] 瞧,我们匹配成功了!
  2. 使用@> operator:
  .where(
        "edge_stats.tags <@ :tags",
        {
          tags:[A, C, D],
        }
      )

这将 return 所有包含 A、C 或 D 的行,即第 1、2、3、4、5、7、11、13 行。然后就可以过滤掉代码中除最高子集匹配之外的所有匹配项。但是使用这种方法,我们不能使用 SUM 和类似的函数,而且 return 行数太多不是好的做法。

  1. 基于 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 函数。