给出以下表格和数据。
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)
我需要在同一个表中插入记录,以计算每个时间段的实际价格(列表折扣)。
e.g。对于产品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年的另一个,但2010年不会是2个。
另外,如果有人能想出更好的头衔,请这样做。我的可怜的大脑在这一点上完全受到挑战。
EDIT2
这是SQL Server 2008R2。我喜欢一个漂亮的基于集合的答案(或者让我从这个方向开始的人),但对于有效的光标解决方案同样满意。
答案 0 :(得分: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
我已经考虑过这个了。上面的基本想法是正确的。基本思想是将时间维度分解为列表和折扣在整个时间间隔内保持不变的时间间隔。问题是如何创建这些间隔,这些间隔位于日期对别名中。
这些间隔只有一些规则:
一旦我们有了间隔,加入适当的定价和该期间的折扣是一件简单的事情。以下查询使用此逻辑:
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
答案 1 :(得分: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
答案 2 :(得分: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
现在还有其他几个答案,所以这只是另一种做事方式;有很多。