T-SQL函数循环

时间:2013-03-27 20:39:00

标签: sql tsql loops sql-server-2008-r2

我想知道我是否可以在我试图创建的T-SQL函数上获得一些帮助:

以下是一些需要查询的示例数据:

简化表格:

ID|PersonID|ValueTypeID|ValueTypeDescription|Value

 1|ZZZZZ000L6|ZZZZZ00071|Start Prison Date|3/28/2012
 2|ZZZZZ000L6|ZZZZZ00071|Start Prison Date|10/10/2012
 3|ZZZZZ000L6|ZZZZZ00072|End Prison Date  |3/29/2012
 4|ZZZZZ000MD|ZZZZZ00071|Start Prison Date|1/15/2012
 5|ZZZZZ000MD|ZZZZZ00072|End Prison Date  |2/15/2012
 6|ZZZZZ000MD|ZZZZZ00071|Start Prison Date|4/1/2012
 7|ZZZZZ000MD|ZZZZZ00072|End Prison Date  |4/5/2012
 8|ZZZZZ000MD|ZZZZZ00071|Start Prison Date|9/3/2012
 9|ZZZZZ000MD|ZZZZZ00072|End Prison Date  |12/1/2012

我需要的是一个接受PersonID和年(@PID, @YR)的T-SQL函数,并返回该人在那一年入狱的天数。

dbo.NumDaysInPrison(@PID, @YR) as int

示例:

dbo.NumDaysInPrison('ZZZZZ000L6', 2012) returns 84
dbo.NumDaysInPrison('ZZZZZ000MD', 2012) returns 124

到目前为止,我已经提出了这个问题,有时会给我答案。

DECLARE @Year int
DECLARE @PersonID nvarchar(50)

SET @Year = 2012
SET @PersonID = 'ZZZZZ000AA'

;WITH StartDates AS
(
SELECT
Value,
ROW_NUMBER() OVER(ORDER BY Value) AS RowNumber
FROM Prisoners
WHERE ValueTypeDescription = 'Start Prison Date' AND PersonID = @PersonID AND YEAR(Value) = @Year
), EndDates AS
(
SELECT
Value,
ROW_NUMBER() OVER(ORDER BY Value) AS RowNumber
FROM Prisoners
WHERE ValueTypeDescription = 'End Prison Date' AND PersonID = @PersonID AND YEAR(Value) = @Year
)
SELECT
SUM(DATEDIFF(d, s.Value, ISNULL(e.Value, cast(str(@Year*10000+12*100+31) as date)))) AS NumDays
FROM StartDates s
LEFT OUTER JOIN EndDates e ON s.RowNumber = e.RowNumber

如果没有结束日期,那么今年早些时候的记录就无法捕获: 例如,如果一个人只有两个记录:

ID|PersonID|ValueTypeID|ValueTypeDescription|Value

 1|ZZZZZ000AA|ZZZZZ00071|Start Prison Date|3/28/2012
 2|ZZZZZ000AA|ZZZZZ00071|Start Prison Date|10/10/2012

(2012年3月28日 - >年终) (10/10/2012 - >年终)

将返回360,而不是278。

3 个答案:

答案 0 :(得分:0)

因此,您似乎拥有分离“开始日期”值和“结束日期”值所需的数据。你真的不需要循环任何东西,你可以根据你的人提取你的起始值然后你的结束值并进行比较。

重要的是要提取您需要的所有内容,然后比较适当的值。

以下是基于您上述数据的示例。需要进行大量调整才能使用生产数据;它对Value数据做出假设。像我这里一样硬编码valuetypeid也是一个坏主意;如果你正在做一个功能,你想要处理它,我想。

DECLARE @pid INT, @yr INT;

WITH startdatecalc AS
(
    SELECT personid, CAST([value] AS date) AS startdate, DATEPART(YEAR, CAST([value] AS date)) AS startyear
    FROM incarctbl 
    WHERE valuetypeid = 'ZZZZZ00071'
),
    enddatecalc AS
(
    SELECT personid, CAST([value] AS date) AS enddate, DATEPART(YEAR, CAST([value] AS date)) AS endyear
    FROM incarctbl 
    WHERE valuetypeid = 'ZZZZZ00072'
)       

SELECT CASE WHEN startyear < @yr THEN DATEDIFF(day, CAST(CAST(@yr AS VARCHAR(4)) + '-01-01' AS date), ISNULL(enddatecalc.enddate, CURRENT_TIMESTAMP))
    ELSE DATEDIFF(DAY, startdate, ISNULL(enddatecalc.enddate, CURRENT_TIMESTAMP)) END AS NumDaysInPrison
FROM startdatecalc 
LEFT JOIN enddatecalc
    ON startdatecalc.personid = enddatecalc.personid
    AND enddatecalc.enddate >= startdatecalc.startdate
    AND NOT EXISTS 
        (SELECT 1 FROM enddatecalc xref 
        WHERE xref.personid = enddatecalc.personid 
        AND xref.enddate < enddatecalc.enddate 
        AND xref.enddate >= startdatecalc.startdate 
        AND xref.endyear < @yr)
WHERE startdatecalc.personid = @pid
AND startdatecalc.startyear <= @yr  
AND (enddatecalc.personid IS NULL OR endyear >= @yr);

编辑:添加了存在检查以尝试处理同一年内多次使用相同的personid。

答案 1 :(得分:0)

这是一个复杂的问题。它不是“要求一个功能”,而是处理两个相互竞争的问题。第一种是将基于交易的数据组织成具有监狱期间的开始和停止日期的记录。第二个是在另一个给定的时间跨度(一年)内总结这个时间。

我认为在开始编写函数之前,您需要花些时间调查数据以了解其中的异常。以下查询可以帮助您。它计算给定年份(即第一次CTE中的年份)所有囚犯的计算:

with vals as (
      select 2012 as yr
     ),
     const as (
      select cast(CAST(yr as varchar(255))+'-01-01' as DATE) as periodstart,
             cast(CAST(yr as varchar(255))+'-12-31' as DATE) as periodend
      from vals
     )
select t.personId, SUM(datediff(d, (case when StartDate < const.periodStart then const.periodStart else StartDate end),
                                (case when EndDate > const.PeriodEnd or EndDate is NULL then const.periodEnd, else EndDate end)
                               )
                      ) as daysInYear
from (select t.*, t.value as StartDate,
             (select top 1 value
              from t t2
              where t.personId = t2.personId and t2.Value >= t.Value and t2.ValueTypeDescription = 'End Prison Date'
              order by value desc
             ) as EndDate
      from t
      where valueTypeDescription = 'Start Prison Date'
     ) t cross join
     const
where StartDate <= const.periodend and (EndDate >= const.periodstart or EndDate is NULL)
group by t.PersonId;

此查询可以作为一种功能进行调整。但是,我会鼓励你在去那里之前调查数据。一旦你把功能包装起来,找到并理解异常就会困难得多 - 为什么有人在同一天进出?监狱中最长的时期如何?等等。

答案 2 :(得分:0)

这是我使用测试表和数据的实现。你必须在适当的地方改变。 注意:我在监狱里服用日期为+1,所以如果你星期一进入并在星期二离开,那就算是两天了。如果您希望将其计为一天,请删除“+ 1”

create table PrisonRegistry
(
    id int not null identity(1,1) primary key
    , PersonId int not null
    , ValueTypeId int not null
    , Value date
)

-- ValueTypeIDs: 1 = start prison date, 2 = end prison date

insert PrisonRegistry( PersonId, ValueTypeId, Value ) values ( 1, 1, '2012-03-28' )
insert PrisonRegistry( PersonId, ValueTypeId, Value ) values ( 1, 1, '2012-10-12' )
insert PrisonRegistry( PersonId, ValueTypeId, Value ) values ( 1, 2, '2012-03-29' )
insert PrisonRegistry( PersonId, ValueTypeId, Value ) values ( 2, 1, '2012-01-15' )
insert PrisonRegistry( PersonId, ValueTypeId, Value ) values ( 2, 2, '2012-02-15' )
insert PrisonRegistry( PersonId, ValueTypeId, Value ) values ( 2, 1, '2012-04-01' )
insert PrisonRegistry( PersonId, ValueTypeId, Value ) values ( 2, 2, '2012-04-05' )
insert PrisonRegistry( PersonId, ValueTypeId, Value ) values ( 2, 1, '2012-09-03' )
insert PrisonRegistry( PersonId, ValueTypeId, Value ) values ( 2, 2, '2012-12-1' )
go


create function dbo.NumDaysInPrison(
    @personId int
    , @year int
)
returns int
as
begin

    declare @retVal int
    set @retVal = 0

    declare @valueTypeId int
    declare @value date
    declare @startDate date
    declare @noDates bit

    set @noDates = 1

    set @startDate = DATEFROMPARTS( @year, 1, 1 )

    declare prisonCursor cursor for
    select
        pr.ValueTypeId
        , pr.Value
    from
        PrisonRegistry pr
    where
        DATEPART( yyyy, pr.Value ) = @year
        and pr.ValueTypeId in (1,2)
        and PersonId = @personId
    order by
        pr.Value

    open prisonCursor

    fetch next from prisonCursor
    into @valueTypeId, @value

    while @@FETCH_STATUS = 0
    begin
        set @noDates = 0

        -- if end date, add date diff to retVal
        if 2 = @valueTypeId
        begin
            --if @startDate is null
            --begin
            --  -- error: two end dates in a row
            --  -- handle
            --end

            set @retVal = @retVal + DATEDIFF( dd, @startDate, @value ) + 1

            set @startDate = null
        end
        else if 1 = @valueTypeId
        begin
            set @startDate = @value
        end

        fetch next from prisonCursor
        into @valueTypeId, @value
    end

    close prisonCursor
    deallocate prisonCursor

    if @startDate is not null and 0 = @noDates
    begin
        set @retVal = @retVal + DATEDIFF( dd, @startDate, DATEFROMPARTS( @year, 12, 31 ) ) + 1
    end

    return @retVal
end

go

select dbo.NumDaysInPrison( 1, 2012 )
select dbo.NumDaysInPrison( 2, 2012 )
select dbo.NumDaysInPrison( 2, 2011 )