日期范围的最小值或最大值 - 根据产品ID,价格和日期范围确定给定日期范围的最低价格

时间:2017-08-02 18:33:11

标签: sql tsql sql-server-2012

我真的希望你们中的一些人喜欢挑战。我有一张产品ID,价格和日期范围表,以了解这些价格何时生效。

+----+-------+---------------------+---------------------+
| Id | Price |      StartDate      |       EndDate       |
+----+-------+---------------------+---------------------+
|  1 |    19 | 2016-12-01 00:00:00 | 2017-12-01 23:59:59 |
|  1 |    18 | 2017-01-01 00:00:00 | 2018-01-12 23:59:59 |
|  1 |    17 | 2017-02-03 00:00:00 | 2017-03-03 23:59:59 |
|  1 |    16 | 2018-01-01 00:00:00 | 2018-03-02 23:59:59 |
|  2 |    15 | 2017-01-01 00:00:00 | 2017-03-05 23:59:59 |
|  2 |    15 | 2017-03-06 00:00:00 | 2017-03-31 23:59:59 |
|  2 |    30 | 2017-04-01 00:00:00 | 2017-05-03 23:59:59 |
|  3 |    12 | 2017-01-01 00:00:00 | 2017-01-31 23:59:59 |
|  3 |    12 | 2017-02-01 00:00:00 | 2017-02-28 23:59:59 |
|  4 |    14 | 2017-01-01 00:00:00 | 2017-04-05 23:59:59 |
|  4 |    14 | 2017-04-01 00:00:00 | 2017-04-30 23:59:59 |
|  4 |    12 | 2017-04-15 00:00:00 | 2017-05-30 23:59:59 |
|  5 |    20 | 2017-01-01 00:00:00 | 2017-01-31 23:59:59 |
|  5 |    20 | 2017-03-01 00:00:00 | 2017-03-31 23:59:59 |
|  6 |    15 | 2017-01-01 00:00:00 | 2017-01-31 23:59:59 |
|  6 |    15 | 2017-02-01 00:00:00 | 2017-02-28 23:59:59 |
|  6 |    15 | 2017-04-01 00:00:00 | 2017-04-30 23:59:59 |
+----+-------+---------------------+---------------------+

SQLFiddle:http://sqlfiddle.com/#!6/39288/1

我需要以下列格式获取它:

  1. 日期与“触摸”(即Id#3)合并为一个期间的ID和价格相同。

  2. 重叠的日期(即Id#4)合并为一个句号。

  3. 显示每种产品的最低价格以及在什么范围内。

  4. 有差距和相同价格的日期范围不合并且是单独的行(即Id#5)。

  5. 结果应为:

    +----+-------+---------------------+---------------------+
    | Id | Price |      StartDate      |       EndDate       |
    +----+-------+---------------------+---------------------+
    |  1 |    19 | 2016-12-01 00:00:00 | 2016-12-31 23:59:59 |
    |  1 |    18 | 2017-01-01 00:00:00 | 2017-02-02 23:59:59 |
    |  1 |    17 | 2017-02-03 00:00:00 | 2017-03-03 23:59:59 |
    |  1 |    19 | 2017-03-04 00:00:00 | 2017-12-01 23:59:59 |
    |  1 |    18 | 2017-12-02 00:00:00 | 2017-12-31 23:59:59 |
    |  1 |    16 | 2018-01-01 00:00:00 | 2018-03-02 23:59:59 |
    |  2 |    15 | 2017-01-01 00:00:00 | 2017-03-31 23:59:59 |
    |  2 |    30 | 2017-04-01 00:00:00 | 2017-05-03 23:59:59 |
    |  3 |    12 | 2017-01-01 00:00:00 | 2017-02-28 23:59:59 |
    |  4 |    14 | 2017-01-01 00:00:00 | 2017-04-14 23:59:59 |
    |  4 |    12 | 2017-04-15 00:00:00 | 2017-05-30 23:59:59 |
    |  5 |    20 | 2017-01-01 00:00:00 | 2017-01-31 23:59:59 |
    |  5 |    20 | 2017-03-01 00:00:00 | 2017-03-31 23:59:59 |
    |  6 |    15 | 2017-01-01 00:00:00 | 2017-02-28 23:59:59 |
    |  6 |    15 | 2017-04-01 00:00:00 | 2017-04-30 23:59:59 |
    +----+-------+---------------------+---------------------+
    

    总的来说,它基本上决定了两个日期之间的最佳价格。

    我过去曾使用过这个表,并且能够用C#解决它,但这次我需要一个纯粹的TSQL方法。

    我已经进行了一些深度嵌套的CTE,并且因为得不到应有的结果而失去理智。提前感谢任何可以提供协助的人。

    编辑:我甚至搞砸了想要的结果,因为这太令人困惑了。固定(我认为)。

    编辑2:示例:

    +------+-------+-------------------------+-------------------------+
    |  Id  | Price |        StartDate        |         EndDate         |
    +------+-------+-------------------------+-------------------------+
    | 8611 | 31.98 | 2017-06-06 00:00:00.000 | 2017-09-24 23:59:59.000 |
    | 8611 | 31.98 | 2017-09-25 00:00:00.000 | 2017-12-31 23:59:59.000 |
    | 8611 | 28.78 | 2017-07-31 00:00:00.000 | 2017-09-30 23:59:59.000 |
    | 8611 | 28.78 | 2017-10-30 00:00:00.000 | 2017-12-31 23:59:59.000 |
    +------+-------+-------------------------+-------------------------+
    

    @ GordonLinoff的结果:

    +------+-------+-------------------------+-------------------------+
    |  Id  | Price |        StartDate        |         EndDate         |
    +------+-------+-------------------------+-------------------------+
    | 8611 | 28.78 | 2017-06-06 00:00:00.000 | 2017-12-31 23:59:59.000 |
    +------+-------+-------------------------+-------------------------+
    

    结果应该是:

    +------+-------+-------------------------+-------------------------+
    |  Id  | Price |        StartDate        |         EndDate         |
    +------+-------+-------------------------+-------------------------+
    | 8611 | 31.98 | 2017-06-06 00:00:00.000 | 2017-07-30 23:59:59.000 |
    | 8611 | 28.78 | 2017-07-31 00:00:00.000 | 2017-09-30 23:59:59.000 |
    | 8611 | 31.98 | 2017-10-01 00:00:00.000 | 2017-10-29 23:59:59.000 |
    | 8611 | 28.78 | 2017-10-30 00:00:00.000 | 2017-12-31 23:59:59.000 |
    +------+-------+-------------------------+-------------------------+
    

2 个答案:

答案 0 :(得分:2)

您有日历/日期表吗?如果是这样,那么您可以使用日期表来帮助您获得表格中各期间每个日期的最低价格。

之后,您可以通过查看具有相同产品ID的下一个和上一个记录来获取每个期间的开始和结束日期。您可以使用LAG和LEAD功能执行此操作。这为您提供了每个所需组的外部边界。

从那里开始,只需要摆弄一下即可获得最终结果。我在下面提供了一个示例,它可以为您提供所需的结果。

--Get the best price per date for each product
WITH BestPricePerDate AS (
    SELECT 
        Id,
        MIN(Price) Price,
        c.[Date]
    FROM [YourTable] yt
        INNER JOIN dbo.Calendar c
            ON c.[Date] BETWEEN yt.StartDate AND yt.EndDate
    GROUP BY Id, [Date]
),
--Check whether the date is the start or the end of a period
PeriodsMarkedPerId AS(
    SELECT 
        Id,
        Price,
        [Date],
        CASE WHEN 
            ISNULL(LAG(Price,1) OVER (PARTITION BY Id ORDER BY [Date]),-1) <> Price 
            OR ISNULL(LAG([Date],1) OVER (PARTITION BY Id ORDER BY [Date]),'1999-01-01') <> DATEADD(DAY,-1,[Date]) THEN 1 ELSE 0 END IsStartDate,
        CASE WHEN 
            ISNULL(LEAD(Price,1) OVER (PARTITION BY Id ORDER BY [Date]),-1) <> Price 
            OR ISNULL(LEAD([Date],1) OVER (PARTITION BY Id ORDER BY [Date]),'1999-01-01') <> DATEADD(DAY,1,[Date]) THEN 1 ELSE 0 END IsEndDate
    FROM BestPricePerDate
),
--Keep only the start and end date records
PeriodStartAndEndDates AS(
    SELECT 
        Id, 
        Price,
        [Date],
        IsStartDate,
        IsEndDate
    FROM PeriodsMarkedPerId
    WHERE IsStartDate = 1 OR IsEndDate = 1
),
--Move StartDate and EndDate to one record
StartAndEndDatesOnSameRow AS(
    SELECT 
        Id, 
        Price, 
        [Date] AS StartDate,
        LEAD([Date],1) OVER (ORDER BY Id, [Date]) AS EndDate,
        IsStartDate
    FROM PeriodStartAndEndDates
)
--Get the resulting periods
SELECT Id, Price, StartDate, EndDate 
FROM StartAndEndDatesOnSameRow
WHERE IsStartDate = 1
ORDER BY Id, StartDate

如果您没有日期表,则可以轻松创建日期表。网上有很多这方面的例子。

我希望这有帮助!

答案 1 :(得分:1)

您可以将句点的开头定义为不重叠的句子。这很棘手,但可以使用exists或除当前行之外的结束日期的累积最大值来完成。

然后,每个非重叠是组的开头。该组可用于聚合:

select id, min(startDate) as startDate, max(endDate) as endDate, min(price) as price
from (select t.*,
             sum(case when prev_endDate < dateadd(second, -1, startDate)
                      then 1 else 0
                 end) over (partition by id order by startdate) as grp
      from (select t.*,
                   max(endDate) over (partition by id
                                      order by startdate
                                      rows between unbounded preceding and 1 preceding
                                     ) as prev_endDate
            from t
           ) t
     ) t
group by id, grp;

我并非100%确定这是有效的。我只想到使用累计最大结束日期。我很确定它涵盖了所有重叠的案例,但我可能错过了一些东西。