我在表格中获得了用户的生日,并希望显示下一个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。谢谢!
答案 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
)