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 &lt;= 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 行。我想这将是非常罕见的。主要的事情是rankdense_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 个域(排名 + 分组依据)的主要内容,如果未能解决你的问题,请参考以下文章

先前排名为零时如何分配排名(第 2 部分)

oracle字段按rownumber 排序不准确怎么解决

按地理分布确定“范围”

LeetCode之小孩分糖果

Postgres 窗口函数 - rank() 按 bigint 分区

如何按周排名前100名商品?