Postgres birthdays selection

后端 未结 11 1211
猫巷女王i
猫巷女王i 2020-12-10 08:31

I work with a Postgres database. This DB has a table with users, who have a birthdate (date field). Now I want to get all users who have their birthday in the upcoming week.

相关标签:
11条回答
  • 2020-12-10 08:45

    Here's a query that gets the right result, most of the time.

    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)
    );
    

    (it doesn't take care of leap years correctly; but you could use extract again to work the subselect in terms of a leap year instead of the current year.

    EDIT: Got it working for all cases, and as a useful query rather than a scalar select. I'm using some extra subselects so that I don't have to type the same date or expression twice for month and day, and of course the actual data would be in a table instead of the values expression. You might adapt this differently. It might still stand to improve by making a more intelligent series for weeks containing leap days, since sometimes that interval will only contain 6 days (for non-leap years).

    I'll try to explain this from the inside-out; First thing I do is normalize the target date (CURRENT_DATE usually, but explicit in this code) into a year that I know is a leap year, so that February 29th appears among dates. The next step is to generate a relation with all of the month-day pairs that are under consideration; Since there's no easy way to do an interval check in terms of month-day, it's all happening using generate_series,

    From there it's a simple matter of extracting the month and day from the target relation (the people alias) and filtering just the rows that are in the subselect.

    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)
            )
        )
    

    Operating on days, as I've done here, should work splendidly all the way up until a two month interval (if you wanted to look out that far), since december 31 + two months and change should include the leap day. On the other hand, it's almost certainly more useful to just work on whole months for such a query, in which case you don't really need anything more than extract(month from ....

    0 讨论(0)
  • 2020-12-10 08:48

    I have simply created this year date from original birth date.

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

    I hope this help.

    0 讨论(0)
  • 2020-12-10 08:51

    This is based on Daniel Lyons's anniversary idea, by calculating the interval between the next birthday and today, with just +/- date arithmetic:

    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;
    
    0 讨论(0)
  • 2020-12-10 08:51

    Here's my take, which works with leap years too:

    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;
    
    0 讨论(0)
  • 2020-12-10 08:52

    I know this post is old, but I had the same issue and came up with this simple and elegant solution: It is pretty easy with age() and accounts for lap years... for the people who had their birthdays in the last 20 days:

    SELECT * FROM c 
    WHERE date_trunc('year', age(birthdate)) != date_trunc('year', age(birthdate + interval '20 days'))
    
    0 讨论(0)
  • 2020-12-10 08:53

    First find out how old the person currently is using age(), then grab the year from that extract(year from age()). This is how old they are currently in years, so for their age at their next birthday add 1 to the year. Then their next birthday is found by adding an interval of this many years * interval '1 year' to their birthday. Done.

    I've used a subselect here to add the next_birth_day column in to the complete table to make the select clause simpler. You can then play with the where conditions to suit your needs.

    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'
    
    0 讨论(0)
提交回复
热议问题