【问题标题】:How to find exact seasons and days from a given date range?如何从给定的日期范围内找到确切的季节和日期?
【发布时间】:2011-10-31 08:10:24
【问题描述】:

我需要这个来计算租车价格。汽车价格因季节而异。

我有一个这样的 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'

这给了我正确的旺季。

但我无法处理的是:如果用户选择了两个季节之间的日期范围怎么办?

例如从 8 月 20 日到 9 月 5 日。

这次我要找出日期范围属于哪个季节? 我必须计算每个季节有多少天?

对于上面的例子, 旺季于 8 月 31 日结束。所以 31-20 = 旺季 11 天,旺季 5 天。

我怎样才能提供这种分离?

希望我能解释一下。

我尝试了很多东西,比如在里面加入表格,但都没有成功。

【问题讨论】:

    标签: php mysql date-range


    【解决方案1】:

    我会让其他人用正确的方法在 SQL 中进行日期比较(您的方法几乎肯定会扼杀表的索引),但首先,您可以准确地获得相关的季节 em> 由

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

    然后您应该在业务逻辑中而不是在数据库查询中进行其余的处理(计算每个季节的天数等等)。

    【讨论】:

    • 对不起,但我在示例中使用的这个查询有什么区别?例如,从 8 月 20 日到 9 月 5 日,该查询要么返回 0 条记录。
    • 您将end出发 日期进行比较,并将start到达 日期进行比较,因此您只会匹配完全包含停留期的季节。我正在做相反的事情,匹配所有与住宿有任何重叠的季节。
    【解决方案2】:

    我会将所有单日存储在一个表中。 这是一个简单的例子。

    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)
    

    编辑。总计版本

    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)
    

    编辑。填充表的存储过程

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

    【讨论】:

    • 这看起来是个好主意。我要试试这个。但是我怎样才能以简单的方式插入 365 条记录:)
    • 我编辑了我的帖子,添加了一个我很久以前制作的存储过程。它甚至适用于闰年。然后,您可以快速更新其他列。
    • 顺便说一句,即使在 php 中使用简单的 while 循环,您也可以做同样的事情。
    • 我已经做到了。但是还有一个问题我想问。我将 datetime 用于 pday 列。但在现实世界中,年份并不重要。因为每年的季节总是一样的。在那种情况下,我只比较像 DATE_FORMAT(end, '%m/%d') 这样的月份和日期。好的。问题是:从 11 月到 4 月 30 日是淡季。那么,如果客户例如选择 31.12.2011 到 10.01.2012 这次我将如何计算天数?
    • 你是对的。我没有想过。您可能会使用相同的数据进入另一年,否则我将不得不考虑不同的解决方案。明天我将尝试 Jonathan Leffler 的解决方案。看起来很有趣。 :)
    【解决方案3】:

    如果你也有一张 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 天;这就是days 表达式中的+1。变得棘手;您必须决定是否在租用的同一天还车,那是一天的租用吗?如果第二天退货怎么办?也许时间很重要?但这涉及到详细的业务规则而不是一般原则。也许持续时间是原始 DATEDIFF() 和 1 中较大的一个?另请注意,此模式中只有租赁开始和结束日期来标识租赁;一个真实的模式会在租赁表中有某种租赁协议编号。

    (忏悔:在 MacOS X 10.7.1 上使用 IBM Informix 11.70.FC2 进行模拟,但 MySQL 被记录为支持 LEAST、GREATEST 和 DATEDIFF,我在 Informix 中模拟了这些。最明显的区别可能是 Informix 有一个 DATE没有任何时间组件的类型,因此不需要或显示时间。)


    但是 [...] 季节周期每年都一样。所以我想只比较几天和几个月。 2011年并不重要。明年将仅使用 2011 年。出现这个时间问题。例如淡季包括 11 月、12 月,然后是 1 月、2 月、3 月、4 月。如果用户选择日期范围 01.05.2011 到 ...2011 没有问题。我只是将月份和日期与 DATE_FORMAT(end, '%m/%d') 进行比较。但是如果他选择从 12 月到明年 1 月的范围,我要如何计算天数?

    请注意,Season_Dates 表中每年的 5 个条目不会让 8" 软盘在存储容量上持续好几年,更不用说 500 GiB 的巨型磁盘了。所以,到目前为止,最简单的事情是在 Season_Dates 表的 5 个新行中定义 2012 年的条目。这也允许您处理这样一个事实,即在 12 月,决定规则的权力将有所不同(12 月 20 日至 1 月 4 日将是“中” ,而不是“淡季”)。

    【讨论】:

    • 这太完美了:) 它奏效了。但另一个问题是,季节周期每年总是相同的。所以我想只比较几天和几个月。 2011 年并不重要。明年将仅使用 2011 年。出现这个时间问题。例如淡季包括 11 月、12 月,然后是 1 月、2 月、马克、4 月。如果用户选择日期范围 01.05.2011 到 ...2011 没有问题。我只是将月份和日期与 DATE_FORMAT(end, '%m/%d') 进行比较。但如果他选择从十二月到明年一月。我将如何计算天数。我不确定我是否可以很好地解释它。我很困惑
    • 我想在下一步中添加持续时间计算。这是我的想法。但首先我试图处理这个赛季的问题。
    • 感谢您的帮助,就这么简单:)我说我很困惑。
    猜你喜欢
    • 1970-01-01
    • 2017-10-05
    • 1970-01-01
    • 1970-01-01
    • 2014-03-29
    • 1970-01-01
    • 2014-01-07
    • 1970-01-01
    • 2013-08-14
    相关资源
    最近更新 更多