I have been playing a lot with this and am failing to get the answers.
Query CASES:
If there is EndDate in Table1 and no EndDate in Table 2, then concatenate Table1 SSC + Table1 StartDate + Table1 EndDate
If there is no EndDate on both tables then display Table1 SSC value + Table2 StartDate space value from Table2 Data column
Sample of tables
CREATE TABLE temp.dbo.Table1
(
CID INT,
PID INT,
SSC VARCHAR(3),
StartDate VARCHAR(20),
EndDate VARCHAR(20),
Data VARCHAR(20)
)
INSERT INTO temp.dbo.Table1
VALUES
(1001, 1333, 'OP','20011505','19952012','OP2001156519952012'),
(1002, 1245, 'OR','20121005',NULL,'OR20121005'),
(1003, 1116, 'OP','20131215',NULL,'OP20131215'),
(1004, 1234, 'OP','20011505','19952012','OP2001156519952012')
CREATE TABLE temp.dbo.Table2
(
CID INT,
PID INT,
SSC VARCHAR(3),
StartDate VARCHAR(20),
EndDate VARCHAR(20),
Data VARCHAR(20)
)
INSERT INTO temp.dbo.Table2
VALUES
(1001, 1333, 'OP','20111015',NULL,'OP20111015'),
(1002, 1245, 'OR','20131005',NULL,'OR20131005'),
(1003, 1116, 'OP','20131415',NULL,'OP2013141520131516'),
(1004, 1235, 'OP','20121015',NULL,'OP20121015')
My query which I am working on is here
SELECT
T1.CID,
T1.PID,
CASE
WHEN T1.EndDate IS NOT NULL AND ISNULL(T2.EndDate,'') <>''
THEN CONCAT(T1.SSC, T1.StartDate, T1.EndDate)
WHEN ISNULL(T1.EndDate,'') <> '' AND ISNULL(T2.EndDate,'') <> ''
THEN CONCAT(T1.SSC, T2.StartDate, ' ', T2.Data)
ELSE NULL
END
FROM
temp.dbo.TABLE1 AS T1
LEFT JOIN
temp.dbo.TABLE2 AS T2 ON T1.CID = T2.CID AND T1.PID = T2.PID AND T1.SSC = T2.SSC
This screenshot shows sample data:
You were close, just a small syntax error.
Your first scenario:
Query CASES: 1_If there is EndDate in Table1 and no EndDate in Table 2, then concatenate Table1 SSC + Table1 StartDate + Table1 EndDate IF you look at your case statement isnull(t2.enddate,'') should be = '' rather than <> ''
2_If there is no EndDate on both tables then display Table1 SSC value + Table2 StartDate space value from Table2 Data column
For second case statement it should be (isnull = rather than <> for both of them
This query will give your desired output.
SELECT T1.CID
,T1.PID
,CASE
WHEN T1.EndDate IS NOT NULL AND ISNULL(T2.EndDate,'') = '' THEN CONCAT(T1.SSC, T1.StartDate,T1.EndDate)
WHEN ISNULL(T1.EndDate,'') = '' AND ISNULL(T2.EndDate,'') = '' THEN CONCAT(T1.SSC, T2.StartDate, ' ', T2.Data)
ELSE NULL END as concatedvalues
FROM temp.dbo.TABLE1 AS T1
LEFT JOIN temp.dbo.TABLE2 AS T2 ON T1.CID = T2.CID AND T1.PID = T2.PID AND T1.SSC = T2.SSC
CID PID concatedvalues
1001 1333 OP2001150519952012
1002 1245 OR20131005 OR20131005
1003 1116 OP20131415 OP2013141520131516
1004 1234 OP2001150519952012
My suggestion(if there are default dates such as 1900-01-01, SQL server will treat them as Blank('') (as for date field blank('') means 1900-01-01')) in cases where 1900 should be treated as valid dates. If you just need to compare null fields try to use null or not null condition instead of isnull(date,'')= ''
Related
I am new to SQL Server and on the learning phase. I wanted to perform following task.
I have two table Table1 and Table2. I want to loop the row of Table1 to check if value matches with any row of Table2.
Table1:
ID Name Nationality DOB Priority
--------------------------------------------
1 Sujan Nepali 1996 NULL
2 Sujan Nepali 1999 NULL
3 Sujan Chinese 1996 NULL
4 Sujan Chinese 1888 NULL
Table 2:
ID Name Nationality DOB Address Rank
---------------------------------------------------
1 Sujan Nepali 1996 Kathmandu 1
In Table1 with ID 1 matches all value of same column name in Table2. I need to Update priority of it as 1.
In ID 2 DOB is different and Name and Nationality matches so Update priority as 2.
In ID 3 Name and Year is same as of Table2, so Update priority as 3.
In ID 4 only Name is same, so Update priority as 4.
Expected Output:
Table1:
ID Name Nationality DOB Priority
---------------------------------------------
1 Sujan Nepali 1996 1
2 Sujan Nepali 1999 2
3 Sujan Chinese 1996 3
4 Sujan Chinese 1888 4
I have used CASE but need to perform using IF ELSE IF condition. Any help would be appreciated.
I guess you need something like this. Cross join two tables and look for columns that match each other. According to that make updates.
declare #table1 table (ID int, Name varchar(100), Nationality varchar(100), DOB int, Priority int)
insert into #table1
values
(1, 'Sujan', 'Nepali', 1996, NULL)
, (2, 'Sujan', 'Nepali', 1999, NULL)
, (3, 'Sujan', 'Chinese', 1996, NULL)
, (4, 'Sujan', 'Chinese', 1888, NULL)
declare #table2 table (ID int, Name varchar(100), Nationality varchar(100), DOB int, Address varchar(100), Rank int)
insert into #table2 values (1, 'Sujan', 'Nepali', 1996, 'Kathmandu', 1)
;with cte as (
select
a.*, rnk = row_number() over (order by case when a.Name = b.Name then 100 else 0 end
+ case when a.Nationality = b.Nationality then 10 else 0 end
+ case when a.DOB = b.DOB then 1 else 0 end desc)
from
#table1 a
join #table2 b on a.Name = b.Name
)
update cte
set priority = rnk
select * from #table1
Here's a rextester demo
Unless this is homework I see no reason to use if constructs.
update table1 set priority = (
select min(case
when table2.id = table1.id and ... then 1
when ... then 2
when ... then 3
...
end
from table2
)
Just make sure the branches go in order of highest to lowest priority.
I have two tables :
Table 1:
Id | PersonId |Variable | Value|
1 12 FirstName NULL
2 12 Address NULL
------------------------
Table2:
Id | PersonId | FirstName| LastName| Address | Phone
1 12 Tommy Stark NY 12365
I need to copy data from table 2 into table 1 and
I need output like:
Table 1:
Id | PersonId |Variable | Value|
1 12 FirstName Tommy
2 12 Address NY
You could use a series of case expressions to match table 1 values to table 2 column names. It's clunky as heck, but it should work:
UPDATE t1
SET t1.value = CASE t1.variable
WHEN 'FirstName' THEN t2.firstname
ELSE t1.value
END,
t1.value = CASE t1.variable
WHEN 'LastName' THEN t2.lastname
ELSE t1.value
END,
t1.value = CASE t1.variable
WHEN 'Address' THEN t2.address
ELSE t1.value
END,
t1.value = CASE t1.phone
WHEN 'Phone' THEN t2.phone
ELSE t1.value
END
FROM t1
JOIN t2 ON t1.personid = t2.personid
Declare #Table1 TABLE
(Id int, PersonId int, Variable varchar(9), Value varchar(4))
;
INSERT INTO #Table1
(Id, PersonId, Variable, Value)
VALUES
(1, 12, 'FirstName', NULL),
(2, 12, 'Address', NULL)
;
DECLARE #Table2 TABLE
(Id int, PersonId int, FirstName varchar(5), LastName varchar(5), Address varchar(2), Phone int)
;
INSERT INTO #Table2
(Id, PersonId, FirstName, LastName, Address, Phone)
VALUES
(1, 12, 'Tommy', 'Stark', 'NY', 12365)
select TT.Id,
TT.PersonId,
TT.Variable,
CASE
WHEN T.col = TT.Variable
THEN T.val
END value
from #Table1 TT
INNER JOIN (
select col,val from #Table2 t CROSS APPLY (values ('Id',CAST(Id AS VARCHAR)), ('PersonId',CAST(PersonId AS VARCHAR)),
('FirstName',CAST(FirstName AS VARCHAR)),
('LastName',CAST(LastName AS VARCHAR)),
('Address',CAST(Address AS VARCHAR)),
('Phone',CAST(Phone AS VARCHAR)))cs(col,val))T
ON T.col = TT.Variable
I'm struggling with this one. I have a table A which looks like this:
Employee_ID Dependant_FirstName DOB
1 John 12/12/1980
1 Lisa 11/11/1982
2 Mark 06/06/1985
2 Shane 07/07/1982
2 Mike 03/04/1990
3 NULL NULL
and would like to copy these data in Table B like this (knowing that there could only be a maximum of 6 dependants in Table A):
Employee_ID Dependant1_FirstName DOB Dependant2_FirstName DOB Dependant3_FirstName DOB
1 John 12/12/1980 Lisa 11/11/1982 NULL NULL
2 Mark 06/06/1985 Shane 07/07/1982 Mike 03/04/1990
3 NULL NULL NULL NULL NULL NULL
Thanks very much for the help.
Marc
This is a working example for just your example data, to give an idea of how I'd do it. I'm using a faked-up dependant counter based on date of birth and name. Bear in mind it will break if an employee has twins with the same name, but if they do that, then they deserve all the lifelong data-confusion that they've got in store :)
Also, please consider upgrading that SQL Server. Or moving this kind of pivoting to your reporting tool rather than the database.
CREATE TABLE #employees (employee_id INTEGER, Dependant_FirstName VARCHAR(20), DOB DATETIME)
INSERT INTO #employees VALUES (1, 'John', '12/12/1980')
INSERT INTO #employees VALUES (1, 'Lisa', '11/11/1982')
INSERT INTO #employees VALUES (2, 'Shane', '07/07/1982')
INSERT INTO #employees VALUES (2, 'Mark', '06/06/1985')
INSERT INTO #employees VALUES (2, 'Mike', '03/04/1990')
INSERT INTO #employees VALUES (3, NULL, NULL)
SELECT
employee_id,
MAX(CASE WHEN dep_count = 1 THEN Dependant_FirstName ELSE NULL END) 'Dependant1_FirstName',
MAX(CASE WHEN dep_count = 1 THEN DOB ELSE NULL END) 'Dependant1_DOB',
MAX(CASE WHEN dep_count = 2 THEN Dependant_FirstName ELSE NULL END) 'Dependant2_FirstName',
MAX(CASE WHEN dep_count = 2 THEN DOB ELSE NULL END) 'Dependant2_DOB',
MAX(CASE WHEN dep_count = 3 THEN Dependant_FirstName ELSE NULL END) 'Dependant3_FirstName',
MAX(CASE WHEN dep_count = 3 THEN DOB ELSE NULL END) 'Dependant3_DOB'
FROM
(
SELECT
employee_id,
Dependant_FirstName,
DOB,
(
SELECT
COUNT(*)
FROM
#employees deps
WHERE
#employees.employee_id = deps.employee_id AND
CONVERT(VARCHAR, #employees.DOB, 126) + #employees.Dependant_FirstName <=
CONVERT(VARCHAR, deps.DOB, 126) + deps.Dependant_FirstName
) 'dep_count'
FROM
#employees
) add_dep_count_query
GROUP BY
employee_id
You could
Create a view
Calculate a fictuous ranking
Group to find the maximum ranking for each employee_ID
return the results.
Note: I have ommitted the DOB column in the examples
Statement
CREATE VIEW dbo.VIEW_Employees_Ranking AS
SELECT Ranking = ISNULL(e6.Employee_ID, 0)
+ ISNULL(e5.Employee_ID, 0)
+ ISNULL(e4.Employee_ID, 0)
+ ISNULL(e3.Employee_ID, 0)
+ ISNULL(e2.Employee_ID, 0)
+ ISNULL(e1.Employee_ID, 0)
, e1.Employee_ID
, Name1 = e1.Dependant_FirstName
, Name2 = e2.Dependant_FirstName
, Name3 = e3.Dependant_FirstName
, Name4 = e4.Dependant_FirstName
, Name5 = e5.Dependant_FirstName
, Name6 = e6.Dependant_FirstName
FROM dbo.Employees e1
LEFT OUTER JOIN dbo.Employees e2 ON e2.Employee_ID = e1.Employee_ID AND e2.DOB > e1.DOB
LEFT OUTER JOIN dbo.Employees e3 ON e3.Employee_ID = e2.Employee_ID AND e3.DOB > e2.DOB
LEFT OUTER JOIN dbo.Employees e4 ON e4.Employee_ID = e3.Employee_ID AND e4.DOB > e3.DOB
LEFT OUTER JOIN dbo.Employees e5 ON e5.Employee_ID = e4.Employee_ID AND e5.DOB > e4.DOB
LEFT OUTER JOIN dbo.Employees e6 ON e6.Employee_ID = e5.Employee_ID AND e6.DOB > e5.DOB
GO
SELECT er.*
FROM dbo.VIEW_Employees_Ranking er
INNER JOIN (
SELECT Ranking = MAX(Ranking)
, Employee_ID
FROM dbo.VIEW_Employees_Ranking
GROUP BY
Employee_ID
) ermax ON ermax.Ranking = er.Ranking AND ermax.Employee_ID = er.Employee_ID
Check this code please, It might work for you.
declare #Emp_Id int
declare #Name int
declare #DOB int
declare #Count int
set #Count=1
DECLARE x_cursor CURSOR FOR
SELECT distinct Employee_ID from tableA
OPEN x_cursor
FETCH NEXT FROM x_cursor
INTO #Emp_Id
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE second_cursor CURSOR FOR
SELECT distinct Dependant_FirstName,DOB from tableA
where Employee_ID=#Emp_Id
OPEN second_cursor
FETCH NEXT FROM second_cursor
INTO #Name,#DOB
WHILE ##FETCH_STATUS = 0
BEGIN
if(#Count=1)
begin
insert into tableB (Employee_ID , Dependant1_FirstName,DOB)
values(#Emp_Id,#Name,#DOB)
set #Count=#Count+1
end
else
begin
exec('Update tableB set Dependant'+#count+'_FirstName='+#Name+' ,DOB'+#Count+'='+#DOB+' where Employee_ID='+#Emp_Id)
set #Count=#Count+1
end
FETCH NEXT FROM second_cursor
INTO #Name,#DOB
END
CLOSE second_cursor
DEALLOCATE second_cursor
set #Count=1
FETCH NEXT FROM x_cursor
INTO #Emp_Id
END
CLOSE x_cursor;
DEALLOCATE x_cursor
GO
Have a look at this example:
http://ryanfarley.com/blog/archive/2005/02/17/1712.aspx
here he is concatentating the child elements of a parent key into a string which should allow you to write out a flat record.
Here is the data
Flag Zone Info Date
R North AAA 2010-2-14
R North AAA 2010-2-24
T North AAA 2010-2-4
R South AAA 2010-2-23
T South AAA 2010-2-14
R EAST AAA 2010-2-22
T EAST AAA 2010-2-11
T EAST AAA 2010-2-1
T EAST AAA 2010-2-14
R WEST AAA 2010-2-29
Here is a table in the SQL SERVER, now I want to get a record from each group based on Zone column. The Flag field of this record should be R, and the Date should be the closest and after today's date.
Best Regards,
Using ROW_NUMBER you can try
DECLARE #Table TABLE(
Flag VARChAR(1),
Zone VARCHAR(10),
Info VARCHAR(10),
Date DATETIME
)
INSERT INTO #Table SELECT 'R','North','AAA','2010-2-14'
INSERT INTO #Table SELECT 'R','North','AAA','2010-2-24'
INSERT INTO #Table SELECT 'T','North','AAA','2010-2-4'
INSERT INTO #Table SELECT 'R','South','AAA','2010-2-23'
INSERT INTO #Table SELECT 'T','South','AAA','2010-2-14'
INSERT INTO #Table SELECT 'R','EAST',' AAA','2010-2-22'
INSERT INTO #Table SELECT 'T','EAST',' AAA','2010-2-11'
INSERT INTO #Table SELECT 'T','EAST',' AAA','2010-2-1'
INSERT INTO #Table SELECT 'T','EAST',' AAA','2010-2-14'
INSERT INTO #Table SELECT 'R','WEST',' AAA','2010-2-28'
;WITH Dates AS(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Zone ORDER BY Date) ROWID
FROM #Table
WHERE Flag = 'R'
AND Date > GETDATE()
)
SELECT *
FROM Dates
WHERE ROWID = 1
If you cannot use ROW_NUMBER you could try
SELECT t.*
FROM (
SELECT Zone,
MAX(Date) MaxDate
FROM #Table
WHERE Flag = 'R'
AND Date > GETDATE()
GROUP BY Zone
) Dates INNER JOIN
#Table t ON Dates.Zone = t.Zone and Dates.MaxDate = t.Date
But this will not exclude duplicates...
I have a SQL Server 2005 database which contains a table called Memberships.
The table schema is:
PersonID int, Surname nvarchar(30), FirstName nvarchar(30), Description nvarchar(100), StartDate datetime, EndDate datetime
I'm currently working on a grid feature which shows a break-down of memberships by person. One of the requirements is to split membership rows where there is an intersection of date ranges. The intersection must be bound by the Surname and FirstName, ie splits only occur with membership records of the same Surname and FirstName.
Example table data:
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
Expected result set:
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
Does anyone have any idea how I could write a stored procedure that will return a result set which has the break-down described above.
The problem you are going to have with this problem is that as the data set grows, the solutions to solve it with TSQL won't scale well. The below uses a series of temporary tables built on the fly to solve the problem. It splits each date range entry into its respective days using a numbers table. This is where it won't scale, primarily due to your open ranged NULL values which appear to be inifinity, so you have to swap in a fixed date far into the future that limits the range of conversion to a feasible length of time. You could likely see better performance by building a table of days or a calendar table with appropriate indexing for optimized rendering of each day.
Once the ranges are split, the descriptions are merged using XML PATH so that each day in the range series has all of the descriptions listed for it. Row Numbering by PersonID and Date allows for the first and last row of each range to be found using two NOT EXISTS checks to find instances where a previous row doesn't exist for a matching PersonID and Description set, or where the next row doesn't exist for a matching PersonID and Description set.
This result set is then renumbered using ROW_NUMBER so that they can be paired up to build the final results.
/*
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
*/
The above solution will also handle gaps between additional Descriptions as well, so if you were to add another Description for PersonID 18 leaving a gap:
INSERT INTO Schedule VALUES (18, 'Smith', 'John', 'Gym', '10/02/2009', '28/02/2009')
It will fill the gap appropriately. As pointed out in the comments, you shouldn't have name information in this table, it should be normalized out to a Persons Table that can be JOIN'd to in the final result. I simulated this other table by using a SELECT DISTINCT to build a temp table to create that JOIN.
Try this
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)
[Only many, many years later.]
I created a stored procedure that will align and break segments by a partition within a single table, and then you can use those aligned breaks to pivot the description into a ragged column using a subquery and XML PATH.
See if the below help:
Documentation: https://github.com/Quebe/SQL-Algorithms/blob/master/Temporal/Date%20Segment%20Manipulation/DateSegments_AlignWithinTable.md
Stored Procedure: https://github.com/Quebe/SQL-Algorithms/blob/master/Temporal/Date%20Segment%20Manipulation/DateSegments_AlignWithinTable.sql
For example, your call might look like:
EXEC dbo.DateSegments_AlignWithinTable
#tableName = 'tableName',
#keyFieldList = 'PersonID',
#nonKeyFieldList = 'Description',
#effectivveDateFieldName = 'StartDate',
#terminationDateFieldName = 'EndDate'
You will want to capture the result (which is a table) into another table or temporary table (assuming it is called "AlignedDataTable" in below example). Then, you can pivot using a subquery.
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