2009-09-09 7 views
2

Memberships이라는 테이블이 포함 된 SQL Server 2005 데이터베이스가 있습니다.날짜 범위 교차점 SQL로 분할

테이블 스키마는 다음과 같습니다 나는 현재 사람에 의해 회원의 휴식 다운을 표시 그리드 기능에서 일하고 있어요

PersonID int, Surname nvarchar(30), FirstName nvarchar(30), Description nvarchar(100), StartDate datetime, EndDate datetime

. 요구 사항 중 하나는 날짜 범위의 교차가있는 멤버십 행을 분할하는 것입니다. 교차로는 성 (Surname)과 성 (FirstName)으로 묶여 야합니다. 즉, 같은 성 (Surname)과 성 (FirstName)의 멤버십 레코드로만 나뉩니다.

예 테이블 데이터 :

18 Smith John Poker Club 01/01/2009 NULL 
18 Smith John Library  05/01/2009 18/01/2009 
18 Smith John Gym   10/01/2009 28/01/2009 
26 Adams Jane Pilates  03/01/2009 16/02/2009

예상 결과 집합 :

18 Smith John Poker Club     01/01/2009 04/01/2009 
18 Smith John Poker Club/Library  05/01/2009 09/01/2009 
18 Smith John Poker Club/Library/Gym 10/01/2009 18/01/2009 
18 Smith John Poker Club/Gym   19/01/2009 28/01/2009 
18 Smith John Poker Club     29/01/2009 NULL 
26 Adams Jane Pilates      03/01/2009 16/02/2009

하는가 나는 브레이크 다운이있는 결과 집합을 반환하는 저장 프로 시저를 쓸 수있는 방법에 대해 어떤 생각을 가지고 사람 전술 한 바와.

+0

귀하의 디자인은 동일한 이름이나 성을 가진 여러 멤버를 어떻게 처리합니까? 당신이 제공 한 샘플 데이터가 존 스미스 (John Smith)라는 세 명의 다른 사람들을 가리킬 가능성이 있습니다. –

+0

이것이 유효한 이유입니다. 그 중 제가이 가능성을 반영하도록 제 질문을 수정했습니다. 나는 실제로 각 사람의 ID를 저장하고 있지만, 중복 이름을 생각하고 있지는 않은 질문을 썼을 때. 의견에 대한 환호. – user168369

+0

PersonID가 있습니다 - 최종 출력까지 이름 비트를 완전히 무시합니다. 선택 – MartW

답변

2

이 문제는 데이터 세트가 커질수록 TSQL로 해결할 수있는 솔루션이 확장되지 않는다는 점에서 문제가 있습니다. 아래에서는 문제를 해결하기 위해 임시로 작성된 일련의 임시 테이블을 사용합니다. 숫자 테이블을 사용하여 각 날짜 범위 항목을 각각의 요일로 나눕니다. 여기서 확장되지 않는 이유는 무한대로 보이는 열린 원거리 NULL 값 때문입니다. 따라서 변환 범위를 실현 가능한 기간으로 제한하는 미래의 고정 날짜로 스와핑해야합니다. 매일의 최적화 된 렌더링을 위해 적절한 인덱싱을 사용하여 일별 테이블 또는 달력 테이블을 구성함으로써 성능을 향상시킬 수 있습니다.

범위가 분할되면 XML PATH를 사용하여 설명이 병합되므로 범위 시리즈의 각 요일에 나열된 모든 설명이 나열됩니다. PersonID 및 Date에 의한 행 번호 매기기는 두 개의 NOT EXISTS 검사를 사용하여 일치하는 PersonID 및 설명 집합에 대해 이전 행이 존재하지 않는 인스턴스를 찾거나 다음 행이없는 경우를 찾는 각 범위의 첫 번째 행과 마지막 행을 허용합니다. 일치하는 PersonID W 설명 세트에 대해 존재하지 않습니다.

그런 다음이 결과 집합은 ROW_NUMBER를 사용하여 번호가 매겨 지므로 최종 결과를 빌드 할 수 있습니다.

/* 
SET DATEFORMAT dmy 
USE tempdb; 
GO 
CREATE TABLE Schedule 
(PersonID int, 
Surname nvarchar(30), 
FirstName nvarchar(30), 
Description nvarchar(100), 
StartDate datetime, 
EndDate datetime) 
GO 
INSERT INTO Schedule VALUES (18, 'Smith', 'John', 'Poker Club', '01/01/2009', NULL) 
INSERT INTO Schedule VALUES (18, 'Smith', 'John', 'Library', '05/01/2009', '18/01/2009') 
INSERT INTO Schedule VALUES (18, 'Smith', 'John', 'Gym', '10/01/2009', '28/01/2009') 
INSERT INTO Schedule VALUES (26, 'Adams', 'Jane', 'Pilates', '03/01/2009', '16/02/2009') 
GO 

*/ 

SELECT 
PersonID, 
Description, 
theDate 
INTO #SplitRanges 
FROM Schedule, (SELECT DATEADD(dd, number, '01/01/2008') AS theDate 
    FROM master..spt_values 
    WHERE type = N'P') AS DayTab 
WHERE theDate >= StartDate 
    AND theDate <= isnull(EndDate, '31/12/2012') 

SELECT 
ROW_NUMBER() OVER (ORDER BY PersonID, theDate) AS rowid, 
PersonID, 
theDate, 
STUFF((
    SELECT '/' + Description 
    FROM #SplitRanges AS s 
    WHERE s.PersonID = sr.PersonID 
    AND s.theDate = sr.theDate 
    FOR XML PATH('') 
), 1, 1,'') AS Descriptions 
INTO #MergedDescriptions 
FROM #SplitRanges AS sr 
GROUP BY PersonID, theDate 


SELECT 
ROW_NUMBER() OVER (ORDER BY PersonID, theDate) AS ID, 
* 
INTO #InterimResults 
FROM 
(
SELECT * 
FROM #MergedDescriptions AS t1 
WHERE NOT EXISTS 
    (SELECT 1 
    FROM #MergedDescriptions AS t2 
    WHERE t1.PersonID = t2.PersonID 
    AND t1.RowID - 1 = t2.RowID 
    AND t1.Descriptions = t2.Descriptions) 
UNION ALL 
SELECT * 
FROM #MergedDescriptions AS t1 
WHERE NOT EXISTS 
    (SELECT 1 
    FROM #MergedDescriptions AS t2 
    WHERE t1.PersonID = t2.PersonID 
    AND t1.RowID = t2.RowID - 1 
    AND t1.Descriptions = t2.Descriptions) 
) AS t 

SELECT DISTINCT 
PersonID, 
Surname, 
FirstName 
INTO #DistinctPerson 
FROM Schedule 

SELECT 
t1.PersonID, 
dp.Surname, 
dp.FirstName, 
t1.Descriptions, 
t1.theDate AS StartDate, 
CASE 
    WHEN t2.theDate = '31/12/2012' THEN NULL 
    ELSE t2.theDate 
END AS EndDate 
FROM #DistinctPerson AS dp 
JOIN #InterimResults AS t1 
ON t1.PersonID = dp.PersonID 
JOIN #InterimResults AS t2 
ON t2.PersonID = t1.PersonID 
    AND t1.ID + 1 = t2.ID 
    AND t1.Descriptions = t2.Descriptions 

DROP TABLE #SplitRanges 
DROP TABLE #MergedDescriptions 
DROP TABLE #DistinctPerson 
DROP TABLE #InterimResults 

/* 

DROP TABLE Schedule 

*/ 

위의 솔루션은 또한뿐만 아니라 추가 설명 사이의 격차를 처리합니다, 그래서 당신이 인 경우에 간격을두고 PersonID (18)에 대한 또 다른 설명을 추가 :

INSERT INTO Schedule VALUES (18, 'Smith', 'John', 'Gym', '10/02/2009', '28/02/2009') 

그것은 적절 공백을 채울 것입니다. 주석에서 지적했듯이이 테이블에 이름 정보가 있으면 안되며 최종 결과에 JOIN 할 수있는 사람 테이블로 정규화되어야합니다. SELECT DISTINCT를 사용하여 JOIN을 생성하는 임시 테이블을 작성하여이 다른 테이블을 시뮬레이션했습니다.

1

SET DATEFORMAT dmy 
DECLARE @Membership TABLE( 
    PersonID int, 
    Surname  nvarchar(16), 
    FirstName nvarchar(16), 
    Description nvarchar(16), 
    StartDate datetime, 
    EndDate  datetime) 
INSERT INTO @Membership VALUES (18, 'Smith', 'John', 'Poker Club', '01/01/2009', NULL) 
INSERT INTO @Membership VALUES (18, 'Smith', 'John','Library', '05/01/2009', '18/01/2009') 
INSERT INTO @Membership VALUES (18, 'Smith', 'John','Gym', '10/01/2009', '28/01/2009') 
INSERT INTO @Membership VALUES (26, 'Adams', 'Jane','Pilates', '03/01/2009', '16/02/2009') 

--Program Starts 
declare @enddate datetime 
--Measuring extreme condition when all the enddates are null(i.e. all the memberships for all members are in progress) 
-- in such a case taking any arbitary date e.g. '31/12/2009' here else add 1 more day to the highest enddate 
select @enddate = case when max(enddate) is null then '31/12/2009' else max(enddate) + 1 end from @Membership 

--Fill the null enddates 
; with fillNullEndDates_cte as 
(
    select 
      row_number() over(partition by PersonId order by PersonId) RowNum 
      ,PersonId 
      ,Surname 
      ,FirstName 
      ,Description 
      ,StartDate 
      ,isnull(EndDate,@enddate) EndDate 
    from @Membership 
) 
--Generate a date calender 
, generateCalender_cte as 
(
    select 
     1 as CalenderRows 
     ,min(startdate) DateValue 
    from @Membership 
     union all 
     select 
      CalenderRows+1 
      ,DateValue + 1 
     from generateCalender_cte 
     where DateValue + 1 <= @enddate 
) 
--Generate Missing Dates based on Membership 
,datesBasedOnMemberships_cte as 
(
    select 
      t.RowNum 
      ,t.PersonId 
      ,t.Surname 
      ,t.FirstName 
      ,t.Description   
      , d.DateValue 
      ,d.CalenderRows 
    from generateCalender_cte d 
    join fillNullEndDates_cte t ON d.DateValue between t.startdate and t.enddate 
) 
--Generate Dscription Based On Membership Dates 
, descriptionBasedOnMembershipDates_cte as 
(
    select  
     PersonID 
     ,Surname 
     ,FirstName 
     ,stuff((
      select '/' + Description 
      from datesBasedOnMemberships_cte d1 
      where d1.PersonID = d2.PersonID 
      and d1.DateValue = d2.DateValue 
      for xml path('') 
     ), 1, 1,'') as Description 
     , DateValue 
     ,CalenderRows 
    from datesBasedOnMemberships_cte d2 
    group by PersonID, Surname,FirstName,DateValue,CalenderRows 
) 
--Grouping based on membership dates 
,groupByMembershipDates_cte as 
(
    select d.*, 
    CalenderRows - row_number() over(partition by Description order by PersonID, DateValue) AS [Group] 
    from descriptionBasedOnMembershipDates_cte d 
) 
select PersonId 
,Surname 
,FirstName 
,Description 
,convert(varchar(10), convert(datetime, min(DateValue)), 103) as StartDate 
,case when max(DateValue)= @enddate then null else convert(varchar(10), convert(datetime, max(DateValue)), 103) end as EndDate 
from groupByMembershipDates_cte 
group by [Group],PersonId,Surname,FirstName,Description 
order by PersonId,StartDate 
option(maxrecursion 0) 
0

시도 [이상에만 수년.]

나는 정렬하고 하나의 테이블에서 파티션으로 세그먼트를 중단하고 사용할 수있는 것입니다 저장 프로 시저를 생성 하위 쿼리와 XML PATH를 사용하여 설명을 불규칙한 열로 피벗하는 정렬 부분.

  1. 문서 : https://github.com/Quebe/SQL-Algorithms/blob/master/Temporal/Date%20Segment%20Manipulation/DateSegments_AlignWithinTable.md

  2. 저장 프로 시저 :

    EXEC dbo.DateSegments_AlignWithinTable 
    @tableName = 'tableName', 
    @keyFieldList = 'PersonID', 
    @nonKeyFieldList = 'Description', 
    @effectivveDateFieldName = 'StartDate', 
    @terminationDateFieldName = 'EndDate' 
    
    : https://github.com/Quebe/SQL-Algorithms/blob/master/Temporal/Date%20Segment%20Manipulation/DateSegments_AlignWithinTable.sql

예를 들어, 호출처럼 보일 수 있습니다 아래의 도움이 경우

은 참조

결과 (테이블)를 다른 테이블 또는 임시 테이블 (아래 예에서 "AlignedDataTable"이라고 가정)에 캡처하려고합니다. 그런 다음 부속 조회를 사용하여 피벗 할 수 있습니다.

SELECT 
    PersonID, StartDate, EndDate, 

    SUBSTRING ((SELECT ',' + [Description] FROM AlignedDataTable AS innerTable 
     WHERE 
      innerTable.PersonID = AlignedDataTable.PersonID 
      AND (innerTable.StartDate = AlignedDataTable.StartDate) 
      AND (innerTable.EndDate = AlignedDataTable.EndDate) 
     ORDER BY id 
     FOR XML PATH ('')), 2, 999999999999999) AS IdList 

FROM AlignedDataTable 
GROUP BY PersonID, StartDate, EndDate 
ORDER BY PersonID, StartDate