1

给出下表和数据。

 create table prices
(productKey int
,PriceType char(10)
,BeginDate date
,EndDate date
,price decimal(18,2))

insert into prices(productKey, PriceType,BeginDate,EndDate, price)
values
(1,'LIST','1-1-2010','1-15-2010',10),
(1,'LIST','1-16-2010','10-15-2010',20),
(1,'DISCOUNT','1-10-2010','1-15-2010',-5),
(2,'LIST','2-1-2010','10-15-2010',30),
(2,'LIST','10-16-2010','1-1-9999',35),
(2,'DISCOUNT','2-10-2010','10-25-2010',-10),
(2,'LIST','1-1-2010','1-15-2010',10),
(3,'DISCOUNT','1-12-2010','1-1-9999',-5),
(3,'LIST','1-16-2010','1-1-9999',10)

我需要将记录插入到计算每个时间段的实际价格(列表折扣)的同一个表中。

例如,对于产品 1,我应该有以下“实际”记录

Begin     End      Price
1-1-2010  1-9-2010 10
1-10-2010  1-15-2010 5
1-16-2010  10-15-2010 20

我有点想出在标价范围内开始打折的任何事情,但我对其他任何事情都不知所措。

谢谢您的帮助

编辑

每个 ProductKey 可以有多个折扣,但折扣期不会重叠。所以你可以有一个 2010 年的,另一个 2012 年的,但不能有 2 个 2010 年的。

另外,如果有人能想出一个更好的标题,请这样做。在这一点上,我可怜的大脑完全受到挑战。

编辑2

它是 SQL 服务器 2008R2。我喜欢一个漂亮的基于集合的答案(或者让我朝那个方向开始的人),但也会对有效的光标解决方案感到满意。

4

3 回答 3

3

聪明的谜题。

您需要重建所有时间跨度。为此,我从价格范围中取出所有日期并重建可能的日期范围。

with alldates as (select d.*, ROW_NUMBER() over (partition by productkey order by thedate) as seqnum
                  from ((select productkey, BeginDate as thedate from prices)
                        union all
                        (select productkey, enddate as thedate from prices)
                       ) d
                 ),
     datepair as (select d1.productkey, d1.thedate as BeginDate, d2.thedate as EndDate
                  from alldates d1 left outer join 
                       alldates d2
                       on d1.seqnum = d2.seqnum - 1 and d1.productKey = d2.productKey
                 )
select dp.productkey, dp.BeginDate, dp.EndDate, SUM(p.price)
from datepair dp join
     prices p
     on dp.productkey = p.productkey and
         dp.BeginDate >= p.BeginDate and
        dp.EndDate <= p.EndDate
group by dp.productkey, dp.BeginDate, dp.EndDate
order by 1, 2, 3

我已经考虑了更多。上面的基本思路是正确的。基本思想是将时间维度分解为区间,其中列表和折扣在整个区间内是恒定的。问题是如何创建这些间隔,它们在 datepairs 别名中。

这些间隔只有几个规则:

  • 日期对间隔可以在任何时间段开始时开始。
  • 日期对间隔可以在任何时间段结束后的一天开始。
  • 日期对间隔可以在任何时间段结束时结束
  • 日期对间隔可以在任何时间段开始前一天结束

一旦我们有了间隔,就可以简单地加入该期间的适当标价和折扣。以下查询使用此逻辑:

with begindates as (select distinct productKey, thedate
                    from ((select productkey, BeginDate as thedate from prices)
                          union all
                          (select productkey, dateadd(d, 1, enddate) as thedate from prices)
                         ) d
                     ),
     enddates as (select distinct productKey, thedate
                  from ((select productkey, DATEADD(d, -1, begindate) as thedate from prices)
                        union all
                        (select productkey, enddate as thedate from prices)
                       ) d
                 ),
     datepair as (select *
                  from (select d1.productkey, d1.thedate as BeginDate,
                               MIN(d2.thedate) as EndDate
                        from begindates d1 left outer join 
                             enddates d2
                             on d1.productKey = d2.productKey and d1.thedate < d2.thedate
                        group by d1.productkey, d1.thedate
                       ) t
                  where BeginDate <> EndDate
                 )
select dp.productkey, dp.BeginDate, dp.EndDate, SUM(p.price)
from datepair dp join
     prices p
     on dp.productkey = p.productkey and
         dp.BeginDate >= p.BeginDate and
        dp.EndDate <= p.EndDate
group by dp.productkey, dp.BeginDate, dp.EndDate
order by 1, 2, 3  
于 2012-06-26T20:16:57.843 回答
0

我相信这应该给你你正在寻找的东西。关键是计算单个产品的所有唯一日期范围而不重叠。

-- Get all end dates
SELECT ROW_NUMBER() OVER (PARTITION BY ProductKey ORDER BY EndDate) RowNum, ProductKey, EndDate 
INTO #EndDates 
FROM (SELECT ProductKey, EndDate
    FROM prices
    UNION
    SELECT  ProductKey, DATEADD(d, -1, BeginDate) AS EndDate
    FROM prices) endDates
ORDER BY EndDate

-- Get all unique date ranges with no overlap
SELECT a.ProductKey, DATEADD(d, 1, a.EndDate) BeginDate, b.EndDate
INTO #DateRange
FROM #EndDates a
    INNER JOIN #EndDates b
        ON a.RowNum = b.RowNum - 1
        AND a.ProductKey = b.ProductKey
ORDER BY productkey, enddate

-- Get actual price
SELECT d.ProductKey, d.BeginDate, d.EndDate, SUM(Price) ActualPrice 
FROM prices p
    INNER JOIN #DateRange d
        ON p.ProductKey = d.ProductKey
        AND p.BeginDate <= d.EndDate
        AND p.EndDate >= d.BeginDate
GROUP BY d.ProductKey, d.BeginDate, d.EndDate
ORDER BY d.ProductKey, d.BeginDate, d.EndDate

-- Clean up
DROP TABLE #EndDates
DROP TABLE #DateRange
于 2012-06-26T21:02:01.803 回答
0

为了让这更有趣,我添加了另一个与之前的部分价格相同的部分,以确保我将它们分开:

INSERT INTO prices(productKey, PriceType,BeginDate,EndDate, price)
VALUES (1, 'DISCOUNT', '5-2-2010', '5-8-2010', -15)

我还有一个名为的表tblDates,由以下内容填充:

INSERT dbo.tblDates (date1)
SELECT TOP(65536) ROW_NUMBER()OVER(ORDER BY v1.number)-1
FROM master.dbo.spt_values v1 CROSS APPLY master.dbo.spt_values v2 WHERE v1.type='p' and v2.type='p'
GO

我在这里给出的脚本不需要那个。但是拥有它不会影响速度,也不会真正占用那么多空间。这是我的答案:

DECLARE @InitialBeginDate DATE
    , @FinalEndDate DATE;

SELECT
    @InitialBeginDate = MIN(BeginDate)
    , @FinalEndDate = MAX(EndDate)
FROM prices
WHERE productKey = @ProductKey


CREATE TABLE #Dates
(
    DateValue DATE NOT NULL
)

INSERT #Dates (DateValue)
SELECT
    DATEADD(DAY, V.number, @InitialBeginDate)
FROM master..spt_values V
WHERE V.[type] = 'P'
    AND V.number BETWEEN 0 AND DATEDIFF(DAY, @InitialBeginDate, @FinalEndDate)

;WITH MergedDays AS
(
    SELECT
        ListedDates.DateValue
        , SUM(P.price) PriceOnDate
        , DATEDIFF(DAY, @InitialBeginDate, ListedDates.DateValue) - DENSE_RANK() OVER(PARTITION BY SUM(P.price) ORDER BY ListedDates.DateValue, SUM(P.price)) DateGroup
    FROM #Dates ListedDates
    INNER JOIN prices P
        ON P.BeginDate <= ListedDates.DateValue
        AND P.EndDate >= ListedDates.DateValue
        AND P.productKey = @ProductKey
    GROUP BY
        ListedDates.DateValue
)
SELECT
    MIN(DateValue) AS SegmentBeginDate
    , MAX(DateValue) AS SegmentEndDate
    , MAX(PriceOnDate) AS SegmentPrice -- This is just to collapse it, it'll be the same for all records.
FROM MergedDays
GROUP BY DateGroup
ORDER BY SegmentBeginDate

DROP TABLE #Dates

There are a couple other answers now, so this is just another way of doing things; there are many.

于 2012-06-26T21:33:28.597 回答