SQL-запрос для наложения даты / периода переопределяет даты по умолчанию - PullRequest
0 голосов
/ 28 октября 2009

Любые идеи по созданию запроса Sql Server (2008), который даст мне, скажем, "конкретные цены на дату для элемента на основе значения по умолчанию или переопределения, где существует" .

Таким образом, таблица по умолчанию может выглядеть следующим образом - столбцы Price, StartDate, EndDate (yyyy-M-d):

Default:  $10, 2010-1-1, 2010-2-1

Таблица переопределения выглядит так:

Override: $12, 2010-1-5, 2010-1-8

И запрос вернется:

Result:   $10, 2010-1-1, 2010-1-4
          $12, 2010-1-5, 2010-1-8
          $10, 2010-1-9, 2010-2-1

Возможно, я бы обернул это в сохраненный процесс или функцию и вызвал бы ее для определенного диапазона дат.

Ответы [ 4 ]

0 голосов
/ 17 мая 2016

Немного опоздал, но искал решение для аналогичной проблемы и не нашел ответа, поэтому попытался его найти.

Одним из решений может быть попытка настроить первый набор данных, чтобы он соответствовал второму набору, и на последнем шаге мы объединяем два набора, скорректированные данные с данными наложения.

Так что нам нужно настроить набор по умолчанию, и это потребует нескольких шагов работы.

Предположения: -

Периоды по умолчанию не накладываются друг на друга. Периоды перекрытия не перекрываются самим собой.

Данные перекрытия должны иметь разрывы, превышающие один день, чтобы гарантировать, что любые корректировки не повлияют на другой период наложения; мы не потеряем связанные значения наложения, так как вернем их на последнем шаге.

Для периодов по умолчанию, которые частично перекрываются (перекрытие больше или равно периоду по умолчанию)

Если дата начала периода перекрывается с любым периодом наложения, мы изменим дату начала на конец периода наложения плюс один день

Default:                                |<-------- P1 ------>|
Overlap:               |<-----------O1------>|
=================================================================
Output:                                       |<-----P1 ---->|         

Если дата окончания периода перекрывается с каким-либо периодом наложения, мы изменим дату окончания на начало периода наложения минус один день

Default:                |<-------- P1 ------>|
Overlap:                               |<-----------O1------>|
===================================================================
Output:                 |<--- P1 ---->|

Удалить любой период по умолчанию, если он полностью покрыт периодом перекрытия

Default:                 |<--- P1 --->|
Overlap:              |<--------O1------>|
===================================================================
Output:                       nothing

Для периодов по умолчанию, которые разбиты перекрывающимися периодами (перекрытие меньше, чем периоды) Получить первую часть (от начала периода до первого периода перекрытия) Получите промежуточные части, то есть от конца любого перекрывающегося периода до начала следующего перекрывающегося периода. Получить последнюю часть (от последнего перекрытия до конца периода)

Default:      |<---------------------------- P1 ------------------------------>|
Overlap:              |<---O1--->|         |<---O2-->|        |<--O3->|
===================================================================
Output:       |<-P1->|             |<-P1->|             |<-P1->|        |<-P1->|

Наконец, объединить данные и получить результат За притирочные типы, которые мы рассматриваем

Default:                |<---- P1 --->|
Overlap:                               |<-----------O1------>|
===================================================================
Output:                 |<---- P1 --->||<-----------O1------>|

Позволяет построить T-SQL

With [System] as (
       Select 1 [RowNum],cast('Jan 01,2017' as date) [StartDate],dateadd(day,-1,cast('Feb 01,2017' as date)) [EndDate],0500 [Value] union all
       Select 2 [RowNum],cast('Feb 01,2017' as date) [StartDate],dateadd(day,-1,cast('Mar 01,2017' as date)) [EndDate],0700 [Value] union all
       Select 3 [RowNum],cast('Mar 01,2017' as date) [StartDate],dateadd(day,-1,cast('Apr 01,2017' as date)) [EndDate],0900 [Value] union all
       Select 4 [RowNum],cast('Apr 01,2017' as date) [StartDate],dateadd(day,-1,cast('May 01,2017' as date)) [EndDate],0700 [Value] union all
       Select 5 [RowNum],cast('May 01,2017' as date) [StartDate],dateadd(day,-1,cast('Jun 01,2017' as date)) [EndDate],0900 [Value] union all
       Select 6 [RowNum],cast('Jun 01,2017' as date) [StartDate],dateadd(day,-1,cast('Jul 01,9999' as date)) [EndDate],1500 [Value]
),Overrides as (
       Select 1 [RowNum],cast('Feb 12,2017' as date) [StartDate],cast('Mar 25,2017' as date) [EndDate],1 [Value] union all
       Select 2 [RowNum],cast('Mar 28,2017' as date) [StartDate],cast('May 15,2017' as date) [EndDate],2 [Value] union all
       Select 3 [RowNum],cast('May 18,2017' as date) [StartDate],cast('May 20,2017' as date) [EndDate],3 [Value] union all
       Select 4 [RowNum],cast('Jun 05,2017' as date) [StartDate],cast('Jun 08,2017' as date) [EndDate],4 [Value] union all
       Select 5 [RowNum],cast('Jun 09,2017' as date) [StartDate],cast('Jun 16,2017' as date) [EndDate],5 [Value] union all
       Select 6 [RowNum],cast('Jun 17,2017' as date) [StartDate],cast('Jun 22,2017' as date) [EndDate],6 [Value] union all
       Select 7 [RowNum],cast('Jun 23,2017' as date) [StartDate],cast('Jun 27,2017' as date) [EndDate],7 [Value]
),PrepareOverridePeriods as (--if override periods have no gabs betwwen we need to merge them
    Select p1.StartDate, p1.EndDate
    from Overrides p1
              left join Overrides p2 on p1.StartDate = DATEADD(day,1,p2.EndDate)
    where p2.StartDate is null
    union all
    select p1.StartDate,p2.EndDate
    from PrepareOverridePeriods p1
    inner join Overrides p2 on p1.EndDate = DATEADD(day,-1,p2.StartDate)
),OverridePeriods as (
       select ROW_NUMBER() over (order by StartDate) [RowNum],StartDate,MAX(EndDate) as EndDate
       from PrepareOverridePeriods group by StartDate
),AdjustedPeriods as (
       select s.RowNum,'Adj.' [type]
       ,isnull(dateadd(day,1,ShiftRight.EndDate),s.StartDate) [StartDate]
       ,isnull(dateadd(day,-1,ShiftLeft.StartDate),s.EndDate) [EndDate]
       ,s.Value
       from System s
              left outer join OverridePeriods ShiftRight on s.StartDate between ShiftRight.StartDate and ShiftRight.EndDate
              left outer join OverridePeriods ShiftLeft on s.EndDate between ShiftLeft.StartDate and ShiftLeft.EndDate
              left outer join OverridePeriods RemovePeriod on s.StartDate between RemovePeriod.StartDate and RemovePeriod.EndDate and s.EndDate between RemovePeriod.StartDate and RemovePeriod.EndDate
       where RemovePeriod.StartDate is null
),SmallOverrides as ( --TODO: change SystemCalculated to AdjustSystemCalculatedPeriods
       select ROW_NUMBER() over (partition by s.RowNum order by o.StartDate ) [RowNum],
               o.RowNum [OverrideRowNum],o.StartDate [OverrideStartDate],o.EndDate [OverrideEndDate],s.Value [Value]
              ,s.RowNum [SystemRowNum],s.StartDate [SystemStartDate],s.EndDate [SystemEndDate]
       from OverridePeriods o
              inner join AdjustedPeriods s on o.StartDate between s.StartDate and s.EndDate and o.EndDate between s.StartDate and s.EndDate
)
--,FirstAndLastParts as (
--select [SystemRowNum],[type]
--     ,case when [type]='First' then min([SystemStartDate]) else dateadd(day,1,max(OverrideEndDate)) end [StartDate]
--     ,case when [type]='First' then dateadd(day,-1,min(OverrideStartDate)) else max([SystemEndDate]) end [EndDate]
--     ,min(Value) [Value]
--     from (select *,'First' [type]     from SmallOverrides o union all
--              select *,'Last' [type] from SmallOverrides o) data
--     group by [SystemRowNum],[type]
--)
,FirstParts as (
       select [SystemRowNum],'First' [type]
              ,min([SystemStartDate]) [StartDate]
              ,dateadd(day,-1,min(OverrideStartDate)) [EndDate]
              ,min(Value) [Value]
       from SmallOverrides
       group by [SystemRowNum]
),LastParts as (
       select [SystemRowNum],'Last' [type]
              ,dateadd(day,1,max(OverrideEndDate))  [StartDate]
              ,max([SystemEndDate])  [EndDate]
              ,min(Value) [Value]
              from SmallOverrides
       group by [SystemRowNum]
),IntermediatParts as (
select s.SystemRowNum [RowNum],'Inter.' [type]
       ,dateadd(day,1,s.OverrideEndDate) [StartDate]
       ,dateadd(day,-1,e.OverrideStartDate) [EndDate]
       ,s.Value
       from SmallOverrides s
       left outer join SmallOverrides e on e.SystemRowNum=s.SystemRowNum and s.RowNum+1=e.RowNum
       where e.RowNum is not null --remove the first and lasts
),AdjustedPeriodsFiltered as (--remove blocks that are broken to smaller pieces
       select s.*
              from AdjustedPeriods s
              left outer join OverridePeriods o on o.StartDate between s.StartDate and s.EndDate and o.EndDate between s.StartDate and s.EndDate
              where o.StartDate is null
),AllParts as (
       select * from IntermediatParts union all --order by SystemRowNum,OverrideStartDate
       select * from FirstParts union all
       select * from LastParts union all
       select * from AdjustedPeriodsFiltered
),Merged as (
       select [RowNum],[type] [Source],StartDate,EndDate,Value,'System' [RecordType] from AllParts
       union all
       select [RowNum],'override' [Source],StartDate,EndDate,Value,'Override' [RecordType] from Overrides
)
select * from Merged order by StartDate

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

With [System] as (
       Select 1 [RowNum],cast('Jan 01,2017' as date) [StartDate],dateadd(day,-1,cast('Feb 01,2017' as date)) [EndDate],0500 [Value] union all
       Select 2 [RowNum],cast('Feb 01,2017' as date) [StartDate],dateadd(day,-1,cast('Mar 01,2017' as date)) [EndDate],0700 [Value] union all
       Select 3 [RowNum],cast('Mar 01,2017' as date) [StartDate],dateadd(day,-1,cast('Apr 01,2017' as date)) [EndDate],0900 [Value] union all
       Select 4 [RowNum],cast('Apr 01,2017' as date) [StartDate],dateadd(day,-1,cast('May 01,2017' as date)) [EndDate],0700 [Value] union all
       Select 5 [RowNum],cast('May 01,2017' as date) [StartDate],dateadd(day,-1,cast('Jun 01,2017' as date)) [EndDate],0900 [Value] union all
       Select 6 [RowNum],cast('Jun 01,2017' as date) [StartDate],dateadd(day,-1,cast('Jul 01,9999' as date)) [EndDate],1500 [Value]
),Overrides as (
       Select 1 [RowNum],cast('Feb 12,2017' as date) [StartDate],cast('Mar 25,2017' as date) [EndDate],1 [Value] union all
       Select 2 [RowNum],cast('Mar 28,2017' as date) [StartDate],cast('May 15,2017' as date) [EndDate],2 [Value] union all
       Select 3 [RowNum],cast('May 18,2017' as date) [StartDate],cast('May 20,2017' as date) [EndDate],3 [Value] union all
       Select 4 [RowNum],cast('Jun 05,2017' as date) [StartDate],cast('Jun 08,2017' as date) [EndDate],4 [Value] union all
       Select 5 [RowNum],cast('Jun 09,2017' as date) [StartDate],cast('Jun 16,2017' as date) [EndDate],5 [Value] union all
       Select 6 [RowNum],cast('Jun 17,2017' as date) [StartDate],cast('Jun 22,2017' as date) [EndDate],6 [Value] union all
       Select 7 [RowNum],cast('Jun 23,2017' as date) [StartDate],cast('Jun 27,2017' as date) [EndDate],7 [Value]
),PrepareOverridePeriods as (--if override periods have no gabs between we need to merge them
    Select p1.StartDate, p1.EndDate
    from Overrides p1
              left join Overrides p2 on p1.StartDate = DATEADD(day,1,p2.EndDate)
    where p2.StartDate is null
    union all
    select p1.StartDate,p2.EndDate
    from PrepareOverridePeriods p1
    inner join Overrides p2 on p1.EndDate = DATEADD(day,-1,p2.StartDate)
),OverridePeriods as (
       select ROW_NUMBER() over (order by StartDate) [RowNum],StartDate,MAX(EndDate) as EndDate
       from PrepareOverridePeriods group by StartDate
)
,AllDates as (
       select ROW_NUMBER() over (order by [Date]) [RowNum],data.Date from (
       select dateadd(day,-1,OverridePeriods.StartDate) [Date] from OverridePeriods union all
       select dateadd(day,+1,OverridePeriods.EndDate) [Date] from OverridePeriods union all
       select StartDate [Date] from [System] union all
       select EndDate [Date] from [System] ) as data
)
,NewPeriods as (
select sy.RowNum, s.[Date] [StartDate],n.[Date] [EndDate] ,sy.Value
       from AllDates s
              left outer join AllDates n on n.RowNum=s.RowNum+1
              left outer join OverridePeriods o on s.[Date] between o.StartDate and o.EndDate and n.[Date] between o.StartDate and o.EndDate
              left outer join [System] sy on s.[Date] between sy.StartDate and sy.EndDate
       where
       s.RowNum % 2 =1 and o.StartDate is null--group it by 2 and remove overriden areas
)
,Merged2 as (
       select [RowNum], StartDate,EndDate,Value,'System' [RecordType] from NewPeriods
       union all
       select [RowNum], StartDate,EndDate,Value,'Override' [RecordType] from Overrides
)
select * from Merged2 order by StartDate

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

На последнем шаге мы можем попытаться объединить результаты, если значение совпадает, но я не думаю, что это было запрошено.

0 голосов
/ 28 октября 2009

Правильный дизайн будет иметь только одну таблицу. Вы не хотите переопределить таблицу вообще. Просто держите все в одной таблице - ограничено диапазоном дат. Запрос также становится намного проще.

Ваша структура таблицы становится

CREATE TABLE Rates
(ID INT NOT NULL,
 Rate Decimal NOT NULL,
 FromDate NOT NULL,
 ToDate   NOT NULL,
 CONSTRAINT PK_RATES (ID,FromDate,ToDate))

Тогда запрос становится

SELECT Rate FROM Rates WHERE ID = @ID AND FromDate = (SELECT MAX(FromDate) FROM Rates WHERE ID = @ID AND FromDate <= @Date) AND ToDate = (SELECT MIN(ToDate) FROM Rates WHERE ID = @ID AND ToDate >=@Date) 
0 голосов
/ 28 октября 2009

Я думаю, вам нужно будет сделать что-то вроде этого (при условии, что вы хотите, чтобы даты переопределения были отдельными, и что вы хотите избежать каких-либо процедурных действий):

  1. определить и заполнить таблицу утилит, перечисляя каждую отдельную дату, которая может иметь отношение к вам
  2. построить SELECT, который "помечает" каждую дату из этой служебной таблицы как либо i) принадлежащий датам переопределения, либо ii) принадлежащий датам по умолчанию
  3. сгруппировать результаты этого SELECT по дате и отметке
  4. присоединить эти результаты к соответствующей информации о цене
0 голосов
/ 28 октября 2009

Что-то вроде:

SELECT
   D.Price, ISNULL(O.StartDate, D.StartDate), ISNULL(O.EndDate, D.EndDate)
FROM
   Default D
   LEFT JOIN
   Override O ON D.Price= O.Price
...