【问题标题】:MIN or MAX Value for Date Ranges - Determining lowest price for a given date range based on Product ID, Price, and Date Ranges日期范围的最小值或最大值 - 根据产品 ID、价格和日期范围确定给定日期范围的最低价格
【发布时间】:2017-08-02 18:33:10
【问题描述】:

我真的希望你们中的一些人喜欢挑战。我有一张产品 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 和价格,“触摸”(即 Id #3)合并为一个周期。

  2. 重叠的日期周期(即 ID #4)合并为一个周期。

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

  4. 有间隔且价格相同的日期范围不会合并,而是单独的行(即 ID #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 |
+------+-------+-------------------------+-------------------------+

【问题讨论】:

  • 使用适当的软件(MySQL、Oracle、DB2...)和版本标记数据库问题很有帮助,例如sql-server-2014。语法和功能的差异通常会影响答案。请注意,tsql 缩小了选择范围,但没有指定数据库。
  • @HABO 已添加,它是 SQL Server 2012。

标签: sql tsql sql-server-2012


【解决方案1】:

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

之后,您可以通过查看具有相同产品 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

如果您没有日期表,那么您可以轻松创建一个。网络上有大量这样的例子。

我希望这会有所帮助!

【讨论】:

  • 我有一个可用的!这就是我开始走下去的路……会试一试。
  • 太棒了。嗯 - 它不像我希望的那样优雅,但似乎确实可以完成这项工作。
  • 非常棒,非常棒。像魅力一样工作,运行速度也非常快!谢谢!
  • 啊...表现时髦从来都不是好事!稍后我会看看发生了什么:-)
  • 没关系,我的错!我没有将您的最终查询放入我自己的 CTE 以在我的实现中使用它,因此它在最后一个查询中省略了 WHERE IsStartDate = 1。再次感谢!
【解决方案2】:

您可以将一个时期的开始定义为不重叠的时期。这很棘手,但可以使用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% 确定这是否有效。我只是想为此使用累积的最大结束日期。我很确定它涵盖了所有重叠的情况,但我可能遗漏了一些东西。

【讨论】:

  • 它似乎做错了两件事:1.它正在合并它们之间有差距的日期期间(相同的价格,相同的ID,但一个日期范围和另一个日期范围之间有一个月的差距)并且它是不合并像#1 期望结果那样“接触”的日期范围。将进行更详细的编辑...很荣幸得到该人本人的回答。
  • @justiceorjustus 。 . .逻辑是倒退的。 &gt;= 应该是 &lt;
  • 查看我刚刚发布的示例...这会将我的所有日​​期范围以最低价格合并为一个,但该价格在结果的整个范围内都不是有效的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多