MYSQL select table 中按用户分组的计数的前 5 名
MYSQL select top 5 of a count from table that is grouped by user
我提前道歉,因为我可能没有正确描述我的问题。我正在尝试编写一个查询,该查询采用每个用户前 5 个最受欢迎的 chosen_user_items,并通过 group_concat
将每个用户的前几行连接成逗号分隔的字符串,然后按 user_id 分组.
例如,如果 user_id of 1 有五行表示 item_id of 1,两行表示 item_id of 2,三行表示 item_id of 3,和一行 4 5 和 6,那么结果将是 1, 3, 2, 4, 5.
这是我的示例 table 结构。
姓名: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.
等语言连接结果
提前致谢。
要解决这个问题,我认为您需要执行 4 个不同的步骤。
首先,您需要 choose/select/order 将显示的行。
这可以使用 row_number 和分区来完成(这不能在 MYSQL 中使用,但在本指南中,他们向您展示了 MYSQL 等效解决方案 https://blog.sqlauthority.com/2014/03/09/mysql-reset-row-number-for-each-group-partition-by-row-number/)
其次,您需要过滤 row_number 小于 5 的行,这将表现得像每个查询的 "limit 5"。
第三步,您需要将每个用户的这5条记录转换为5列。
这可以使用 pivot table 来完成。在这里您可以找到您必须执行的操作的示例:MySQL pivot table
最后一步:您需要做的就是连接 5 列中的每一列,您将获得每个用户所需的信息。
我希望这能说明问题
编辑:使用函数 GROUP_CONCAT 将允许您替换最后 2 个步骤
通过某种排名,只需 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
使用 R 的解决方案。
dbplyr
程序包将允许您 运行 此脚本直接针对数据库,而无需将数据提取到内存中。如果您不想使用 R,您可以 render
dbplyr
从您的 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
这是最佳解决方案,因为您在嵌套列表列 data
中保留了 item_id
及其计数。
我提前道歉,因为我可能没有正确描述我的问题。我正在尝试编写一个查询,该查询采用每个用户前 5 个最受欢迎的 chosen_user_items,并通过 group_concat
将每个用户的前几行连接成逗号分隔的字符串,然后按 user_id 分组.
例如,如果 user_id of 1 有五行表示 item_id of 1,两行表示 item_id of 2,三行表示 item_id of 3,和一行 4 5 和 6,那么结果将是 1, 3, 2, 4, 5.
这是我的示例 table 结构。
姓名: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.
等语言连接结果提前致谢。
要解决这个问题,我认为您需要执行 4 个不同的步骤。
首先,您需要 choose/select/order 将显示的行。 这可以使用 row_number 和分区来完成(这不能在 MYSQL 中使用,但在本指南中,他们向您展示了 MYSQL 等效解决方案 https://blog.sqlauthority.com/2014/03/09/mysql-reset-row-number-for-each-group-partition-by-row-number/)
其次,您需要过滤 row_number 小于 5 的行,这将表现得像每个查询的 "limit 5"。
第三步,您需要将每个用户的这5条记录转换为5列。 这可以使用 pivot table 来完成。在这里您可以找到您必须执行的操作的示例:MySQL pivot table
最后一步:您需要做的就是连接 5 列中的每一列,您将获得每个用户所需的信息。
我希望这能说明问题
编辑:使用函数 GROUP_CONCAT 将允许您替换最后 2 个步骤
通过某种排名,只需 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
使用 R 的解决方案。
dbplyr
程序包将允许您 运行 此脚本直接针对数据库,而无需将数据提取到内存中。如果您不想使用 R,您可以 render
dbplyr
从您的 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
这是最佳解决方案,因为您在嵌套列表列 data
中保留了 item_id
及其计数。