2012-06-26 5 views
1

Учитывая следующую таблицу и данные.Даты и времени с SQL

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, я должен иметь следующие «Actual» записи

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

я вроде есть разобрался для чего, где скидка начинается в пролете список цен, но я в растерянности что-нибудь еще.

Спасибо за помощь

EDIT

Там может быть несколько скидок на ProductKey, но периоды скидки не будут пересекаться. Таким образом, у вас может быть один на 2010 год, а другой на 2012 год, но не на 2010 год.

Кроме того, если кто-то может придумать лучший заголовок, сделайте это. На данный момент мой плохой мозг полностью оспаривается.

EDIT2

Это SQL сервер 2008R2. Мне понравился бы прекрасный ответ на основе набора (или кто-то, кто дал бы мне начало в этом направлении), но он будет так же доволен решением курсора, которое работает.

+4

Не могу сказать, насколько мне не нравится видеть «вызов» в названии. – Oded

+0

@Oded Задача состоит в том, что это должно быть сделано в пятьдесят символов или меньше. –

+0

Откуда «1-9-2010»? –

ответ

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.

Эти интервалы имеют лишь несколько правил:

  • интервал datepair может начаться, когда начинается какой-то период времени.
  • Интервал дат может начинаться один день после окончания любого периода времени.
  • интервал datepair может закончиться, когда какой-то период времени заканчивается
  • datepair интервал может закончиться за один день до какой-то период времени начинается

После того как мы интервалы, это простой вопрос, чтобы присоединиться к соответствующей цены и скидки за этот период. Следующий запрос использует эту логику:

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 
+0

Это может сделать это. Мне нужно выяснить даты окончания. Даты начала и окончания не могут быть одинаковыми. –

+0

Очень впечатляет! – Sean

+0

Хм ... это, похоже, не дает правильных результатов. Вы запустили его против данных образца? –

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 
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 

Есть еще несколько других ответов, так что это всего лишь еще один способ сделать вещи; Здесь очень много.

Смежные вопросы