2

我想知道是否可以在我尝试创建的 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

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

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

(3/28/2012 -> 年底) (10/10/2012 -> 年底)

将返回 360,而不是 278。

4

3 回答 3

0

这是我使用测试表和数据的实现。您必须在适当的地方进行更改。 注意:我在监狱里用 datediff + 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 )
于 2013-03-27T21:22:50.567 回答
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);

编辑:添加了存在检查以尝试处理同一个人是否在同一年多次使用。

于 2013-03-27T21:00:52.630 回答
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;

该查询可以作为一个函数进行调整。但是,我鼓励您在去那里之前调查数据。一旦你把事情包装在一个函数中,发现和理解异常就会变得更加困难——为什么有人在同一天进出?最长的监狱时间是多少?等等。

于 2013-03-27T21:05:22.377 回答