【问题标题】:Query to count the frequence of many-to-many associations查询计算多对多关联的频率
【发布时间】:2016-04-06 00:28:19
【问题描述】:

我在 postgresql 中有两个具有多对多关联的表。第一个表包含活动,可能计数零个或多个原因:

CREATE TABLE activity (
   id integer NOT NULL,
   -- other fields removed for readability
);

CREATE TABLE reason (
   id varchar(1) NOT NULL,
   -- other fields here
);

为了执行关联,在这两个表之间存在一个连接表

CREATE TABLE activity_reason (
   activity_id integer NOT NULL, -- refers to activity.id
   reason_id varchar(1) NOT NULL, -- refers to reason.id
   CONSTRAINT activity_reason_activity FOREIGN KEY (activity_id) REFERENCES activity (id),
  CONSTRAINT activity_reason_reason FOREIGN KEY (reason_id) REFERENCES reason (id)
);

我想计算活动和原因之间可能存在的关联。假设我在表activity_reason 中有这些记录:

+--------------+------------+
| activity_id  |  reason_id |
+--------------+------------+
|           1  |          A |
|           1  |          B |
|           2  |          A |
|           2  |          B |
|           3  |          A |
|           4  |          C |
|           4  |          D |
|           4  |          E |
+--------------+------------+

我应该有类似的东西:

+-------+---+------+-------+
| count |   |      |       |
+-------+---+------+-------+
|     2 | A | B    | NULL  |
|     1 | A | NULL | NULL  |
|     1 | C | D    | E     |
+-------+---+------+-------+

或者,最终,类似:

+-------+-------+
| count |       |
+-------+-------+
|     2 | A,B   |
|     1 | A     |
|     1 | C,D,E |
+-------+-------+

我找不到执行此操作的 SQL 查询。

【问题讨论】:

  • 你在数什么?我很困惑,因为您的示例结果中包含“C,D,E”,但您的示例数据在任何地方都没有“D”。您是否尝试获取与 activity_id 相关联的所有 reason_id?
  • I remove the constraint for readability。这是一种误解。 从不移除约束,这些约束是清晰的必要条件。假设在(activity_id, reason_id) 上有一个PK 的典型实现。
  • @rdubya :我修复了丢失的字母。
  • @erwin-brandstetter :我添加了约束。

标签: sql arrays postgresql many-to-many aggregate


【解决方案1】:

我认为你可以使用这个查询得到你想要的:

SELECT count(*) as count, reasons
FROM (
  SELECT activity_id, array_agg(reason_id) AS reasons
  FROM (
    SELECT A.activity_id, AR.reason_id
    FROM activity A
    LEFT JOIN activity_reason AR ON AR.activity_id = A.activity_id
    ORDER BY activity_id, reason_id
  ) AS ordered_reasons
  GROUP BY activity_id
) reason_arrays
GROUP BY reasons

首先,您将活动的所有原因汇总到每个活动的数组中。您必须先对关联进行排序,否则 ['a','b'] 和 ['b','a'] 将被视为不同的集合并具有单独的计数。您还需要包括加入或任何没有任何原因的活动都不会出现在结果集中。我不确定这是否可取,如果您想要没有理由不包括在内的活动,我可以将其取消。然后计算具有相同原因的活动的数量。

这里有一个sqlfiddle 来演示

正如 Gordon Linoff 所提到的,您也可以使用字符串而不是数组。我不确定哪个对性能更好。

【讨论】:

  • 你不需要使用左连接。这应该做同样的事情:SELECT count(*) as count, reasons FROM ( SELECT activity_id, array_agg(reason_id) AS reasons FROM activity_reason GROUP BY activity_id ) reason_arrays GROUP BY reasons
  • @ZhiliangTakutoXing 正如我在答案中提到的,如果您还想计算没有相关原因的活动,则需要左连接。如果这些不应该被计算在内,那么就不需要左连接。另外,正如我在回答中所说,您需要对原因进行排序,否则数组将具有不同的顺序并且无法正确分组。
【解决方案2】:

我们需要比较排序的个原因列表来识别相等的集合。

SELECT count(*) AS ct, reason_list
FROM  (
   SELECT array_agg(reason_id) AS reason_list
   FROM  (SELECT * FROM activity_reason ORDER BY activity_id, reason_id) ar1
   GROUP  BY activity_id
   ) ar2
GROUP  BY reason_list
ORDER  BY ct DESC, reason_list;

最里面的子查询中的ORDER BY reason_id 也可以,但添加activity_id 通常更快。

而且我们根本不需要最里面的子查询。这也有效:

SELECT count(*) AS ct, reason_list
FROM  (
   SELECT array_agg(reason_id ORDER BY reason_id) AS reason_list
   FROM   activity_reason
   GROUP  BY activity_id
   ) ar2
GROUP  BY reason_list
ORDER  BY ct DESC, reason_list;

但处理全部或大部分表格的速度通常较慢。 Quoting the manual:

另外,从排序的子查询中提供输入值通常也可以。

我们可以使用string_agg() 而不是array_agg(),这适用于varchar(1) 的示例(使用数据类型"char" 可能更有效,顺便说一句)。但是,对于更长的字符串,它可能会失败。聚合值可能不明确。


如果reason_id 将是一个integer(就像通常那样),还有另一个更快的解决方案,来自附加模块sort()intarray

SELECT count(*) AS ct, reason_list
FROM  (
   SELECT sort(array_agg(reason_id)) AS reason_list
   FROM   activity_reason2
   GROUP  BY activity_id
   ) ar2
GROUP  BY reason_list
ORDER  BY ct DESC, reason_list;

相关,有更多解释:

【讨论】:

    【解决方案3】:

    您可以使用string_agg()

    select reasons, count(*)
    from (select activity_id, string_agg(reason_id, ',' order by reason_id) as reasons
          from activity_reason
          group by activity_id
         ) a
    group by reasons
    order by count(*) desc;
    

    【讨论】:

    • 未排序 聚合进行分组将无法识别相等的集合。
    • 我遇到了同样的问题。 B,A 和 A,B 被视为两个不同的对。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-04
    • 1970-01-01
    • 1970-01-01
    • 2020-10-10
    • 1970-01-01
    相关资源
    最近更新 更多