Выбор дат месяца на основе существующих строк

У меня есть таблица со следующей схемой:

`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`bookingDate` date NOT NULL,
 PRIMARY KEY(`id`)

Мне нужно получить все даты текущего месяца, когда имеется менее 5 бронирований. (это означает, что существует менее 5 строк, где bookingDate == дата)

Я пробовал перебирать каждый день месяца по отдельности, но из соображений производительности не имеет смысла выполнять +/- 30 запросов за каждый месяц.

Что вы пробовали? Что именно не получилось?

Akina 26.09.2023 15:47

Подсказка: вы можете использовать предложения GROUP BY и HAVING для подсчета бронирований на каждую дату и фильтрации результатов.

Amit Mohanty 26.09.2023 16:00

GROUP BY делает все это одновременно, а CTE — это необычный способ итерации.

Rick James 26.09.2023 17:44

@user1191247 user1191247 - Лучше всего это сделать с помощью LEFT JOIN из таблицы, в которой указаны все даты. См. пример в mysql.rjweb.org/doc.php/index_cookbook_mysql#sequence

Rick James 27.09.2023 05:53

@ user1191247 - У аналогичного вопроса есть тот недостаток, что он не использует MariaDB. Также обратите внимание, что в MariaDB есть 2 типа «упорядочения» — движок и псевдотаблица для построения последовательности чисел. Последнее можно легко +динамически_ превратить в серию последовательных дат для задачи ОП.

Rick James 27.09.2023 18:37
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
4
5
160
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Есть много разных способов решить эту проблему.

Вариант 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» в моем ответе?

user1191247 28.09.2023 12:29

Почему в условии больше 4?

Amin Zayeromali 28.09.2023 17:05

Надеюсь, этот код вам поможет:

-- Получить даты текущего месяца с менее чем 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;

Другие вопросы по теме