Postgres生日选择

时间:2011-08-02 14:30:39

标签: postgresql intervals date-of-birth

我使用Postgres数据库。此DB具有一个包含用户的表,该用户具有生日(日期字段)。现在我想让所有在即将到来的一周过生日的用户....

我的第一次尝试:SELECT id FROM public.users WHERE id IN (lange reeks) AND birthdate > NOW() AND birthdate < NOW() + interval '1 week'

但这不会导致,显然是因为一年之后。我该如何解决这个问题?

有没有人知道PG在29-02生日会发生什么事情?

11 个答案:

答案 0 :(得分:9)

我们可以使用postgres函数以非常好的方式执行此操作。

假设我们在people列中有一个表dob,其出生日期是日期,我们可以创建一个函数,允许我们将该列编入索引而忽略年份。 (感谢Zoltán Böszörményi):

CREATE OR REPLACE FUNCTION indexable_month_day(date) RETURNS TEXT as $BODY$
  SELECT to_char($1, 'MM-DD');
$BODY$ language 'sql' IMMUTABLE STRICT;

CREATE INDEX person_birthday_idx ON people (indexable_month_day(dob));

现在,我们需要查询表和索引。例如,为了让每个人在任何一年四月过生日:

SELECT * FROM people 
WHERE 
    indexable_month_day(dob) >= '04-01'
AND 
    indexable_month_day(dob) < '05-01';

有一个问题:如果我们的开始/结束时段跨越一年边界,我们需要更改查询:

SELECT * FROM people 
WHERE 
    indexable_month_day(dob) >= '12-29'
OR 
    indexable_month_day(dob) < '01-04';

为了确保我们匹配闰日生日,我们需要知道我们是否会向前或向后“移动”它们。就我而言,两天都匹配起来比较简单,所以我的一般查询如下:

SELECT * FROM people 
WHERE 
    indexable_month_day(dob) > '%(start)%'
%(AND|OR)%
    indexable_month_day(dob) < '%(finish)%';

我有一个django queryset方法,这使得这一切变得更加简单:

def birthday_between(self, start, finish):
    """Return the members of this queryset whose birthdays
    lie on or between start and finish."""
    start = start - datetime.timedelta(1)
    finish = finish + datetime.timedelta(1)
    return self.extra(where=["indexable_month_day(dob) < '%(finish)s' %(andor)s indexable_month_day(dob) > %(start)s" % {
        'start': start.strftime('%m-%d'),
        'finish': finish.strftime('%m-%d'),
        'andor': 'and if start.year == finish.year else 'or'
    }]

def birthday_on(self, date):
    return self.birthday_between(date, date)

现在,我可以做以下事情:

Person.objects.birthday_on(datetime.date.today())

仅在前一天或仅在后一天匹配闰日生日:您只需将SQL测试更改为“&gt; =”或“&lt; =”,而不是调整开始/在python函数中完成。

答案 1 :(得分:2)

我对此并不过分自信,但它似乎在我的测试中起作用。这里的关键是OVERLAPS运算符和一些日期算术。

我假设你有一张桌子:

create temporary table birthdays (name varchar, bday date);

然后我把一些东西放进去:

insert into birthdays (name, bday) values 
('Aug 24', '1981-08-24'), ('Aug 04', '1982-08-04'), ('Oct 10', '1980-10-10');

此查询将为我提供下周生日的人:

select * from 
  (select *, bday + date_trunc('year', age(bday)) + interval '1 year' as anniversary from birthdays) bd 
where 
  (current_date, current_date + interval '1 week') overlaps (anniversary, anniversary)

date_trunc会截断当年的日期,因此它应该会让您达到当前年份。我不得不加一年。这告诉我,由于某种原因,我在那里有一个一个接一个。也许我只需要找到一种方法来获取日期。无论如何,还有其他方法可以进行此计算。 age为您提供从日期或时间戳到今天的间隔。我想在生日和今天之间添加年份以获得当年的日期。

真正的关键是使用overlaps查找日期重叠的记录。我使用周年纪念日两次来获得时间点。

答案 2 :(得分:2)

首先了解当前人使用age()的年龄,然后从extract(year from age())获取年份。这是他们目前的年龄,所以他们的下一个生日年龄增加1年。然后通过将这多年* interval '1 year'的间隔添加到他们的生日来找到他们的下一个生日。完成。

我在这里使用了一个子选择,将next_birth_day列添加到完整的表中,以使select子句更简单。然后,您可以根据自己的需要选择适合的条件。

select * 
from (
     select *, 
       (extract(year from age(birth_date)) + 1) *  interval '1 year' + birth_date "next_birth_day"
      from public.users
) as users_with_upcoming_birth_days
where next_birth_day between now() and now() + '7 days'

答案 3 :(得分:2)

最后,为了展示接下来14天的即将到来的生日,我使用了这个:

SELECT 
    -- 14 days before birthday of 2000
    to_char( to_date(to_char(c.birthdate, '2000-MM-dd'), 'YYYY-MM-dd') - interval '14 days' , 'YYYY-MM-dd')  as _14b_b2000,
    -- birthday of 2000
    to_date(to_char(c.birthdate, '2000-MM-dd'), 'YYYY-MM-dd') as date_b2000,
    -- current date of 2000
    to_date(to_char(current_date, '2000-MM-dd'), 'YYYY-MM-dd') as date_c2000,
    -- 14 days after current date of 2000
    to_char( to_date(to_char(current_date, '2000-MM-dd'), 'YYYY-MM-dd') + interval '14 days' , 'YYYY-MM-dd') as _14a_c2000,
    -- 1 year after birthday of 2000
    to_char( to_date(to_char(c.birthdate, '2000-MM-dd'), 'YYYY-MM-dd') + interval '1 year' , 'YYYY-MM-dd') as _1ya_b2000
FROM c
WHERE 
    -- the condition 
    -- current date of 2000 between 14 days before birthday of 2000 and birthday of 2000
    to_date(to_char(current_date, '2000-MM-dd'), 'YYYY-MM-dd') between 
        to_date(to_char(c.birthdate, '2000-MM-dd'), 'YYYY-MM-dd') - interval '14 days' and 
        to_date(to_char(c.birthdate, '2000-MM-dd'), 'YYYY-MM-dd') 
    or 
    -- 1 year after birthday of 2000 between current date of 2000 and 14 days after current date of 2000
    to_date(to_char(c.birthdate, '2000-MM-dd'), 'YYYY-MM-dd') + interval '1 year' between 
        to_date(to_char(current_date, '2000-MM-dd'), 'YYYY-MM-dd') and 
        to_date(to_char(current_date, '2000-MM-dd'), 'YYYY-MM-dd') + interval '14 days' 
;

所以: 为了解决闰年问题,我将生日和当前日期设置为2000, 并仅从这个初始正确的日期处理间隔。

照顾近端/开始日期, 我首先将2000年当前日期与2000年生日间隔进行比较, 如果当前日期是年末,而生日是在开头, 我将2001年的生日与2000年的当前日期间隔进行了比较。

答案 4 :(得分:1)

这是一个在大多数情况下获得正确结果的查询。

SELECT 
    (EXTRACT(MONTH FROM DATE '1980-08-05'),
     EXTRACT(DAY FROM DATE '1980-08-05')) 
IN (
    SELECT EXTRACT(MONTH FROM CURRENT_DATE + s.a) AS m,
           EXTRACT(DAY FROM CURRENT_DATE + s.a) AS d 
    FROM GENERATE_SERIES(0, 6) AS s(a)
);

(它没有正确处理闰年;但您可以再次使用extract来按照闰年而不是当前年份来处理子选择。

编辑:让它适用于所有情况,并作为有用的查询而不是标量选择。我正在使用一些额外的子选择,因此我不必每月和每天两次输入相同的日期或表达式,当然实际数据将在表中而不是values表达式中。您可能会采用不同的方式进通过制作一个包含闰日的数周的更智能系列,它可能仍然有待提高,因为有时候这段时间只包含6天(非闰年)。

我会尝试从内到外解释这个问题;我做的第一件事是将目标日期(CURRENT_DATE通常,但在此代码中显式)规范化为我认为是闰年的一年,以便2月29日出现在日期之间。下一步是与正在考虑的所有月 - 日对产生关系;由于没有简单的方法来进行间隔检查,所以使用generate_series

进行间隔检查。

从那里可以很简单地从目标关系(people别名)中提取月和日,并仅过滤子选择中的行。

SELECT * 
FROM 
    (select column1 as birthdate, column2 as name
    from (values 
        (date '1982-08-05', 'Alice'),
        (date '1976-02-29', 'Bob'),
        (date '1980-06-10', 'Carol'),
        (date '1992-06-13', 'David')
    ) as birthdays) as people 
WHERE 
    ((EXTRACT(MONTH FROM people.birthdate), 
     EXTRACT(DAY FROM people.birthdate)) IN (
        SELECT EXTRACT(MONTH FROM thedate.theday + s.a) AS m,
               EXTRACT(DAY FROM thedate.theday + s.a) AS d
        FROM 
                (SELECT date (v.column1 - 
                        (extract (YEAR FROM v.column1)-2000) * INTERVAL '1 year'
                       ) as theday
                 FROM (VALUES (date '2011-06-09')) as v) as thedate,
                 GENERATE_SERIES(0, 6) AS s(a)
        )
    )

我在这里工作的日子应该一直很好地工作,直到两个月的时间间隔(如果你想看那么远),自12月31日+两个月后,变化应该包括闰日。另一方面,对于这样的查询来说,整个月工作几乎肯定更有用,在这种情况下,您实际上不需要extract(month from ...以外的任何内容。

答案 5 :(得分:0)

这是基于Daniel Lyons的周年纪念,通过计算下一个生日和今天之间的间隔,只需+/-日算术:

SELECT
    today,
    birthday,
    CASE
        WHEN this_year_anniversary >= today
        THEN this_year_anniversary
        ELSE this_year_anniversary + '1 year'::interval
    END - today < '1 week'::interval AS is_upcoming
FROM
    (
        SELECT
            today,
            birthday,
            birthday + years AS this_year_anniversary
       FROM
            (
                SELECT
                    today,
                    birthday,
                    ((
                        extract(year FROM today) - extract(year from birthday)
                    ) || ' years')::interval AS years
                FROM
                    (VALUES ('2011-02-28'::date)) AS t1 (today),
                    (VALUES
                        ('1975-02-28'::date),
                        ('1975-03-06'::date),
                        ('1976-02-28'::date),
                        ('1976-02-29'::date),
                        ('1976-03-06'::date)
                    ) AS t2 (birthday)
            ) AS t
    ) AS t;

答案 6 :(得分:0)

如果您希望它与闰年一起使用:

create or replace function birthdate(date)
  returns date
as $$
  select (date_trunc('year', now()::date)
         + age($1, 'epoch'::date)
         - (extract(year from age($1, 'epoch'::date)) || ' years')::interval
         )::date;
$$ language sql stable strict;

然后:

where birthdate(birthdate) between current_date
                            and current_date + interval '1 week'

另见:

Getting all entries who's Birthday is today in PostgreSQL

答案 7 :(得分:0)

例如:生日:1月20日至10月10日

SELECT * FROM users WHERE TO_CHAR(birthdate, '1800-MM-DD') BETWEEN '1800-01-20' AND '1800-02-10'

为何1800? 无论哪一年都可以;

在我的注册表格中,我可以告知出生日期(有年)或只是生日(没有年份),在这种情况下,我保存为1800,以便更容易使用日期

答案 8 :(得分:0)

这是我的看法,也适用于闰年:

CREATE OR REPLACE FUNCTION days_until_birthday(
    p_date date
    ) RETURNS integer AS $$
DECLARE
    v_now date;
    v_days integer;
    v_date_upcoming date;

    v_years integer;

BEGIN
    v_now = now()::date;
    IF (p_date IS NULL OR p_date > v_now) THEN
        RETURN NULL;
    END IF;

    v_years = date_part('year', v_now) - date_part('year', p_date);

    v_date_upcoming = p_date + v_years * interval '1 year';

    IF (v_date_upcoming < v_now) THEN
        v_date_upcoming = v_date_upcoming + interval '1 year';
    END IF;

    v_days = v_date_upcoming - v_now;
    RETURN v_days;
END
$$ LANGUAGE plpgsql IMMUTABLE;

答案 9 :(得分:0)

我知道这篇文章很老,但我遇到了同样的问题,想出了这个简单而优雅的解决方案: 对于在过去20天中过生日的人来说,年龄()和年龄相当容易...

<html amp

答案 10 :(得分:0)

我只是从原始出生日期创建了今年的日期。

( DATE_PART('month', birth_date) || '/' || DATE_PART('day', birth_date) || '/' || DATE_PART('year', now()))::date between :start_date and :end_date

我希望这有帮助。