【问题标题】:MYSQL select top 5 of a count from table that is grouped by userMYSQL 从按用户分组的表中选择前 5 个计数
【发布时间】:2018-02-14 16:55:06
【问题描述】:

我提前道歉,因为我可能没有正确描述我的问题。我正在尝试编写一个查询,该查询获取每个用户前 5 个最受欢迎的 selected_user_items,并通过 group_concat 将每个用户的前几行连接成逗号分隔的字符串,然后按 user_id 进行分组。

例如,如果 user_id 为 1 的 item_id 为 1 有 5 行,item_id 为 2 有 2 行,item_id 为 3 有 3 行,4 5 和 6 有 1 行,那么结果将是 1, 3 , 2, 4, 5。

这是我的示例表结构。

姓名:chosen_user_items

id | user_id | item_id
------------------------
1  | 1       | 1
2  | 1       | 4
3  | 1       | 19
4  | 1       | 10
5  | 1       | 13
.  | 1       | 1
.  | 1       | 11
.  | 1       | 18
.  | 1       | 212
.  | 1       | 654
.  | 2       | 1
.  | 2       | 28
.  | 2       | 568
.  | 2       | 112
.  | 2       | 354
.  | 3       | 4
.  | 3       | 4
.  | 3       | 19
.  | 3       | 212
.  | 3       | 654
.  | 3       | 4
.  | 3       | 4
.  | 3       | 253
.  | 3       | 187
.  | 3       | 212

这是我想要的输出示例:

user_id | group_concat_results
------------------------------
1       | 1, 4, 19, 13, 212
2       | 1, 28, 568, 212, 354
3       | 4, 212, 19, 654, 253

这是我目前的查询

SELECT `chosen_user_items`.`item_id`, COUNT(`chosen_user_items`.`item_id`) AS 'item_count' 
FROM `chosen_user_items` 
WHERE `chosen_user_items`.`user_id` = 1
GROUP BY `chosen_user_items`.`item_id` 
ORDER BY `item_count` DESC 
LIMIT 5

虽然这对单个用户很有用,但我希望能够为所有用户只运行一次此查询(以避免执行数百或数千个数据库查询),并且必须手动连接一种语言的结果比如PHP。

提前致谢。

【问题讨论】:

    标签: mysql group-by limit group-concat


    【解决方案1】:

    通过某种排名,可以通过 1 个查询来完成。

    select user_id, group_concat(item_id) from 
    (
    select 
      user_id
      ,item_id
      ,@item_rank := if(@current_item = user_id, @item_rank+1,1) as item_rank 
      ,@current_item:=user_id
      from
          (      
          select 
            user_id
            ,item_id
            ,count(*) aantal
          from chosen_user_items
          group by user_id,item_id
          order by user_id,count(*) desc
          ) a )b
          where item_rank <6
          group by user_id
    

    这是一个sqlfiddle 来测试它。

    我只是对 group_concat 的顺序有疑问,它没有相应地排序。

    尝试以下查询以查看 group_concat 之前的结果,也许您可​​以更好地连接它。

    select 
      user_id
      ,item_id
      ,@item_rank := if(@current_item = user_id, @item_rank+1,1) as item_rank 
      ,@current_item:=user_id
      from
          (      
          select 
            user_id
            ,item_id
            ,count(*) aantal
          from chosen_user_items
          group by user_id,item_id
          order by user_id,count(*) desc
          ) a 
    

    此查询根据您问题中的数据正确排序 concat:

    select user_id, group_concat(item_id) from 
    (
    select 
      user_id
      ,item_id
      ,@item_rank := if(@current_item = user_id, @item_rank+1,1) as item_rank 
      ,@current_item:=user_id
      from
          (      
          select 
            user_id
            ,item_id
            ,count(*) aantal
          from chosen_user_items
          group by user_id,item_id
          order by user_id,count(*) desc
          ) a )b
          where item_rank <6
          group by user_id
          order by user_id,item_rank asc
    

    【讨论】:

    • 谢谢你,我会看看并回复你。
    • 抱歉耽搁了,这很完美!我知道我至少需要两个子查询。
    【解决方案2】:

    使用 R 的解决方案。

    dbplyr 包将允许您直接针对数据库运行此脚本,而无需将数据拉入内存。如果您不想使用 R,可以renderdbplyr 从您的 R 语句生成的 SQL 查询。

    library(tidyverse)
    library(stringr)
    
    # --- Set Up ---
    dat <- tribble(
      ~user_id, ~item_id,
      1, 1,
      1, 4,
      1, 19,
      1, 10,
      1, 13,
      1, 1,
      1, 11,
      1, 18,
      1, 212,
      1, 654,
      2, 1,
      2, 28,
      2, 568,
      2, 112,
      2, 354,
      3, 4,
      3, 4,
      3, 19,
      3, 212,
      3, 654,
      3, 4,
      3, 4,
      3, 253,
      3, 187,
      3, 212
    )
    
    # --- Prep --- 
    pre <- dat %>% 
      group_by(user_id) %>% 
      arrange(user_id, item_id) %>% 
      add_count(item_id) %>% 
      rename(
        n_items = n
      ) %>% 
      distinct(user_id, item_id, .keep_all = TRUE) %>% 
      top_n(5, n_items) %>% 
      slice(1:5) %>% 
      arrange(user_id, desc(n_items)) 
    
    # --- Solve ---
    # Hacky
    solution_one <- pre %>% 
      mutate(collapsed = str_c(item_id, collapse = ", ")) %>% 
      slice(1) %>% 
      select(user_id, collapsed)
    
    # Ideal
    solution_two <- pre %>%
      nest() %>% 
      mutate(
        collapsed = data %>% 
          map("item_id") %>% 
          map_chr(str_c, collapse = ", "))
    

    输出:

    solution_two
    #> # A tibble: 3 x 3
    #>   user_id             data            collapsed
    #>     <dbl>           <list>                <chr>
    #> 1       1 <tibble [5 x 2]>     1, 4, 10, 11, 13
    #> 2       2 <tibble [5 x 2]> 1, 28, 112, 354, 568
    #> 3       3 <tibble [5 x 2]> 4, 212, 19, 187, 253
    

    这是最好的解决方案,因为您将item_id 及其计数保留在嵌套列表列data 中。

    【讨论】:

    • 感谢您提供此解决方案!我不确定如何将其转换为 mysql。
    【解决方案3】:

    要解决此问题,我认为您需要执行 4 个不同的步骤。

    首先,您需要选择/选择/排序要显示的行。 这可以使用 row_number 和 partition by 来完成(这不能在 MYSQL 中使用,但在本指南中,他们向您展示了 MYSQL 等效解决方案https://blog.sqlauthority.com/2014/03/09/mysql-reset-row-number-for-each-group-partition-by-row-number/

    其次,您需要过滤 row_number 小于 5 的行,这将类似于每个查询的“限制 5”。

    第三步,您需要将每个用户的这 5 条记录转换为 5 列。 这可以使用数据透视表来完成。在这里你可以找到一个你必须做的例子:MySQL pivot table

    最后一步:您只需连接 5 列中的每一列,您将获得每个用户所需的信息。

    我希望这可以澄清

    编辑:使用函数 GROUP_CONCAT 将允许您替换最后两个步骤

    【讨论】:

    • 感谢您的链接。我会看看它。您是否知道 GROUP_CONCAT 是否可以使用该方法实现?
    • 此方法允许您为每个 user_id 选择要连接的元素以及它们将显示的顺序,但它不会连接它们,只需按照您希望的方式对它们进行编号。也许我问错了问题,但这不是目标吗?
    • 对于 concat,我认为您需要完成 2 个步骤。首先,使行表现得像列,这可以通过枢轴来实现。其次,您需要连接结果列。
    • 不错,我也可以做个临时查表
    • 编辑后的答案,让您更清楚地了解您所期望的一切。
    猜你喜欢
    • 2015-07-17
    • 2013-01-12
    • 2015-05-15
    • 1970-01-01
    • 2018-01-11
    • 1970-01-01
    • 2014-05-11
    • 2018-05-17
    • 1970-01-01
    相关资源
    最近更新 更多