MySQL. Ближайшие дни рождения.

/ Просмотров: 349
MySQL. Ближайшие дни рождения.

Есть таблица дней рождения. Необходимо сделать запрос ближайших именинников. В инете так и не нашел ничего красивого, вот что получилось у меня.

SET @nd := 12;
SET @sd := NOW();
Select * 
    from `bday_test` 
    where 
        (DAYOFYEAR(birthday) - DAYOFYEAR(@sd) > 1 AND DAYOFYEAR(birthday) - DAYOFYEAR(@sd) <= @nd)
        OR DAYOFYEAR(birthday)+364 - DAYOFYEAR(@sd) < @nd 
    order by 
        MONTH(birthday) < MONTH(@sd), 
        MONTH(birthday), 
        DAYOFMONTH(birthday);

В MySQL есть такая функция DAYOFYEAR которая возвращает порядковый номер дня года для аргумента date в диапазоне от 1 до 366.

В нашем запросе мы делаем следующее:

1. проверяем, что DAYOFYEAR дня рождения больше DAYOFYEAR текущей даты.

2. проверяем, что DAYOFYEAR дня рождения больше DAYOFYEAR текущей даты не более чем на 10 дней.

3. На этом бы и все, но нужно отработать ситуацию, когда текущая дата приходится на конец года, а ближайшие дни рождения уже в следующем году. Для этого, прибавляем к DAYOFYEAR дня рождения 364. Таким образом, 4 января будет иметь порядковый номер дня года ~368. Как и во втором пункте, проверяем, что полученное число больше DAYOFYEAR текущей даты не более чем на 10 дней.

Теперь протестируем.

Создадим таблицу дней рождения:

CREATE TABLE `bday_test` (
 `id` int(4) NOT NULL AUTO_INCREMENT,
 `birthday` date NOT NULL DEFAULT '0000-00-00',
 `full_name` varchar(128) NOT NULL DEFAULT '''',
 PRIMARY KEY (`id`));

Для того, чтобы заполнить таблицу данными создадим процедуру:

DELIMITER $$
DROP PROCEDURE IF EXISTS `xbetomskru_ru`.`ins_bds`$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `ins_bds`()
BEGIN
  DECLARE i INT DEFAULT 1;
  DECLARE d DATE;
  DECLARE yr INT;
  WHILE i < 367 DO
    SET yr = FLOOR(RAND()*(100)+1920);
    SET d = MAKEDATE(yr, i);
    INSERT INTO `bday_test` (`birthday`, `full_name`) VALUES (d, DATE_FORMAT(d, '%d %M %Y'));
    SET d = MAKEDATE(1976, i);
    INSERT INTO `bday_test` (`birthday`, `full_name`) VALUES (d, DATE_FORMAT(d, '%d %M %Y'));
    SET i = i + 1;
  END WHILE;
END$$
DELIMITER ;

Эта процедура в цикле числа от 1 до 366 и создает дату из дня года и случайного года (1920 - 2020). Из-за 29 февраля этот алгоритм пропускает некоторые даты. Для того, чтобы эти пробелы исключить, попутно создаем даты только для 1976 года.

Выполняем процедуру, заполняем таблицу:

call ins_bds();

На всякий случай добавляем несколько дней рождения 29 февраля:

INSERT INTO `bday_test` (`birthday`, `full_name`) VALUES ('2020-02-29', CONCAT('LEAP ', DATE_FORMAT('2020-02-29', '%d %M %Y')));
INSERT INTO `bday_test` (`birthday`, `full_name`) VALUES ('2016-02-29', CONCAT('LEAP ', DATE_FORMAT('2016-02-29', '%d %M %Y')));
INSERT INTO `bday_test` (`birthday`, `full_name`) VALUES ('2012-02-29', CONCAT('LEAP ', DATE_FORMAT('2012-02-29', '%d %M %Y')));
INSERT INTO `bday_test` (`birthday`, `full_name`) VALUES ('2008-02-29', CONCAT('LEAP ', DATE_FORMAT('2008-02-29', '%d %M %Y')));

Для тестирования будем использовать параметрический запрос:

SET @nd := 12;
SET @sd := NOW();
Select * from `bday_test` where (DAYOFYEAR(birthday) - DAYOFYEAR(@sd) > 1 AND DAYOFYEAR(birthday) - DAYOFYEAR(@sd) <= @nd) OR DAYOFYEAR(birthday)+364 - DAYOFYEAR(@sd) < @nd order by MONTH(birthday), DAYOFMONTH(birthday);
+------+------------+---------------+
| id   | birthday   | full_name     |
+------+------------+---------------+
| 1716 | 1976-03-31 | 31 March 1976 |
| 1715 | 2007-04-01 | 01 April 2007 |
| 1718 | 1976-04-01 | 01 April 1976 |
| 1720 | 1976-04-02 | 02 April 1976 |
| 1719 | 1928-04-02 | 02 April 1928 |
| 1717 | 1993-04-02 | 02 April 1993 |
| 1721 | 1940-04-03 | 03 April 1940 |
| 1722 | 1976-04-03 | 03 April 1976 |
| 1724 | 1976-04-04 | 04 April 1976 |
| 1726 | 1976-04-05 | 05 April 1976 |
| 1723 | 1998-04-05 | 05 April 1998 |
| 1725 | 1951-04-06 | 06 April 1951 |
| 1728 | 1976-04-06 | 06 April 1976 |
| 1727 | 1942-04-07 | 07 April 1942 |
| 1730 | 1976-04-07 | 07 April 1976 |
| 1729 | 1938-04-08 | 08 April 1938 |
| 1732 | 1976-04-08 | 08 April 1976 |
| 1734 | 1976-04-09 | 09 April 1976 |
| 1731 | 1941-04-09 | 09 April 1941 |
| 1733 | 1975-04-10 | 10 April 1975 |
| 1736 | 1976-04-10 | 10 April 1976 |
| 1735 | 1931-04-11 | 11 April 1931 |
+------+------------+---------------+
22 rows in set (0.00 sec)

Теперь сделаем запрос, как будто текущая дата ближе к 29 февраля:

SET @nd := 12;
SET @sd := '2020-02-25';
Select * from `bday_test` where (DAYOFYEAR(birthday) - DAYOFYEAR(@sd) > 1 AND DAYOFYEAR(birthday) - DAYOFYEAR(@sd) <= @nd) OR DAYOFYEAR(birthday)+364 - DAYOFYEAR(@sd) < @nd order by MONTH(birthday), DAYOFMONTH(birthday);
+------+------------+-----------------------+
| id   | birthday   | full_name             |
+------+------------+-----------------------+
| 1649 | 1948-02-27 | 27 February 1948      |
| 1650 | 1976-02-27 | 27 February 1976      |
| 1651 | 2009-02-28 | 28 February 2009      |
| 1652 | 1976-02-28 | 28 February 1976      |
| 2269 | 2012-02-29 | LEAP 29 February 2012 |
| 2268 | 2016-02-29 | LEAP 29 February 2016 |
| 2267 | 2020-02-29 | LEAP 29 February 2020 |
| 2270 | 2008-02-29 | LEAP 29 February 2008 |
| 1654 | 1976-02-29 | 29 February 1976      |
| 1653 | 1982-03-01 | 01 March 1982         |
| 1656 | 1976-03-01 | 01 March 1976         |
| 1658 | 1976-03-02 | 02 March 1976         |
| 1655 | 1965-03-02 | 02 March 1965         |
| 1657 | 1957-03-03 | 03 March 1957         |
| 1660 | 1976-03-03 | 03 March 1976         |
| 1662 | 1976-03-04 | 04 March 1976         |
| 1659 | 1970-03-04 | 04 March 1970         |
| 1664 | 1976-03-05 | 05 March 1976         |
| 1661 | 1961-03-05 | 05 March 1961         |
| 1666 | 1976-03-06 | 06 March 1976         |
| 1663 | 1973-03-06 | 06 March 1973         |
| 1665 | 1966-03-07 | 07 March 1966         |
| 1668 | 1976-03-07 | 07 March 1976         |
| 1667 | 1990-03-08 | 08 March 1990         |
| 1670 | 1976-03-08 | 08 March 1976         |
| 1669 | 1931-03-09 | 09 March 1931         |
+------+------------+-----------------------+
26 rows in set (0.00 sec)

Теперь сделаем запрос, как будто текущая дата ближе к концу года:

SET @nd := 12;
SET @sd := '2020-12-25';
Select * from `bday_test` where (DAYOFYEAR(birthday) - DAYOFYEAR(@sd) > 1 AND DAYOFYEAR(birthday) - DAYOFYEAR(@sd) <= @nd) OR DAYOFYEAR(birthday)+364 - DAYOFYEAR(@sd) < @nd order by MONTH(birthday), DAYOFMONTH(birthday);
+------+------------+------------------+
| id   | birthday   | full_name        |
+------+------------+------------------+
| 1535 | 1933-01-01 | 01 January 1933  |
| 1536 | 1976-01-01 | 01 January 1976  |
| 1537 | 1954-01-02 | 02 January 1954  |
| 1538 | 1976-01-02 | 02 January 1976  |
| 1539 | 1950-01-03 | 03 January 1950  |
| 1540 | 1976-01-03 | 03 January 1976  |
| 1542 | 1976-01-04 | 04 January 1976  |
| 1541 | 1971-01-04 | 04 January 1971  |
| 1543 | 1984-01-05 | 05 January 1984  |
| 1544 | 1976-01-05 | 05 January 1976  |
| 1545 | 1989-01-06 | 06 January 1989  |
| 1546 | 1976-01-06 | 06 January 1976  |
| 1548 | 1976-01-07 | 07 January 1976  |
| 1547 | 1972-01-07 | 07 January 1972  |
| 2258 | 1976-12-27 | 27 December 1976 |
| 2257 | 1982-12-28 | 28 December 1982 |
| 2260 | 1976-12-28 | 28 December 1976 |
| 2262 | 1976-12-29 | 29 December 1976 |
| 2261 | 1992-12-29 | 29 December 1992 |
| 2259 | 2011-12-29 | 29 December 2011 |
| 2264 | 1976-12-30 | 30 December 1976 |
| 2263 | 2007-12-31 | 31 December 2007 |
| 2265 | 1940-12-31 | 31 December 1940 |
| 2266 | 1976-12-31 | 31 December 1976 |
+------+------------+------------------+
24 rows in set (0.00 sec)

Косяк, сначала идет январь, а потом декабрь. Нужно наоборот. Добавляем в сортировку "MONTH(birthday) < MONTH(@sd)":

SET @nd := 12;
SET @sd := '2020-12-25';
Select *, MONTH(birthday)<MONTH(@sd) as ffff from `bday_test` where (DAYOFYEAR(birthday) - DAYOFYEAR(@sd) > 1 AND DAYOFYEAR(birthday) - DAYOFYEAR(@sd) <= @nd) OR DAYOFYEAR(birthday)+364 - DAYOFYEAR(@sd) < @nd order by MONTH(birthday)<MONTH(@sd), MONTH(birthday), DAYOFMONTH(birthday);
+------+------------+------------------+------+
| id   | birthday   | full_name        | ffff |
+------+------------+------------------+------+
| 2258 | 1976-12-27 | 27 December 1976 |    0 |
| 2257 | 1982-12-28 | 28 December 1982 |    0 |
| 2260 | 1976-12-28 | 28 December 1976 |    0 |
| 2261 | 1992-12-29 | 29 December 1992 |    0 |
| 2262 | 1976-12-29 | 29 December 1976 |    0 |
| 2259 | 2011-12-29 | 29 December 2011 |    0 |
| 2264 | 1976-12-30 | 30 December 1976 |    0 |
| 2266 | 1976-12-31 | 31 December 1976 |    0 |
| 2263 | 2007-12-31 | 31 December 2007 |    0 |
| 2265 | 1940-12-31 | 31 December 1940 |    0 |
| 1535 | 1933-01-01 | 01 January 1933  |    1 |
| 1536 | 1976-01-01 | 01 January 1976  |    1 |
| 1537 | 1954-01-02 | 02 January 1954  |    1 |
| 1538 | 1976-01-02 | 02 January 1976  |    1 |
| 1540 | 1976-01-03 | 03 January 1976  |    1 |
| 1539 | 1950-01-03 | 03 January 1950  |    1 |
| 1541 | 1971-01-04 | 04 January 1971  |    1 |
| 1542 | 1976-01-04 | 04 January 1976  |    1 |
| 1544 | 1976-01-05 | 05 January 1976  |    1 |
| 1543 | 1984-01-05 | 05 January 1984  |    1 |
| 1546 | 1976-01-06 | 06 January 1976  |    1 |
| 1545 | 1989-01-06 | 06 January 1989  |    1 |
| 1548 | 1976-01-07 | 07 January 1976  |    1 |
| 1547 | 1972-01-07 | 07 January 1972  |    1 |
+------+------------+------------------+------+
24 rows in set (0.01 sec)