Как найти точные времена года и дни из заданного диапазона дат? - PullRequest
2 голосов
/ 20 августа 2011

Мне нужно это для расчета стоимости аренды автомобиля. Цены на автомобили различаются в зависимости от сезона.

У меня есть такая таблица season_dates

id    slug    start                  end 
1     low     2011-01-01 00:00:00    2011-04-30 00:00:00
2     mid     2011-05-01 00:00:00    2011-06-30 00:00:00
3     high    2011-07-01 00:00:00    2011-08-31 00:00:00
4     mid     2011-09-01 00:00:00    2011-10-31 00:00:00
5     low     2011-11-01 00:00:00    2011-12-31 00:00:00

Пользователи выбирают дни, например:

start_day   08/20   end_day  08/25

Мой запрос таков:

SELECT * from arac_donemler
where DATE_FORMAT(start, '%m/%d') <= '08/20'
  and DATE_FORMAT(end, '%m/%d') >= '08/25'

Это дает мне высокий сезон, это правильно.

Но что я не смог обработать, так это: что если пользователь выберет диапазон дат между двумя сезонами?

Например, с 20 августа по 5 сентября.

На этот раз я должен выяснить, к каким временам относится диапазон дат? И я должен рассчитать, сколько дней в каждом сезоне?

Для примера выше, Высокий сезон заканчивается 31 августа. Таким образом, 31-20 = 11 дней в разгар сезона, 5 дней в разгар сезона.

Как я могу обеспечить это разделение?

Я надеюсь, что смогу это объяснить.

Я пробовал так много вещей, как соединительный стол внутри, но не смог.

Ответы [ 3 ]

0 голосов
/ 20 августа 2011

Я бы хранил все отдельные дни в таблице.Это простой пример.

create table dates (
id int not null auto_increment primary key,
pday date,
slug tinyint,
price int);

insert into dates (pday,slug,price)
values 
('2011-01-01',1,10),
('2011-01-02',1,10),
('2011-01-03',2,20),
('2011-01-04',2,20),
('2011-01-05',2,20),
('2011-01-06',3,30),
('2011-01-07',3,30),
('2011-01-08',3,30);

select 
concat(min(pday),'/',max(pday)) as period,
count(*) as days,
sum(price) as price_per_period
from dates 
where pday between '2011-01-02' and '2011-01-07'
group by slug

+-----------------------+------+------------------+
| period                | days | price_per_period |
+-----------------------+------+------------------+
| 2011-01-02/2011-01-02 |    1 |               10 |
| 2011-01-03/2011-01-05 |    3 |               60 |
| 2011-01-06/2011-01-07 |    2 |               60 |
+-----------------------+------+------------------+
3 rows in set (0.00 sec)

РЕДАКТИРОВАТЬ. Версия с grandtotal

select 
case
when slug is null then 'Total' else concat(min(pday),'/',max(pday)) end as period,
count(*) as days,
sum(price) as price_per_period
from dates 
where pday between '2011-01-02' and '2011-01-07'
group by slug
with rollup;

+-----------------------+------+------------------+
| period                | days | price_per_period |
+-----------------------+------+------------------+
| 2011-01-02/2011-01-02 |    1 |               10 |
| 2011-01-03/2011-01-05 |    3 |               60 |
| 2011-01-06/2011-01-07 |    2 |               60 |
| Total                 |    6 |              130 |
+-----------------------+------+------------------+
4 rows in set (0.00 sec)

edit.Хранимая процедура для заполнения таблицы

delimiter $$
create procedure calendario(in anno int)
begin
  declare i,ultimo int;
  declare miadata date; 
  set i = 0;
  select dayofyear(concat(anno,'-12-31')) into ultimo;
  while i < ultimo do
    select concat(anno,'-01-01') + interval i day into miadata;
    insert into dates (pday) values (miadata);
    set i = i + 1;
  end while;
end $$
delimiter ;

call calendario(2011);
0 голосов
/ 21 августа 2011

Если у вас есть таблица RENTAL (в реальной версии потребуется много других деталей):

CREATE TABLE Rental
(
    start   DATE NOT NULL,
    end     DATE NOT NULL
);

и вы заполните его:

INSERT INTO rental VALUES('2011-08-20', '2011-09-05');
INSERT INTO rental VALUES('2011-08-20', '2011-08-25');

тогда этот запрос дает правдоподобный результат:

SELECT  r.start AS r_start, r.end AS r_end,
        s.start AS s_start, s.end AS s_end,
        GREATEST(r.start, s.start) AS p_start,
        LEAST(r.end, s.end)        AS p_end,
        DATEDIFF(LEAST(r.end, s.end), GREATEST(r.start, s.start)) + 1 AS days,
        s.id, s.slug
  FROM rental AS r
  JOIN season_dates AS s ON r.start <= s.end AND r.end >= s.start;

Это дает:

r_start     r_end       s_start     s_end       p_start     p_end       days id slug
2011-08-20  2011-09-05  2011-07-01  2011-08-31  2011-08-20  2011-08-31  12   3  high
2011-08-20  2011-09-05  2011-09-01  2011-10-31  2011-09-01  2011-09-05   5   4  mid 
2011-08-20  2011-08-25  2011-07-01  2011-08-31  2011-08-20  2011-08-25   6   3  high

Обратите внимание, что я считаю 12 дней вместо 11; это +1 в выражении days. Это становится сложно; Вы должны решить, будет ли автомобиль возвращен в тот же день, когда он был взят в аренду, будет ли это один день аренды? Что если он будет возвращен на следующий день? Может быть, время имеет значение? Но это входит в подробные бизнес-правила, а не в общие принципы. Может быть, продолжительность больше из необработанных DATEDIFF () и 1? Также обратите внимание, что в этой схеме указаны только даты начала и окончания аренды; реальная схема будет иметь какой-то номер договора аренды в таблице аренды.

(Исповедь: смоделирован с использованием IBM Informix 11.70.FC2 на MacOS X 10.7.1, но MySQL задокументирован как поддерживающий LEAST, GREATEST и DATEDIFF, и я смоделировал их в Informix. Наиболее заметное отличие может заключаться в том, что Informix имеет DATE введите без какого-либо компонента времени, поэтому время не требуется и не отображается.)


Но [...] период сезонов всегда один и тот же каждый год. Поэтому я подумал сравнить только дни и месяцы. 2011 год не важен. В последующие годы будет использоваться только 2011 год. На этот раз проблема возникает. Например, низкий сезон включает в себя ноябрь, декабрь и затем переход на январь, февраль, март, апрель. Если пользователь выбирает диапазон дат с 01.05.2011 по ... 2011 Нет проблем. Я просто сравниваю месяц и день с DATE_FORMAT (конец, «% m /% d»). Но если он выберет диапазон с декабря по январь следующего года, как мне рассчитать дни?

Обратите внимание, что 5 записей в год в таблице Season_Dates не приведут к потере 8 "гибкого диска из-за емкости хранилища в течение хороших нескольких лет, не говоря уже о монстрах с диском 500 ГиБ. Так что, безусловно, самая простая вещь определить записи на 2012 год в 5 новых строках таблицы Season_Dates. Это также позволяет обрабатывать тот факт, что в декабре власти, принимающие решения, примут правила, которые будут другими (с 20 декабря по 4 января будет «середина»). например, не «низкий» сезон).

0 голосов
/ 20 августа 2011

Я позволю другим присоединиться к правильному способу сравнения дат в SQL (ваш почти наверняка убивает индексирование для таблицы), но для начала вы можете получить именно те сезоны, которые актуальны by

select * from arac_donemler
 where end >= [arrival-date]
   and start <= [departure-date]

Затем вы должны выполнить остальную часть своей обработки (выяснить, сколько дней в каждом сезоне и т. д.) в бизнес-логике, а не в запросе к базе данных.

...