【问题标题】:Count all existing combinations of groupings of records计算记录分组的所有现有组合
【发布时间】:2019-04-12 22:19:49
【问题描述】:

我有这些数据库表

  • 问题:id、文本
  • 答案:id、text、question_id
  • answer_tags:id、answer_id、tag_id
  • 标签:id、文本

  • 问题有很多答案
  • answer通过answer_tags有很多标签,属于question
  • 标签通过 answer_tags 有很多答案
  • 答案有无限数量的标签

我想显示按计数排序的标签分组的所有组合

示例数据

Question 1, Answer 1, tag1, tag2, tag3, tag4
Question 2, Answer 2, tag2, tag3, tag4
Question 3, Answer 3, tag3, tag4
Question 4, Answer 4, tag4
Question 5, Answer 5, tag3, tag4, tag5
Question 1, Answer 6, <no tags>

如何使用 SQL 解决这个问题?

我不确定这是否可以通过 SQL 实现,但如果可以,我认为它需要 RECURSIVE 方法。

预期结果:

tag3, tag4 occur 4 times
tag2, tag3, tag4 occur 2 times
tag2, tag3 occur  2 times

我们只会返回分组大于 1 的结果。不会返回单个标签,它必须至少有 2 个标签加在一起才能计算在内。

【问题讨论】:

  • "tag2, tag3, tag4" 在什么意义上是一个pair?为什么 "tag3, tag4, tag5" 和 "tag3, tag5" 在您的预期结果中不是对?
  • 很抱歉。我更新了问题。目标是识别不止一次出现的所有分组。 tag2、tag3、tag4 是一个分组,它们出现在 Question1 和 Question2 中。 tag3, tag4, tag5 (Question5) 只出现一次,没有其他问题具有相同的三个标签。这也适用于仅针对 Question5 的 tag3 tag5。对不起,如果我不清楚。
  • 我不明白“问题有很多答案”和“一个问题只有一个答案”?
  • 不应该是tag3、tag4出现4次(1、2、4、5题)吗?
  • @RyanSparks 哇,你说得对。固定

标签: sql postgresql


【解决方案1】:

以@filiprem 的回答为基础,并使用回答here 中稍作修改的函数,您将得到:

--test data
create table questions (id int, text varchar(100));
create table answers (id int, text varchar(100), question_id int);
create table answer_tags (id int, answer_id int, tag_id int);
create table tags (id int, text varchar(100));

insert into questions values (1, 'question1'), (2, 'question2'), (3, 'question3'), (4, 'question4'), (5, 'question5');
insert into answers values (1, 'answer1', 1), (2, 'answer2', 2), (3, 'answer3', 3), (4, 'answer4', 4), (5, 'answer5', 5), (6, 'answer6', 1);
insert into tags values (1, 'tag1'), (2, 'tag2'), (3, 'tag3'), (4, 'tag4'), (5, 'tag5');
insert into answer_tags values 
(1,1,1), (2,1,2), (3,1,3), (4,1,4),
(5,2,2), (6,2,3), (7,2,4),
(8,3,3), (9,3,4),
(10,4,4),
(11,5,3), (12,5,4), (13,5,5);
--end test data

--function to get all possible combinations from an array with at least 2 elements
create or replace function get_combinations(source anyarray) returns setof anyarray as $$
 with recursive combinations(combination, indices) as (
   select source[i:i], array[i] from generate_subscripts(source, 1) i
   union all
   select c.combination || source[j], c.indices || j
   from   combinations c, generate_subscripts(source, 1) j
   where  j > all(c.indices) and
          array_length(c.combination, 1) <= 2
 )
 select combination from combinations
 where  array_length(combination, 1) >= 2
$$ language sql;

--expected results
SELECT tags, count(*) FROM (
    SELECT q.id, get_combinations(array_agg(DISTINCT t.text)) AS tags
    FROM questions q
    JOIN answers a ON a.question_id = q.id
    JOIN answer_tags at ON at.answer_id = a.id
    JOIN tags t ON t.id = at.tag_id
    GROUP BY q.id
) t1
GROUP BY tags
HAVING count(*)>1;

注意:这给出了 tag2,tag4 出现 2 次,在预期结果中错过了(来自问题 1 和 2)

【讨论】:

    【解决方案2】:

    您确实可以使用递归 CTE 来生成可能的组合。首先选择所有标签 ID 作为一个元素的数组。然后UNION ALL CTE 和标签 ID 的 JOIN,如果标签 ID 大于数组中的最大 ID,则将标签 ID 附加到数组中。

    到 CTE 加入一个聚合,获取每个答案的标签 ID 作为一个数组。在ON 子句中检查答案的数组是否包含来自CTE 的数组,其中数组包含运算符@&gt;

    从 CTE 中排除在 WHERE 子句中只有一个标签的组合,因为您对这些组合不感兴趣。

    现在GROUP BY 的标签组合排除了在HAVING 子句中出现少于两次的所有组合——您也对它们不感兴趣。如果您愿意,还可以将 ID“翻译”为 SELECT 列表中的标签名称。

    WITH RECURSIVE "cte"
    AS
    (
    SELECT ARRAY["t"."id"] "id"
           FROM "tags" "t"
    UNION ALL
    SELECT "c"."id" || "t"."id" "id"
           FROM "cte" "c"
                INNER JOIN "tags" "t"
                           ON "t"."id" > (SELECT max("un"."e")
                                                 FROM unnest("c"."id") "un" ("e"))
    )
    SELECT "c"."id" "id",
           (SELECT array_agg("t"."text")
                   FROM unnest("c"."id") "un" ("e")
                        INNER JOIN "tags" "t"
                                   ON "t"."id" = "un"."e") "text",
           count(*) "count"
           FROM "cte" "c"
                INNER JOIN (SELECT array_agg("at"."tag_id" ORDER BY "at"."tag_id") "id"
                                   FROM "answer_tags" "at"
                                   GROUP BY at.answer_id) "x"
                           ON "x"."id" @> "c"."id"
           WHERE array_length("c"."id", 1) > 1
           GROUP BY "c"."id"
           HAVING count(*) > 1;
    

    结果:

     id      | text             | count
    ---------+------------------+-------
     {2,3}   | {tag2,tag3}      |     2
     {3,4}   | {tag3,tag4}      |     4
     {2,4}   | {tag2,tag4}      |     2
     {2,3,4} | {tag2,tag3,tag4} |     2
    

    db<>fiddle

    【讨论】:

      【解决方案3】:

      试试这个:

      SELECT tags, count(*) FROM (
          SELECT q.id, array_agg(DISTINCT t.text) AS tags
          FROM questions q
          JOIN answers a ON a.question_id = q.id
          JOIN answer_tags at ON at.answer_id = a.id
          JOIN tags t ON t.id = at.tag_id
          GROUP BY q.id
      ) t1
      GROUP BY tags
      HAVING count(*)>1;
      

      【讨论】:

      • 感谢您的回复。你的回答有点用。当问题具有完全相同的标签但如果一个问题有两个标签,而第二个问题有三个标签则它不会返回任何结果。
      猜你喜欢
      • 2016-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多