在T-SQL中返回两个日期之间的月/年

时间:2012-01-23 17:45:48

标签: tsql

我在一个表格中有开始日期和结束日期的记录。

我需要对此进行调整,以便我在X上有多年,在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 个答案:

答案 0 :(得分:3)

使用经典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 :(得分:1)

当我必须将日期范围转换为年/月值时,我会迭代几个月以确定哪个点在该范围内,然后将其存储在另一个表中

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

HTH

答案 2 :(得分:1)

我想我已经明白了。它可能不是很漂亮,但有点时间了,读过Beth的想法,我有一个火花。

我想出了:

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

所以我采用整个日期范围并创建一个循环,每月迭代循环。它似乎输出了我需要的东西,但它已经很晚了,直到我回到办公室才真正验证。这是我认为我可以避免的迭代方法,但我猜不是。

<强> 更新

更新了更多的生产质量代码,包括Andriy强调的注意事项。

答案 3 :(得分:0)

以下查询提供了一种纯粹基于集合的方法。基本上,这是尝试将您的答案转换为基于集合的解决方案。

;
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 */

以下是对每个部分的作用的一些解释:

  • QueryPeriod CTE返回表示请求期间(实际)开始和结束的行,即Start@fromDateMIN(MIS_Policies.Start)所代表的月份中的第一个[End],类似地@toDate应该包含作为MAX(MIS_Policies.[End])传递或作为Months检索的月份的最后一个月。我认为这就是你在SP开始时所做的事情,我希望我在那里没有太多错误。

  • QueryPeriod.Start CTE会构建跨越QueryPeriod.[End]ContractNumber个月的月份表。该表将用于过滤/收集其期间从指定月份开始,结束,在其中或跨越指定月份的Start。与更新后的逻辑一样,每个月都以日期范围的形式表示,[End]&amp; Query列。

  • 如果我可以这样说,WHERE CTE是整个查询的核心。这是获得基本行集的位置,稍后执行分组(和旋转)的基本行集。 Grouped子句直接从您的答案中借用。

  • Pivoted CTE仅用于向您显示GROUP BY查询与“对应”PIVOT查询的差异(或多少)。我认为你可以毫不费力地追踪相似之处。当我建议你首先尝试提出GROUP BY解决方案时,这基本上就是我的意思,因为一旦你知道使用PIVOT语法的旋转技术的基础知识,将其转换为PIVOT解决方案应该不会太困难。 / p>

  • 最后一部分Query几乎完全复制了答案中的PIVOT查询。我想强调一下,这个PIVOT子查询直接从Grouped读取行,而不是从Grouped读取行,所以最后两个CTE是彼此的替换,Pivoted,不再需要,可以删除。不要忘记在主SELECT中取消注释Grouped(并注释掉{{1}})以实际查看它返回的内容。