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

私が必要としているのは、PersonIDと Year ( @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

これは、年の初めのレコードが終了日なしで残されている場合、キャプチャに失敗します。たとえば、個人のレコードが 2 つしかない場合:

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 -> 年末) (2012/10/10 -> 年末)

278 ではなく 360 を返します。

4

3 に答える 3

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);

編集: 同じ人物 ID が同じ年に複数回使用された場合に処理を試みる存在チェックを追加しました。

于 2013-03-27T21:00:52.630 に答える
0

これは複雑な質問です。2 つの競合する問題を扱っているため、「関数を要求する」ということではありません。1 つ目は、トランザクション ベースのデータを、懲役期間の開始日と終了日を含むレコードに整理することです。2 つ目は、別の期間 (1 年) 内に費やされた時間を集計することです。

関数の作成に進む前に、データの異常を理解するためにデータの調査に時間を費やす必要があると思います。次のクエリが役立ちます。これは、特定の年 (最初の 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 に答える
0

テスト テーブルとデータを使用した実装を次に示します。必要に応じて変更する必要があります。 : 私は datediff + 1 を刑務所内の日数と見なします。月曜日に刑務所に入って火曜日に出発すると、2 日とカウントされます。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 に答える