【问题标题】:Working with date ranges (Classic ASP and SQL)使用日期范围(经典 ASP 和 SQL)
【发布时间】:2015-12-03 18:24:22
【问题描述】:

我必须实施一个解决方案,其中两个日期范围可以相互重叠。在重叠的日期内,我必须计算彼此重叠的天数。一旦我知道了重叠的天数,我就可以根据每天附加的价格计算出一个总数字。

一个场景是这样的

客户正在预订酒店

客户预订日期 - 2011 年 2 月 17 日至 2011 年 2 月 26 日

正常价格(全年) - 01/01/2011 - 31/12/2011(每天价格:$30.00)

特别优惠 1 日期 - 01/01/2011 至 19/02/2011(每日价格:$20.00)

特别优惠 2 个日期 - 2011 年 2 月 17 日至 2011 年 2 月 24 日(每天价格:10.00 美元)

在上述场景中,建议的算法应该计算出日期范围重叠的最便宜的报价,并计算出预订的价格。如果没有特别优惠,则使用正常价格。

因此,在前两天,系统应该从“特价 1”中获取价格,因为它是最便宜的可用价格。接下来的 5 天应该是“特价 2 价格”,接下来的 2 天应该是正常价格。

我很高兴看到 SQL(使用 MS-SQL Server)或代码库答案以获得不同的视图。

我希望问题很清楚,并期待看到答案。

在此先感谢

【问题讨论】:

  • 请告诉我们您使用的是什么 rdbms。 SQLServer?
  • 你有code或sql了吗?
  • @Matt-如上所述,我已经制定了获取每个“假日财产”现有优惠的代码,它正在计算我正在努力解决的日期范围和优惠的不同变体的总数. :-)
  • 在我看来,特价 2 期间完全包含在客户预订期间,因此我无法将您的预期结果与实际数据相吻合。另外,请确认您认为从 2011 年 2 月 17 日到 2011 年 2 月 24 日期间涵盖的天数:6、7 或 8 天?
  • @onedaywhen - 抱歉我的错误,我纯粹编造这些日期来解释解决方案。我会说,如果您将“特别优惠 2 期间”从 2011 年 2 月 14 日更改为 2011 年 2 月 19 日。这将有助于更清楚地了解重叠 bti。

标签: asp.net sql asp-classic vbscript


【解决方案1】:

使用使用auxiliary calendar table 的标准技巧,这只是一个简单的连接和分组情况,以每天获得最优惠的价格:

SELECT C.dt, MIN(price) AS best_price
  FROM Prices P
       INNER JOIN Calendar C
          ON C.dt >= P.price_start_date 
             AND C.dt < P.price_end_date 
       INNER JOIN CustomerBooking B
          ON C.dt >= B.booking_start_date 
             AND C.dt < B.booking_end_date
 GROUP 
    BY C.dt;

与上述相同的查询,包括使用 CTE 的示例数据:

WITH Prices (price_start_date, price_end_date, narrative, price)
     AS 
     (
      SELECT CAST(start_date AS Date), CAST(end_date AS Date), narrative, price
        FROM (
              VALUES ('2011-01-01T00:00:00', '2011-12-31T00:00:00', 'Normal price', 30), 
                     ('2011-01-01T00:00:00', '2011-02-21T00:00:00', 'Special Offer 1', 20),
                     ('2011-02-19T00:00:00', '2011-02-24T00:00:00', 'Special Offer 2', 10)
             ) AS T (start_date, end_date, narrative, price)
     ), 
     CustomerBooking (booking_start_date, booking_end_date)
     AS 
     (
      SELECT CAST(start_date AS Date), CAST(end_date AS Date)
        FROM (
              VALUES ('2011-02-17T00:00:00', '2011-02-26T00:00:00')
             ) AS T (start_date, end_date)
     )     
SELECT C.dt, MIN(price) AS best_price
  FROM Prices P
       INNER JOIN Calendar C
          ON C.dt >= P.price_start_date 
             AND C.dt < P.price_end_date 
       INNER JOIN CustomerBooking B
          ON C.dt >= B.booking_start_date 
             AND C.dt < B.booking_end_date
 GROUP 
    BY C.dt;

【讨论】:

    【解决方案2】:

    让我们假设您应该每天应用最低价格。

    create function price ( @fromDate date, @toDate date) returns money
    as
    begin
     declare @iterator_day date
     declare @total money
     set @total = 0
     set @iterator_day = @fromDate
     WHILE @iterator_day < = @toDate
     begin
        select @total = @total + min( price )
        from offers
        where @iterator_day  between offers.fromFay and offers.toDay
        set @iterator_day = DATEADD (day , 1 , @iterator_day )
     end
     return @total
    end
    

    然后您可以在查询中调用函数:

    select 
       b.fromDay, b.toDay, dbo.price( b.fromDay, b.toDay )
    from 
       booking b
    

    【讨论】:

    • 循环确实是一个答案,但在 SQL 等基于集合的语言中,我会寻找替代方案。
    • 非常感谢所有贡献,因为没有空间来重构模型。根据性能,目前系统最多只能有 3 个特价商品。因此,性能下降并不显着。
    【解决方案3】:

    我只使用过 ASP.net 4.0,但我可以提供一些 SQL 来为您提供给定日期的价格:

    SELECT     ISNULL(MIN(PricePerDay), 0) AS MinPricePerDay
    FROM       Offers
    WHERE     (StartDate <= '18/2/11') AND (EndDate >= '18/2/11')
    

    从您的应用程序中,您可以将查询构建为如下所示:

    SELECT     ISNULL(MIN(PricePerDay), 0) AS MinPricePerDay
    FROM       Offers
    WHERE     (StartDate <= '17/2/11') AND (EndDate >= '17/2/11');
    SELECT     ISNULL(MIN(PricePerDay), 0) AS MinPricePerDay
    FROM       Offers
    WHERE     (StartDate <= '18/2/11') AND (EndDate >= '18/2/11');
    SELECT     ISNULL(MIN(PricePerDay), 0) AS MinPricePerDay
    FROM       Offers
    WHERE     (StartDate <= '19/2/11') AND (EndDate >= '19/2/11');
    

    这将返回一个表格数据集,其中包含该日期最低价格的单个值(与您的查询顺序相同)

    对于Stored Procedure 来说听起来不错...

    【讨论】:

      【解决方案4】:

      您的问题是您有多个重叠的时间段。您要么需要稍微限制问题,要么稍微重塑数据。 (以获得理想的性能。)

      选项 1 - 约束

      • “正常”价格数据集 - 不会相互重叠
      • “特价”数据集 - 也不会相互重叠
      • 每个可预订日期都有一个“正常”价格
      • 每个可预订日期都有一个“特价”(即使它为 NULL 表示“没有特价”)

      最后一个约束是最奇怪的。但需要使简单的加入工作。在比较日期范围时,如果两组范围之间没有间隙并且内部没有重叠,则很多更容易形成查询。

      这意味着您现在应该能够通过几个连接来解决它...

      SELECT
        CASE WHEN [sp].started > [np].started THEN [sp].started ELSE [np].started END AS [started]
        CASE WHEN [sp].expired < [np].expired THEN [sp].expired ELSE [np].expired END AS [expired]
        CASE WHEN [sp].price   < [np].price   THEN [sp].price   ELSE [np].price   END AS [price]
      FROM
        normal_prices          AS [np]
      LEFT JOIN
        special_prices         AS [sp]
          ON  [sp].started <  [np].expired
          AND [sp].expired >  [np].started
          AND [sp].started >= (SELECT ISNULL(MAX(started),0) FROM special_prices WHERE started <= [np].started)
          --  The third condition is an optimisation for large data-sets.
      WHERE
            [np].started < @expired
        AND [np].expired > @started
      
      -- Note: Inclusive StartDates, Exlusive EndDate
      --       For example, "all of Jan" would be "2011-01-01" to "2011-02-01"
      


      选项 2 - 重新建模

      在我的经验中,这通常是最快的;您增加了正在使用的空间量,并获得了更简单更快的查询...

      Table Of Prices, stored by DAY rather than period...  
      - calendar_date  
      - price_code  
      - price  
      
      SELECT
        calendar_date,
        MIN(price)
      FROM
        prices
      WHERE
          calendar_date >= @started
      AND calendar_date <  @expired
      

      或者,如果您也需要 price_code...

      WITH
        ordered_prices AS
      (
        SELECT
          ROW_NUMBER() OVER (PARTITION BY calendar_date ORDER BY price ASC, price_code) AS price_rank,
          *
        FROM
          prices
      )
      SELECT
        calendar_date,
        price_code,
        price
      FROM
        ordered_prices
      WHERE
          calendar_date >= @started
      AND calendar_date <  @expired
      

      【讨论】:

      • 我喜欢选项 2,但与其改造不如使用 calendar table 即时将期间费率转换为日费率。
      • 同意。不过,这确实意味着许多记录会加入基于期间的数据。由于此类连接 (start &lt;= x AND end &gt; x) 在大型数据集中可能代价高昂(由于仅通过 &lt;= 过滤索引列),因此重构可以产生非常显着的好处。这一切都取决于end &gt; x 必须扫描多少条记录才能获得您感兴趣的记录。
      • 现在添加了我自己的答案来演示这种方法。我同意你们对性能的看法,我个人会接受改造;)
      猜你喜欢
      • 1970-01-01
      • 2021-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多