Postgres 按年龄组确定前 10 个域(排名 + 分组依据)
Posted
技术标签:
【中文标题】Postgres 按年龄组确定前 10 个域(排名 + 分组依据)【英文标题】:Postgres determine top 10 domains by age group (rank + group by) 【发布时间】:2021-12-12 09:25:47 【问题描述】:给定user_table
和电子邮件地址,我们需要按年龄组划分的“前 10 名”域列表。
所以对于每个组,我应该获得前 10 名的排名。 (即 50 行)。
到目前为止我所拥有的(我正在使用 Postgres)。 这似乎接近了,但我认为并列排名正在被吃掉。我没有返回 50 行。我回到12,基本上似乎排名1-10,有2个平局。都是同一个年龄段。如果我将其增加到 r
with users as (
select a.*,
extract(year from age(dob)) age,
substr(email, position('@' in email)+1, 1000) domain
from user_table a
),
useragegroup as (
select a.*,
case when age between 0 and 18 then '0-18'
when age between 19 and 29 then '19-29'
when age between 30 and 49 then '30-49'
when age between 50 and 65 then '50-65'
else '66-up'
end agegroup
from users a
),
rank as (
select agegroup, domain,
dense_rank() over (order by count(*) desc) r
from useragegroup a
group by agegroup, domain
)
select a.*
from rank a
where r<=10;
要生成一些测试日期,我有: (每组更改日期 10 年)
insert into user_table (
first, last, email, dob
)
select
left(md5(i::text), 3),
left(md5(random()::text), 3),
'user_' || i || '@' || (
CASE (RANDOM() * 14)::INT
WHEN 0 THEN 'gmail'
WHEN 1 THEN 'hotmail'
WHEN 2 THEN 'apple'
WHEN 3 THEN 'icloud'
WHEN 4 THEN 'aol'
WHEN 5 THEN 'usa'
WHEN 6 THEN 'govt'
WHEN 7 THEN '***'
WHEN 8 THEN 'random'
WHEN 9 THEN 'domain'
WHEN 10 THEN 'subby'
WHEN 11 THEN 'youtube'
WHEN 12 THEN 'google'
WHEN 13 THEN 'triple'
WHEN 14 THEN 'pixar'
END
) || '.com' AS email,
'2005-01-01' as date
from generate_series(1, 500) s(i);
【问题讨论】:
你的问题是什么? 我没有返回 50 行,澄清。 【参考方案1】:我认为因为您使用dense_rank
,所以您有重复的排名并且总记录一直在增加,如下表所示:
总记录:13 行
| agegroup | domain | r |
| -------- | ------------------ | -- |
| 66-up | youtube.com | 1 |
| 66-up | triple.com | 2 | <-- duplicate
| 66-up | google.com | 2 | <-- duplicate
| 66-up | random.com | 3 |
| 66-up | usa.com | 4 |
| 66-up | aol.com | 5 | <-- duplicate
| 66-up | subby.com | 5 | <-- duplicate
| 66-up | hotmail.com | 5 | <-- duplicate
| 66-up | ***.com | 6 |
| 66-up | apple.com | 7 |
| 66-up | domain.com | 8 |
| 66-up | icloud.com | 9 |
| 66-up | govt.com | 10 |
您的查询有两个问题:
你应该使用row_number
,因为dense_rank
添加了重复的排名,当你使用r <= 10
时,如果记录中存在重复的r,则每个组的总记录有所增加
windows函数中的第二个问题,你必须为每个组使用partition by agegroup
,因为需要为每个组创建排名
with users as (
select a.*,
extract(year from age(dob)) as age,
substr(email, position('@' in email)+1, 1000) as domain
from user_table a
),
useragegroup as (
select a.*,
case when age between 0 and 18 then '0-18'
when age between 19 and 29 then '19-29'
when age between 30 and 49 then '30-49'
when age between 50 and 65 then '50-65'
else '66-up'
end agegroup
from users a
),
rank as (
select agegroup, domain,
row_number() over (partition by agegroup order by count(*) desc) r
from useragegroup a
group by agegroup, domain
)
select a.*
from rank a
where r <= 10;
【讨论】:
【参考方案2】:您的查询可能没问题。看起来有问题,但没有什么特别突出的。但是,您确实有问题。您期望在结果中获得 50 行。我想这将是非常罕见的。主要的事情是rank
和dense_rank
都不会生成唯一值,如果被排名的值在多行中是相同的,那么每一行都会获得相同的排名。 rank 的差异将跳过值,而 dense_rank 不会。 IE。如果前 2 行的值相同,而第三行的值不同,则以下情况成立:
+------------+-------------+------+------------+
| Row_number | Count_Value | Rank | Dense_Rank |
+------------+-------------+------+------------+
| 1 | 12 | 1 | 1 |
| 2 | 12 | 1 | 1 |
| 3 | 14 | 3 | 2 |
+------------+-------------+------+------------+
查看演示 ""您的数据 here。它包括 rank
(rnk) 和 dense_rank
(drnk) 的列。向下扫描您感兴趣的年龄组的 rnk 和/或 drnk,然后转到 row_num 。那是为那个 age_group 返回的行数。请注意,某些 age_group 的 drnk 列没有达到 10;那些将返回所有 15 个。假设随机域选择为每个域生成一行。虽然很可能无法保证这一点。
顺便说一句:我的问题。我为age_groups创建了一个表,它也在demo中。
select domain, ag_name, dom_cnt, rnk, drnk
from ( -- rank each group by iten count
select domain, ag_name, dom_cnt
, rank() over (partition by ag_name order by dom_cnt desc) rnk
, dense_rank() over (partition by ag_name order by dom_cnt desc) drnk
, row_number() over (partition by ag_name order by dom_cnt desc) row_num
from ( -- count #items for each edomain, ag_name
select domain, ag_name ,count(*) dom_cnt
from (-- extract email domain and group name
select substr(email, position('@' in email)+1) as domain, ag.ag_name
from age_groups ag
join user_table ut
on (extract(year from age(ut.dob)))::int4 <@ ag.ag_range
) agdom
group by ag_name, domain
) dom_cnt
) dom_rank
-- where rnk <= 10
;
【讨论】:
以上是关于Postgres 按年龄组确定前 10 个域(排名 + 分组依据)的主要内容,如果未能解决你的问题,请参考以下文章