查询所有过去和未来的圆形生日

时间:2018-01-12 23:10:44

标签: sql postgresql date postgresql-9.5

我在表格中获得了用户的生日,并希望显示下一个n年的圆形生日列表(从任意日期x开始),如下所示:

 +----------------------------------------------------------------------------------------+
 | Name   | id | birthdate  | current_age | birthday   | year | month | day | age_at_date |
 +----------------------------------------------------------------------------------------+
 | User 1 | 1  | 1958-01-23 | 59          | 2013-01-23 | 2013 | 1     | 23  | 55          | 
 | User 2 | 2  | 1988-01-29 | 29          | 2013-01-29 | 2013 | 1     | 29  | 25          | 
 | User 3 | 3  | 1963-02-12 | 54          | 2013-02-12 | 2013 | 2     | 12  | 50          | 
 | User 1 | 1  | 1958-01-23 | 59          | 2018-01-23 | 2018 | 1     | 23  | 60          | 
 | User 2 | 2  | 1988-01-29 | 29          | 2018-01-29 | 2018 | 1     | 29  | 30          | 
 | User 3 | 3  | 1963-02-12 | 54          | 2018-02-12 | 2018 | 2     | 12  | 55          | 
 | User 1 | 1  | 1958-01-23 | 59          | 2023-01-23 | 2023 | 1     | 23  | 65          | 
 | User 2 | 2  | 1988-01-29 | 29          | 2023-01-29 | 2023 | 1     | 29  | 35          | 
 | User 3 | 3  | 1963-02-12 | 54          | 2023-02-12 | 2023 | 2     | 12  | 60          | 
 +----------------------------------------------------------------------------------------+

正如你所看到的,我想要“环绕”,不仅要展示下一个即将到来的圆形生日,这很容易,也是历史和未来的数据。

我当前方法的核心思想如下:我通过generate_series生成从1900年到2100年的所有日期,并通过将生日的月和月与用户匹配来加入它们。基于此,我计算了那个日期的年龄,最后只选择生日,这是圆的(可以除以5)并且屈服于非负年龄。

WITH
  test_users(id, name, birthdate) AS (
    VALUES
      (1, 'User 1', '23-01-1958' :: DATE),
      (2, 'User 2', '29-01-1988'),
      (3, 'User 3', '12-02-1963')
  ),
  dates AS (
    SELECT
      s                     AS date,
      date_part('year', s)  AS year,
      date_part('month', s) AS month,
      date_part('day', s)   AS day
    FROM generate_series('01-01-1900' :: TIMESTAMP, '01-01-2100' :: TIMESTAMP, '1 days' :: INTERVAL) AS s
  ),
  birthday_data AS (
    SELECT
      id                                                                                AS member_id,
      test_users.birthdate                                                              AS birthdate,
      (date_part('year', age((test_users.birthdate)))) :: INT                           AS current_age,
      date :: DATE                                                                      AS birthday,
      date_part('year', date)                                                           AS year,
      date_part('month', date)                                                          AS month,
      date_part('day', date)                                                            AS day,
      ROUND(extract(EPOCH FROM (dates.date - birthdate)) / (60 * 60 * 24 * 365)) :: INT AS age_at_date
    FROM test_users, dates
    WHERE
      dates.day = date_part('day', birthdate) AND
      dates.month = date_part('month', birthdate) AND
      dates.year >= date_part('year', birthdate)
  )

SELECT
  test_users.name,
  bd.*
FROM test_users
LEFT JOIN birthday_data bd ON bd.member_id = test_users.id
WHERE
  bd.age_at_date % 5 = 0 AND
  bd.birthday BETWEEN NOW() - INTERVAL '5' YEAR AND NOW() + INTERVAL '10' YEAR
ORDER BY bd.birthday;

我目前的做法似乎非常低效且相当复杂:需要> 100毫秒。有没有人想要更紧凑和高性能的查询?我正在使用Postgresql 9.5.3。谢谢!

1 个答案:

答案 0 :(得分:0)

也许尝试加入生成系列:

create table bday(id serial, name text, dob date);
insert into bday (name, dob) values ('a', '08-21-1972'::date);
insert into bday (name, dob) values ('b', '03-20-1974'::date);

select * from bday , 
lateral( select generate_series( (1950-y)/5  , (2010-y)/5)*5 + y  as year
         from (select date_part('year',dob)::integer as y) as t2 
       ) as t1;

这将针对每个条目产生1950年至2010年之间的年份。

你可以添加一个where子句来排除2010年以后出生的人(他们不能在范围内过生日) 或者排除1850年之前出生的人(他们不太可能......)

- 编辑(编辑后):

因此,您的generate_series每年创建360多行。在超过30,000的100年。他们加入了每个用户。 (3个用户=> 100.000行)

我的查询只生成需要多年的行。在100年,即20行。 这意味着每个用户20行。

除以5,确保开始日期为圆形生日。 (1950-y)/5)计算1950年之前的生日数。

1941年出生的人需要跳过1941年和1946年,但是在1951年有一个圆形的生日。所以这是差异(9年)除以5,然后实际加1来说明第0个。

如果该人在1950年之后出生,则该数字为负数,greatest(-1,...)+1从实际生日年份开始给出0。

但实际上它应该是

select * from bday , 
lateral( select generate_series( greatest(-1,(1950-y)/5)+1, (2010-y)/5)*5 + y  as year
         from (select date_part('year',dob)::integer as y) as t2 
       ) as t1;

(如果你想从5岁开始,你可能正在做greatest(0,...)+1