У меня есть таблица со следующей схемой:
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`bookingDate` date NOT NULL,
PRIMARY KEY(`id`)
Мне нужно получить все даты текущего месяца, когда имеется менее 5 бронирований. (это означает, что существует менее 5 строк, где bookingDate == дата)
Я пробовал перебирать каждый день месяца по отдельности, но из соображений производительности не имеет смысла выполнять +/- 30 запросов за каждый месяц.
Подсказка: вы можете использовать предложения GROUP BY и HAVING для подсчета бронирований на каждую дату и фильтрации результатов.
GROUP BY делает все это одновременно, а CTE — это необычный способ итерации.
@user1191247 user1191247 - Лучше всего это сделать с помощью LEFT JOIN из таблицы, в которой указаны все даты. См. пример в mysql.rjweb.org/doc.php/index_cookbook_mysql#sequence
@ user1191247 - У аналогичного вопроса есть тот недостаток, что он не использует MariaDB. Также обратите внимание, что в MariaDB есть 2 типа «упорядочения» — движок и псевдотаблица для построения последовательности чисел. Последнее можно легко +динамически_ превратить в серию последовательных дат для задачи ОП.


Есть много разных способов решить эту проблему.
Вариант 1 (не совсем вариант)
SELECT bookingDate
FROM bookings
WHERE bookingDate BETWEEN '2023-09-01' AND '2023-09-30'
GROUP BY bookingDate
HAVING COUNT(*) < 5;
Это вернет только bookingDate с 1, 2, 3 или 4 бронированиями, так как не известно о датах с нулевым бронированием.
Вариант 2
Вы можете инвертировать приведенный выше запрос и перебрать результат в своем клиентском приложении, удаляя все даты, существующие в результате:
SELECT bookingDate
FROM bookings
GROUP BY bookingDate
HAVING COUNT(*) >= 5;
Но мне это кажется неуклюжим.
Вариант 3
Вы можете начать с полного списка интересующих вас дней, созданного с помощью механизма хранения последовательностей MariaDB:
/* Full list of days in September 2023 based on first
* of month and number of days in month */
SELECT '2023-09-01' + INTERVAL (seq - 1) DAY AS dt
FROM seq_1_to_30;
/* Full list of days in current month */
SELECT CURRENT_DATE - INTERVAL (DAY(CURRENT_DATE) - 1) DAY + INTERVAL (seq - 1) DAY AS dt
FROM seq_1_to_31
WHERE seq <= DAY(LAST_DAY(CURRENT_DATE));
Очевидно, что в этом шаге нет необходимости, если у вас уже есть календарная таблица со всеми датами или непрерывный набор для работы.
И затем LEFT JOIN к вашему bookings:
SELECT s.dt, COUNT(b.id) AS num_bookings
FROM (
SELECT '2023-09-01' + INTERVAL (seq - 1) DAY AS dt
FROM seq_1_to_30
) s
LEFT JOIN bookings b
ON s.dt = b.bookingDate
GROUP BY s.dt
HAVING num_bookings < 5;
Вот db<>рабочий пример, с которой можно поиграть.
Примечание. В вашей текущей таблице отсутствует индекс bookingDate:
ALTER TABLE bookings ADD INDEX idx_booking_date (bookingDate);
Чтобы добиться этого более эффективно, вы можете использовать один запрос. Вот пошаговое описание решения:
Сгенерируйте все даты для текущего месяца. LEFT JOIN сгенерированные даты с таблицей бронирования в bookingDate. Сгруппируйте по дате и подсчитайте бронирования на каждую дату. Отфильтруйте даты, на которые есть 5 или более бронирований. Вот как вы можете это сделать:
-- Step 1: Generate all dates for the current month
WITH RECURSIVE DateSequence AS (
SELECT DATE_FORMAT(CURDATE(), '%Y-%m-01') AS dateValue -- Start from the first day of the current month
UNION ALL
SELECT DATE_ADD(dateValue, INTERVAL 1 DAY)
FROM DateSequence
WHERE DATE_ADD(dateValue, INTERVAL 1 DAY) <= LAST_DAY(CURDATE()) -- Up to the last day of the current month
)
-- Step 2, 3, and 4
SELECT ds.dateValue
FROM DateSequence ds
LEFT JOIN YourTableName t ON ds.dateValue = t.bookingDate
GROUP BY ds.dateValue
HAVING COUNT(t.id) < 5;
Замените YourTableName названием вашего стола бронирования. Результат этого запроса предоставит вам все даты текущего месяца, когда имеется менее 5 бронирований.
Преимущество этого подхода заключается в том, что вы получаете результат в одном запросе, что снижает накладные расходы на выполнение нескольких отдельных запросов.
Вместо этого вы можете просто найти все даты, на которые ЕСТЬ 5 или более бронирований, и реализовать соответствующую логику в своем клиентском приложении.
SELECT bookingDate
FROM bookings
GROUP BY bookingDate
HAVING COUNT(bookingDate) > 4;
Разве это не «вариант 2» в моем ответе?
Почему в условии больше 4?
Надеюсь, этот код вам поможет:
-- Получить даты текущего месяца с менее чем 5 бронированиями.
SELECT DISTINCT bookingDate
FROM bookings # your-table-name
WHERE MONTH(bookingDate) = MONTH(CURRENT_DATE()) AND YEAR(bookingDate) = YEAR(CURRENT_DATE())
GROUP BY bookingDate
HAVING COUNT(*) < 5;
Что вы пробовали? Что именно не получилось?