Возврат месяца / года между двумя датами в T-SQL - PullRequest
2 голосов
/ 23 января 2012

У меня есть записи с начальной и конечной датами в одной таблице.

Мне нужно развернуть это так, чтобы у меня были годы на Х и месяцы на Y ...

         1     2     3    4    5    6    7    8    9    10    11    12
2000   123   123   123  123  123  123  123  123  123   123   123   123
2001   123   123   123  123  123  123  123  123  123   123   123   123
2002   123   123   123  123  123  123  123  123  123   123   123   123
2003   123   123   123  123  123  123  123  123  123   123   123   123

... с количеством элементов, которые можно считать «активными», поскольку в позиции в матрице (год / месяц) месяц / дата попадает в начало / конец записейДата.

Я управлял PIVOT на основе одной даты для другой задачи, но по сути это PIVOT для диапазона дат, включая каждую дату между датами начала / окончания!

Есть ли что-то, о чем я не знаю в T-SQL, что могло бы включить это?

Ответы [ 4 ]

3 голосов
/ 23 января 2012

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

select y, [1] as January,
            [2] as February, 
            [3] as March,
            [4] as April, 
            [5] as May, 
            [6] as June, 
            [7] as July, 
            [8] as August, 
            [9] as September, 
            [10] as October, 
            [11] as November, 
[12] as December 
from  (select ord_num, 
        DATEPART(year,ord_date)as y,
        DATEPART(month, ord_date) as m from sales) as p
 pivot (count(ord_num) for m in ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12])) as pvt
 order by pvt.y
1 голос
/ 24 января 2012

Я думаю, я понял это. Это, вероятно, не красиво, но немного времени, и, прочитав идею Бет, у меня возникла искра.

Я придумал:

create procedure sp_MIS_mySP 
    @fromDate datetime=null,
    @toDate datetime=null,
    @debug int=0
as 

-- get overall date range
declare @firstDate datetime
declare @lastDate datetime

select top 1 @firstDate=Start from MIS_Policies where (@fromDate is null or Start>=@fromDate) order by Start
select top 1 @lastDate=End from MIS_Policies where (@toDate is null or End<=@toDate) order by End desc

if @debug=1
begin
    print 'Parameters:'
    if (@fromDate is null) print '   @fromDate=NULL' else print '   @fromDate='''+rtrim(convert(varchar,@fromDate))+''''
    if (@toDate is null) print '   @toDate=NULL' else print '   @toDate='''+rtrim(convert(varchar,@toDate))+''''
    print 'Evaluated:'
    print '   @firstDate='''+rtrim(convert(varchar,@firstDate))+''''
    print '   @lastDate='''+rtrim(convert(varchar,@lastDate))+''''
end

-- step through date range by month
--   inserting count of active contracts at that point
declare @dateTImePtr datetime
set @dateTImePtr=dateadd(d,-(datepart(d,@firstDate)-1),@firstDate)

declare @startOfMonthPtr datetime
declare @endOfMonthPtr datetime

create table #yearMonthActiveBreakdown (
    [year] int,
    [month] int,
    [count] int)

while @dateTImePtr<=@lastDate
begin
    set @startOfMonthPtr=@dateTImePtr
    set @endOfMonthPtr=DATEADD(m,1,@startOfMonthPtr)
    set @endOfMonthPtr=DATEADD(d,-1,@endOfMonthPtr)

    if @debug=1
    begin
        print '@dateTimePtr='''+rtrim(convert(varchar,@dateTimePtr))+''' (@startOfMonthPtr='''+rtrim(convert(varchar,@startofMonthPtr))+''', @endOfMonthPtr='''+rtrim(convert(varchar,@endOfMonthPtr))+''')'
    end

    -- insert row for year/month aggregating by count of items
    insert into #yearMonthActiveBreakdown ([year],[month],[count]) 
        select year(@dateTImePtr),
            MONTH(@dateTImePtr),
            COUNT(ContractNumber) 
            from MIS_Policies 
            where 
                Start<=@endOfMonthPtr and End>=@startOfMonthPtr and Status in (0,3,4,5)     

    set @dateTImePtr=DATEADD(m,1,@dateTimePtr)

end

if @debug = 1
begin
    -- pre-pivot
    select * from #yearMonthActiveBreakdown
end

select [YEAR],[1] as [Exposure_1],[2] as [Exposure_2],[3] as [Exposure_3],[4] as [Exposure_4],[5] as [Exposure_5],[6] as [Exposure_6],[7] as [Exposure_7],[8] as [Exposure_8],[9] as [Exposure_9],[10] as [Exposure_10],[11] as [Exposure_11],[12] as [Exposure_12]
from (
select [Year],[Month],[Count]
from #yearMonthActiveBreakdown p
) as s
pivot (sum([Count])
for [Month] in ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12])) as pivoted

drop table #yearMonthActiveBreakdown

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

Обновлены

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

1 голос
/ 23 января 2012

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

ALTER PROCEDURE [dbo].[usp_ssis_sa_yr] 
AS
BEGIN
    declare @yr int
    declare @mo int
    declare @i int
    declare @moStart datetime
    declare @nextMoStart datetime

    delete from serviceagreement_year

    set @i = 1
    while @i < 6
    begin
        set @yr =  year(getdate()) - @i
        set @mo = 1
        while @mo < 13
        begin
            set @moStart =  cast(cast(@mo as char(2)) + '/1/' + cast(@yr as char(4)) as datetime)
            set @nextMoStart =  dateadd(m,1,@mostart)

            insert into serviceagreement_year
            SELECT     SANumber, @yr AS yr, @mo as mo
            FROM         dbo.ServiceAgreement sa ...        
            WHERE     (DateFrom < @nextMoStart) AND (DateTo >= @moStart)
            set @mo = @mo + 1
        end
        set @i = @i + 1
    end

НТН

0 голосов
/ 24 января 2012

В следующем запросе представлен подход, основанный исключительно на множествах. По сути, это попытка перевести ваш ответ в решение на основе множеств.

;
WITH
  QueryPeriod AS (
    SELECT
      Start = DATEADD(M, DATEDIFF(M, 0, MIN(Start)), 0),
      [End] = DATEADD(D, -1, DATEADD(M, DATEDIFF(M, 0, MAX([End])) + 1, 0))
    FROM MIS_Policies
    WHERE (@fromDate IS NULL OR Start >= @fromDate)
      AND (@toDate   IS NULL OR [End] >= @toDate  )
  ),
  Months AS (
    SELECT
      Start = DATEADD(M, v.number, p.Start),
      [End] = DATEADD(D, -1, DATEADD(M, v.number + 1, p.Start))
    FROM QueryPeriod p
      INNER JOIN master..spt_values v
        ON v.number BETWEEN 0 AND DATEDIFF(M, p.Start, p.[End])
    WHERE v.type = 'P'
  ),
  Query AS (
    SELECT
      [Year]  = YEAR (m.Start),
      [Month] = MONTH(m.Start),
      ContractNumber
    FROM MIS_Policies p
      INNER JOIN Months m ON p.Start <= m.[End]
                         AND p.[End] >= m.Start
    WHERE Status IN (0, 3, 4, 5)
  ),
  Grouped AS (
    SELECT
      [Year],
      [Month],
      [Count] = COUNT(ContractNumber)
    FROM Query
    GROUP BY
      [Year],
      [Month]
  ),
  Pivoted AS (
    SELECT
      [Year],
      Exposure_1  = [1],
      Exposure_2  = [2],
      Exposure_3  = [3],
      Exposure_4  = [4],
      Exposure_5  = [5],
      Exposure_6  = [6],
      Exposure_7  = [7],
      Exposure_8  = [8],
      Exposure_9  = [9],
      Exposure_10 = [10],
      Exposure_11 = [11],
      Exposure_12 = [12]
    FROM Query
    PIVOT (
      COUNT(ContractNumber) FOR [Month] IN (
        [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12]
      )
    ) p
  )
SELECT *
FROM Grouped /* Pivoted */

Вот немного объяснения того, что делает каждая часть:

  • CTE QueryPeriod возвращает строку, представляющую (фактическое) начало и конец запрошенного периода, т. Е. Start - это первое число месяца, представленное либо @fromDate, либо MIN(MIS_Policies.Start), и аналогично [End] должен содержать последний месяц, который либо передан как @toDate, либо получен как MAX(MIS_Policies.[End]). Я думал, что это было то, что вы делали в начале своего SP, и я надеюсь, что я не слишком ошибся там.

  • CTE Months строит таблицу месяцев, охватывающую месяцы от QueryPeriod.Start до QueryPeriod.[End] включительно. Таблица будет использоваться для фильтрации / сбора ContractNumber с, чьи периоды начинаются, заканчиваются, находятся в пределах или пересекают указанные месяцы. Как и в обновленной логике, каждый месяц представлен в виде диапазона дат, столбцов Start & [End].

  • CTE Query является, если можно так выразиться, сердцем всего запроса. Это то место, где получается базовый набор строк, тот, на котором позже выполняется группировка (и поворот). Предложение WHERE заимствовано непосредственно из вашего ответа.

  • CTE Grouped предназначен только для того, чтобы показать вам, насколько (или насколько мало) запрос GROUP BY отличается от «соответствующего» запроса PIVOT. Я думаю, что вы можете проследить сходство без особого труда. Это в основном то, что я имел в виду, когда предложил сначала попытаться найти решение GROUP BY, потому что преобразование его затем в решение PIVOT не должно быть слишком сложным, если вы знакомы с основами техники поворота с использованием синтаксиса PIVOT.

  • Последняя часть, Pivoted, почти полностью повторяет запрос PIVOT в вашем ответе. Я хотел бы подчеркнуть, что этот подзапрос PIVOT считывает строки непосредственно из Query, а не из Grouped, поэтому два последних CTE являются своего рода заменой друг другу, и Grouped, когда он больше не нужен, может просто удалить. Не забудьте раскомментировать Pivoted в главном SELECT (и закомментировать Grouped), чтобы увидеть, что он возвращает.

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