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 の場合、次の「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

定価範囲内で割引が開始されるものについては、ある程度把握していますが、それ以外については途方に暮れています。

助けてくれてありがとう

編集

プロダクト キーごとに複数の割引が適用される場合がありますが、割引期間が重複することはありません。したがって、2010 年に 1 つ、2012 年にもう 1 つ作成できますが、2010 年に 2 つ作成することはできません。

また、もっといいタイトルを思いつく人がいたらお願いします。私のかわいそうな脳は、この時点で完全に挑戦されています.

EDIT2

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 エイリアスにあるこれらの間隔を作成する方法です。

これらの間隔には、いくつかのルールがあります。

  • 日付ペアの間隔は、任意の期間の開始時に開始できます。
  • 日付ペアの間隔は、任意の期間が終了した 1 日後に開始できます。
  • 任意の期間が終了すると、日付ペアの間隔を終了できます
  • 日付ペアの間隔は、期間が始まる 1 日前に終了できます

間隔がわかれば、その期間の適切なリスト価格と割引に参加するのは簡単なことです。次のクエリでは、このロジックを使用しています。

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

現在、他にもいくつかの答えがあるため、これは別の方法です。沢山あります。

于 2012-06-26T21:33:28.597 に答える