2005 SSRS/SQL Server PIVOT results need reversing - sql

Preamble: I've read through the three questions/answers here,here, and here, with big ups to #cade-roux. This all stemmed from trying to use the following data in a 2005 SSRS matrix that, I believe, doesn't work because I want to show a member having to take a test multiple times, and SSRS seems to require the aggregate where I want to show all dates.
I get the following results in my table, which seems to be showing all the data correctly:
How do I change the code below to show a) the "tests" at the top of each column with b) if it's called for, the multiple dates that test was taken?
Here's the code I have to produce the table, above. Much of it is commented out as I was just trying to get the pivot to work, but you may notice I am also trying to specify which test column comes first.
CREATE TABLE #tmp ( ---THIS WORKS BUT TESTS ARE VERTICAL
[TEST] [varchar](30) NOT NULL,
[ED] [datetime] NOT NULL
)
--WHERE THE TEST AND ED COME FROM
INSERT #TMP
SELECT DISTINCT
-- N.FULL_NAME
-- , CONVERT(VARCHAR(30), AM.CREATEDATE, 101) AS ACCOUNT_CLAIMED
-- , N.EMAIL
-- , NULL AS 'BAD EMAIL'
-- , CONVERT(VARCHAR(30), AC.EFFECTIVE_DATE, 101) AS EFFECTIVE_DATE
AC.PRODUCT_CODE AS TEST
, CONVERT(VARCHAR(30), AC.EFFECTIVE_DATE, 101) AS ED
-- , CASE
-- WHEN AC.PRODUCT_CODE = 'NewMem_Test' THEN '9'
-- WHEN AC.PRODUCT_CODE = 'NM_Course1' THEN '1'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course1' THEN '2'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course2' THEN '3'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course3' THEN '4'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course4' THEN '5'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course5' THEN '6'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course6' THEN '7'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course7' THEN '8'
-- END AS 'COLUMN_ORDER'
FROM NAME N
JOIN USERMAIN UM
ON N.ID = UM.CONTACTMASTER
JOIN formTransLog TL
ON UM.USERID = TL.USERNAME
JOIN anet_Users AU
ON UM.USERID = AU.USERNAME
JOIN anet_Membership AM
ON AU.USERID = AM.USERID
JOIN ACTIVITY AC
ON N.ID = AC.ID
AND AC.ACTIVITY_TYPE = 'COURSE'
AND AC.PRODUCT_CODE LIKE 'N%'
--ORDER BY 1, 7
DECLARE #sql AS varchar(max)
DECLARE #pivot_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #select_list AS varchar(max) -- Leave NULL for COALESCE technique
SELECT #pivot_list = COALESCE(#pivot_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + ']'
,#select_list = COALESCE(#select_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + '] AS [col_' + CONVERT(varchar, PIVOT_CODE) + ']'
FROM (
SELECT DISTINCT PIVOT_CODE
FROM (
SELECT TEST, ED, ROW_NUMBER() OVER (PARTITION BY TEST ORDER BY ED) AS PIVOT_CODE
FROM #tmp
) AS rows
) AS PIVOT_CODES
SET #sql = '
;WITH p AS (
SELECT TEST, ED, ROW_NUMBER() OVER (PARTITION BY TEST ORDER BY ED) AS PIVOT_CODE
FROM #tmp
)
SELECT TEST, ' + #select_list + '
FROM p
PIVOT (
MIN(ED)
FOR PIVOT_CODE IN (
' + #pivot_list + '
)
) AS pvt
'
PRINT #sql
EXEC (#sql)
EDIT:
The goal is to have the report in SSRS look like this:

I was able to produce the results you were looking for by adding in a number (RowNum) to the query underneath the PIVOT operator. It doesn't have to be in the final query (though you might want it for client-side sorting), but by having it in the underlying layer the PIVOT operation treats that number like a member of a GROUP BY clause.
Please look through my sample SQL below and let me know if this matches your criteria.
CREATE TABLE #TMP
(
Name VARCHAR(10),
Test VARCHAR(20),
EffectiveDate DATETIME
)
INSERT INTO #TMP (Name, Test, EffectiveDate)
SELECT 'Jane', 'NM_Course1', '01/17/2014' UNION
SELECT 'Jane', 'NMEP_Course1', '12/19/2013' UNION
SELECT 'Jane', 'NMEP_Course1', '12/20/2013' UNION
SELECT 'Jane', 'NMEP_Course2', '12/19/2013' UNION
SELECT 'Jane', 'NMEP_Course2', '12/22/2013' UNION
SELECT 'Jane', 'NMEP_Course2', '01/05/2014' UNION
SELECT 'John', 'NM_Course1', '01/17/2014' UNION
SELECT 'John', 'NMEP_Course1', '01/11/2014'
DECLARE #sql AS varchar(max)
DECLARE #pivot_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #select_list AS varchar(max) -- Leave NULL for COALESCE technique
SELECT #pivot_list = COALESCE(#pivot_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + ']'
,#select_list = COALESCE(#select_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + '] AS [col_' + CONVERT(varchar, PIVOT_CODE) + ']'
FROM (
SELECT DISTINCT PIVOT_CODE
FROM (
SELECT TEST AS PIVOT_CODE
FROM #tmp
) AS rows
) AS PIVOT_CODES
SET #sql = '
SELECT Name, ' + #select_list + '
FROM
(
SELECT b.Name, RowNum, b.EffectiveDate, b.TEST AS PIVOT_CODE
FROM
(
SELECT Name, Test, EffectiveDate, ROW_NUMBER() OVER (PARTITION BY NAME, TEST ORDER BY EffectiveDate) RowNum
FROM #Tmp
) b
) p
PIVOT (
MIN(EffectiveDate)
FOR PIVOT_CODE IN (
' + #pivot_list + '
)
) AS pvt
ORDER BY Name, RowNum
'
PRINT #sql
EXEC (#sql)
DROP TABLE #TMP

Related

Filtering a Result Set based on changed columns SQL

Using the following sample code I'm trying to remove rows that have not had columns changed based on the scalar #CompareFields
create table #ResultSet_fields(
claimId int,
adjustmentVersion int,
ServiceDateFrom date,
ServiceDateTo date,
ProcedureCode varchar(10),
PlaceOfService varchar(3)
)
declare #CompareFields varchar(max)
select #CompareFields = 'ProcedureCode,PlaceOfService'
insert into #ResultSet_fields (ClaimId,adjustmentVersion,ServiceDateFrom,ServiceDateTo,ProcedureCode,PlaceOfService) select 1010,1,'5/5/2015','5/5/2015',92213,11
insert into #ResultSet_fields (ClaimId,adjustmentVersion,ServiceDateFrom,ServiceDateTo,ProcedureCode,PlaceOfService) select 1010,2,'5/5/2015','5/5/2015',92213,11
insert into #ResultSet_fields (ClaimId,adjustmentVersion,ServiceDateFrom,ServiceDateTo,ProcedureCode,PlaceOfService) select 1010,3,'5/5/2015','5/5/2015',92214,11
insert into #ResultSet_fields (ClaimId,adjustmentVersion,ServiceDateFrom,ServiceDateTo,ProcedureCode,PlaceOfService) select 1011,1,'5/5/2015','5/5/2015',5555,11
insert into #ResultSet_fields (ClaimId,adjustmentVersion,ServiceDateFrom,ServiceDateTo,ProcedureCode,PlaceOfService) select 1012,1,'5/7/2015','5/7/2015',66666,11
insert into #ResultSet_fields (ClaimId,adjustmentVersion,ServiceDateFrom,ServiceDateTo,ProcedureCode,PlaceOfService) select 1012,2,'5/7/2015','5/7/2015',66666,13
insert into #ResultSet_fields (ClaimId,adjustmentVersion,ServiceDateFrom,ServiceDateTo,ProcedureCode,PlaceOfService) select 1013,1,'5/7/2015','5/7/2015',99999,11
insert into #ResultSet_fields (ClaimId,adjustmentVersion,ServiceDateFrom,ServiceDateTo,ProcedureCode,PlaceOfService) select 1014,1,'5/9/2015','5/9/2015',99999,11
I only want rows that have had a column change of 'ProcedureCode' and 'PlaceOfService' between adjustment versions.
ex. since the row with ClaimId '1010' had a 'ProcedureCode' change and row with claimId '1012' had a 'PlaceOfService' change I would only like those rows to remain in the Result Set.
I have thought about using the EXCEPT clause or UPDATE() trigger but I'm having trouble forming the syntax.
Can someone point me to the right direction on how to accomplish this?
You can use LAG to get the previous row's value to compare to.
SELECT
ClaimId,
adjustmentVersion,
ServiceDateFrom,
ServiceDateTo,
ProcedureCode,
PlaceOfService
FROM (
SELECT *,
ServiceDateFrom_prev = LAG(rs.ServiceDateFrom) OVER (PARTITION BY rs.ClaimId ORDER BY rs.adjustmentVersion),
ServiceDateTo_prev = LAG(rs.ServiceDateTo ) OVER (PARTITION BY rs.ClaimId ORDER BY rs.adjustmentVersion),
ProcedureCode_prev = LAG(rs.ProcedureCode ) OVER (PARTITION BY rs.ClaimId ORDER BY rs.adjustmentVersion),
PlaceOfService_prev = LAG(rs.PlaceOfService ) OVER (PARTITION BY rs.ClaimId ORDER BY rs.adjustmentVersion)
FROM #ResultSet_fields rs
) rs
WHERE (
#CompareFields LIKE '%ServiceDateFrom%' AND ServiceDateFrom <> ServiceDateFrom_prev
OR #CompareFields LIKE '%ServiceDateTo%' AND ServiceDateTo <> ServiceDateTo_prev
OR #CompareFields LIKE '%ProcedureCode%' AND ProcedureCode <> ProcedureCode_prev
OR #CompareFields LIKE '%PlaceOfService%' AND ProcedureCode <> PlaceOfService_prev
);
If you want to use indexes, or you have a lot of columns ot compare, you can use dynamic SQL
DECLARE #lagCols nvarchar(max), #whereFilters nvarchar(max);
SELECT
#lagCols = STRING_AGG(CAST(
' ' + QUOTENAME(c.name + '_chg') + ' = LAG(rs.' + QUOTENAME(c.name) + ') OVER (PARTITION BY rs.ClaimId ORDER BY rs.adjustmentVersion)
' AS nvarchar(max)), ',')
,#whereFilters = STRING_AGG(CAST(
QUOTENAME(c.name + '_chg') + ' <> ' + QUOTENAME(c.name)
AS nvarchar(max)), ' OR
')
FROM STRING_SPLIT(#CompareFields, ',') s
JOIN tempdb.sys.columns c ON c.name = TRIM(s.value) -- make sure to get the right database
WHERE c.object_id = OBJECT_ID('tempdb..#ResultSet_fields');
DECLARE #sql nvarchar(max) = '
SELECT
ClaimId,
adjustmentVersion,
ServiceDateFrom,
ServiceDateTo,
ProcedureCode,
PlaceOfService
FROM (
SELECT *,
' + #lagCols + '
FROM #ResultSet_fields rs
) rs
WHERE (
' + #whereFilters + '
);
';
PRINT #sql; -- for testing
EXEC sp_executesql #sql;

Moving Complex Query Results to a Table

I have a complex query that cannot be saved as a view because of the DECLARE statements at the beginning of the query.
I would like to save the results of the query into a table.
I tried to insert the following statement to surround the whole query and also only around the SET #sql and EXEC(#sql).
Both caused errors that I could not resolve.
This is the query that produces the results I need:
DECLARE #sql VARCHAR(MAX)
DECLARE #colList VARCHAR(MAX)
--create dynamic list of columns
SELECT #colList = STUFF((SELECT + ',' + QUOTENAME(colName + CAST(CAST(Billing_Order AS INTEGER) AS VARCHAR))
FROM Credible_Client_Insurance_Raw_Data
CROSS APPLY
(SELECT 1 As Ord, 'Payer_ID' ColName
UNION ALL
SELECT 2 As Ord, 'Billing_Order'
UNION ALL
SELECT 3 As Ord, 'Insurance_ID'
UNION ALL
SELECT 4 As Ord, 'Group_No'
UNION ALL
SELECT 5 As Ord, 'Copay_Fee'
UNION ALL
SELECT 6 As Ord, 'Start_Date') v
GROUP BY colName, Ord, CAST(CAST(Billing_Order AS INTEGER) AS VARCHAR)
ORDER BY CAST(CAST(Billing_Order AS INTEGER) AS VARCHAR), Ord
FOR XML PAT(''), TYPE).value('/', 'VARCHAR(MAX)'), 1, 1, '')
--unpivot columns into rows and then apply pivot
SET #sql = 'SELECT Client_ID, ' + #colList + '
FROM
(SELECT
Client_ID, ColVal,
colName + CAST(CAST(Billing_Order AS INTEGER) AS VARCHAR) ColName
FROM Credible_Client_Insurance_Raw_Data
CROSS APPLY
(
SELECT Payer_ID As ColVal, ''Payer_ID'' ColName UNION ALL
SELECT Billing_Order, ''Billing_Order'' UNION ALL
SELECT Insurance_ID, ''Insurance_ID'' UNION ALL
SELECT Group_No, ''Group_No'' UNION ALL
SELECT CAST(Copay_Fee AS VARCHAR), ''Copay_Fee'' UNION ALL
SELECT CAST(Start_Date AS VARCHAR), ''Start_Date''
) v
) A
PIVOT
(
MAX(ColVal) FOR ColName IN (' + #colList + ')
) P1 '
EXEC(#sql)
I just want to INSERT the query's result set into a permanent table Credible_Client_Insurance_Data.
Is there any way to essentially perform a
INSERT INTO Credible_Client_Insurance_Data (Client_ID, etc.)
SELECT Client_ID, etc.
FROM query_results
Or some type of data insert into the table?
IF I'm understanding correctly, don't you just need to change the value of #sql to:
SET #sql = '
INSERT INTO Credible_Client_Insurance_Data (' + #colList + ')
SELECT Client_ID, ' + #colList + '
FROM
(
SELECT Client_ID, ColVal,
colName + CAST(CAST(Billing_Order AS INTEGER) AS VARCHAR) ColName
FROM Credible_Client_Insurance_Raw_Data
CROSS APPLY
(
SELECT Payer_ID As ColVal, ''Payer_ID'' ColName UNION ALL
SELECT Billing_Order, ''Billing_Order'' UNION ALL
SELECT Insurance_ID, ''Insurance_ID'' UNION ALL
SELECT Group_No, ''Group_No'' UNION ALL
SELECT CAST(Copay_Fee AS VARCHAR), ''Copay_Fee'' UNION ALL
SELECT CAST(Start_Date AS VARCHAR), ''Start_Date''
) v
) A
PIVOT
(
MAX(ColVal) FOR ColName IN (' + #colList + ')
) P1 ;';

Merging rows to columns

I have the following situation (heavily abstracted, please ignore bad design):
CREATE TABLE dbo.PersonTest (Id INT, name VARCHAR(255))
INSERT INTO dbo.PersonTest
(Id, name )
VALUES (1, 'Pete')
, (1, 'Marie')
, (2, 'Sam')
, (2, 'Daisy')
I am looking for the following result:
Id Name1 Name2
1 Marie Pete
2 Daisy Sam
So, for each Id, the rows should be merged.
Getting this result I used the following query:
WITH PersonRN AS
(
SELECT *
, ROW_NUMBER() OVER(PARTITION BY Id ORDER BY name) RN
FROM dbo.PersonTest
)
SELECT PT1.Id
, PT1.name Name1
, PT2.name Name2
FROM PersonRN AS PT1
LEFT JOIN PersonRN AS PT2 -- Left join in case there's only 1 name
ON PT2.Id = PT1.Id
AND PT2.RN = 2
WHERE PT1.RN = 1
Which works perfectly fine.
My question is: Is this the best way (best in terms of performance and resilience)? If, for example, one of these Id's has a third name, this third name is ignored by my query. I'm thinking the best way to deal with that would be dynamic SQL, which would be fine, but if it can be done without dynamic, I would prefer that.
Aside from dynamic PIVOT, you can do this using Dynamic Crosstab, which I prefer for readability.
SQL Fiddle
DECLARE #sql1 VARCHAR(1000) = '',
#sql2 VARCHAR(1000) = '',
#sql3 VARCHAR(1000) = ''
DECLARE #max INT
SELECT TOP 1 #max = COUNT(*) FROM PersonTest GROUP BY ID ORDER BY COUNT(*) DESC
SELECT #sql1 =
'SELECT
ID' + CHAR(10)
SELECT #sql2 = #sql2 +
' , MAX(CASE WHEN RN =' + CONVERT(VARCHAR(5), RN)
+ ' THEN name END) AS ' + QUOTENAME('Name' + CONVERT(VARCHAR(5), RN)) + CHAR(10)
FROM(
SELECT TOP(#max)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RN
FROM sys.columns
)t
ORDER BY RN
SELECT #sql3 =
'FROM(
SELECT *,
RN = ROW_NUMBER() OVER(PARTITION BY ID ORDER BY name)
FROM PersonTest
)t
GROUP BY ID
ORDER BY ID'
PRINT (#sql1 + #sql2 + #sql3)
EXEC (#sql1 + #sql2 + #sql3)

Aggregate multiple rows into new single long row of data

I have a database with many rows of data per "case". Each "case" has a unique ID, but each row has a "multiple-choice-element" and a "value". Obviously there is a new row every time the user selects one of the multiple choice elements(mce) and the new value too. The unique ID is like a linchpin holding all of the rows together as a common element for this instance
The data is as follows:
UniqueID Value Text Username Contact
--------------------------------------------------
123456 No Sound Horn Johnson 0788
123456 Broken Headlight Johnson 0788
123456 Broken Windscreen Johnson 0788
I am looking to keep just one row of data, their user details, the key (unique ID), and then have multiple columns for each mce and each value.
UniqueID Username Contact Text Value Text Value Text Value
---------------------------------------------------------------------------------
123456 Johnson 0788 Horn No Sound Headlight Broken Windscreen Broken
I have done this using an update statement for each mce based on the Unique ID so far, but it's a bit clunky and long winded as a stored-procedure and can take quite a bit of time to run.
Can anyone suggest a better way please.
Thank you.
EDIT 2: I wasn't even sure this was possible, but I think I've found a way. Unfortunately, it's extremely hacky, and since you never posted your RDBMS tag, I don't even know if it will work for you.
I don't particularly like the solution, as I said - it feels clumsy to me, but it does work and I don't have any more time to dedicate to this issue. Unless someone else can figure out a way to make it work more efficiently, this is the best I can do for you. Note that I create and reference a temp table called #Numbers - you'll need this to stay in your query. Also note that you might end up with more columns than you want, but that will have to stay.
The following script will allow you to pivot two columns, preserving the relationship between a distinct value in column #1 (Txt) and multiple values in column #2 (Value)
CREATE TABLE #Data (UniqueID INT, Value varchar(10), Txt varchar(10), Username varchar(10), Contact INT)
INSERT INTO #Data (UniqueID, Value, Txt, Username, Contact)
SELECT 123456, 'No Sound', 'Horn', 'Johnson', 0788 UNION
SELECT 123456, 'Broken', 'Headlight', 'Johnson', 0788 UNION
SELECT 123456, 'Smashed', 'Headlight', 'Johnson', 0788 UNION
SELECT 123456, 'Shattered', 'Headlight', 'Johnson', 0788 UNION
SELECT 123456, 'Busted', 'Headlight', 'Johnson', 0788 UNION
SELECT 123456, 'Inop', 'Brake', 'Johnson', 0788
DECLARE #sql AS varchar(max)
DECLARE #vpivot_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #vselect_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #tpivot_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #tselect_list AS varchar(max) -- Leave NULL for COALESCE technique
CREATE TABLE #Numbers (Number INT)
;WITH NumberSequence( Number ) AS
(
SELECT 1 as Number
UNION ALL
SELECT Number + 1
FROM NumberSequence
WHERE Number < 100
)
INSERT INTO #Numbers (Number)
SELECT Number FROM NumberSequence
SELECT
#vpivot_list = COALESCE(#vpivot_list + ', ', '') + '[' + TxtValCombinations + ']'
FROM
(
SELECT
'T' + CAST(Txt.Number AS VARCHAR(5)) + '_' +
'V' + CAST(Val.Number AS VARCHAR(5)) AS TxtValCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
INNER JOIN
#Numbers Val ON
Val.Number <
(
SELECT MAX(CountValue) MaxCountValue
FROM
(
SELECT COUNT(Value) CountValue
FROM #Data
GROUP BY UniqueID, Txt
) cv
)
) PossibleValues
SELECT
#tpivot_list = COALESCE(#tpivot_list + ', ', '') + '[' + TxtCombinations + ']'
FROM
(
SELECT
'T' + CAST(Txt.Number AS VARCHAR(5)) AS TxtCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
) PossibleValues
SELECT #vselect_list = STUFF(
(
SELECT',' +
'T' + CAST(Txt.Number AS VARCHAR(5)) + '_' +
'V' + CAST(Val.Number AS VARCHAR(5)) --AS TxtValCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
INNER JOIN
#Numbers Val ON
Val.Number <
(
SELECT MAX(CountValue) MaxCountValue
FROM
(
SELECT COUNT(Value) CountValue
FROM #Data
GROUP BY UniqueID, Txt
) cv
)
ORDER BY Txt.Number, Val.Number
FOR XML PATH(''), type
).value('.', 'varchar(max)'), 1, 1, '')
SELECT #tselect_list = STUFF(
(
SELECT TxtValCombinations
FROM
(
SELECT',MAX(' +
'T' + CAST(Txt.Number AS VARCHAR(5)) + '_' +
'V' + CAST(Val.Number AS VARCHAR(5)) + ') AS '+
'T' + CAST(Txt.Number AS VARCHAR(5)) + '_' +
'V' + CAST(Val.Number AS VARCHAR(5)) AS TxtValCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
INNER JOIN
#Numbers Val ON
Val.Number <
(
SELECT MAX(CountValue) MaxCountValue
FROM
(
SELECT COUNT(Value) CountValue
FROM #Data
GROUP BY UniqueID, Txt
) cv
)
UNION ALL
SELECT',MAX(' +
'T' + CAST(Txt.Number AS VARCHAR(5)) +') AS T' + CAST(Txt.Number AS VARCHAR(5)) --AS TxtValCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
) s
ORDER BY TxtValCombinations
FOR XML PATH(''), type
).value('.', 'varchar(max)'), 1, 1, '')
SET #sql = '
SELECT UniqueID, Username, Contact, ' + #tselect_list + '
FROM
(
SELECT UniqueID, Username, Contact, Txt, tPIVOT_CODE, ' + #vselect_list + '
FROM
(
SELECT b.UniqueID, Value, Username, Contact, Txt, tPIVOT_CODE, vPIVOT_CODE
FROM
(
SELECT
Data.UniqueID,
Data.Username,
Data.Contact,
Data.Txt,
''T'' + CAST(grpTxt.TxtNum AS VARCHAR(5)) AS tPIVOT_CODE,
Data.Value,
''T'' + CAST(grpTxt.TxtNum AS VARCHAR(5)) + ''_V'' + CAST(ROW_NUMBER() OVER (PARTITION BY Data.UniqueID, Data.Txt ORDER BY Data.Value) AS VARCHAR(4)) vPIVOT_CODE
FROM
#Data Data
INNER JOIN
(
SELECT UniqueID, Txt, ROW_NUMBER() OVER (PARTITION BY UniqueID ORDER BY Txt) AS TxtNum
FROM #Data
GROUP BY UniqueID, Txt
) grpTxt ON
Data.UniqueID = grpTxt.UniqueID AND
Data.Txt = grpTxt.Txt
) b
) vp
PIVOT (
MIN(Value)
FOR vPIVOT_CODE IN (
' + #vpivot_list + '
)
) AS vpvt
) tp
PIVOT (
MIN(Txt)
FOR tPIVOT_CODE IN (
' + #tpivot_list + '
)
) AS tpvt
GROUP BY UniqueID, UserName, Contact
ORDER BY UniqueID, UserName, Contact
'
--PRINT #sql
--PRINT #tselect_list
--PRINT #vpivot_list
EXEC (#sql)
DROP TABLE #Data
DROP TABLE #Numbers

How to declare the columns dynamically in a Select query using PIVOT

I am writing a query to get the address for PersonID. Following query is working for me but it only returns with two Addresses. I want to handle the 'n' number of address with a single query. Is there any way to do this?
Many thanks
SELECT
PersonID, PersonName
[Address1], [Address2]
FROM
(
SELECT
P.PersonID,
P.PersonName,
(ROW_NUMBER() OVER(PARTITION BY P.PersonID ORDER BY A.AddressID)) RowID
FROM tblPerson
INNER JOIN tblAddress AS A ON A.PersonID = P.PersonID
) AS AddressTable
PIVOT
(
MAX(AddressID)
FOR RowID IN ([Address1], [Address2])
) AS PivotTable;
Assuming the following tables and sample data:
USE tempdb;
GO
CREATE TABLE dbo.tblPerson(PersonID INT, PersonName VARCHAR(255));
INSERT dbo.tblPerson SELECT 1, 'Bob'
UNION ALL SELECT 2, 'Charlie'
UNION ALL SELECT 3, 'Frank'
UNION ALL SELECT 4, 'Amore';
CREATE TABLE dbo.tblAddress(AddressID INT, PersonID INT, [Address] VARCHAR(255));
INSERT dbo.tblAddress SELECT 1,1,'255 1st Street'
UNION ALL SELECT 2,2,'99 Elm Street'
UNION ALL SELECT 3,2,'67 Poplar Street'
UNION ALL SELECT 4,2,'222 Oak Ave.'
UNION ALL SELECT 5,1,'36 Main Street, Suite 22'
UNION ALL SELECT 6,4,'77 Sicamore Ct.';
The following query gets the results you want, and shows how it handles 0, 1 or n addresses. In this case the highest number is 3 but you can play with more addresses if you like by adjusting the sample data slightly.
DECLARE #col NVARCHAR(MAX) = N'',
#sel NVARCHAR(MAX) = N'',
#from NVARCHAR(MAX) = N'',
#query NVARCHAR(MAX) = N'';
;WITH m(c) AS
(
SELECT TOP 1 c = COUNT(*)
FROM dbo.tblAddress
GROUP BY PersonID
ORDER BY c DESC
)
SELECT #col = #col + ',[Address' + RTRIM(n.n) + ']',
#sel = #sel + ',' + CHAR(13) + CHAR(10) + '[Address' + RTRIM(n.n) + '] = x'
+ RTRIM(n.n) + '.Address',
#from = #from + CHAR(13) + CHAR(10) + ' LEFT OUTER JOIN xMaster AS x'
+ RTRIM(n.n) + ' ON x' + RTRIM(n.n) + '.PersonID = p.PersonID AND x'
+ RTRIM(n.n) + '.rn = ' + RTRIM(n.n)
FROM m CROSS JOIN (SELECT n = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_columns) AS n WHERE n.n <= m.c;
SET #query = N';WITH xMaster AS
(
SELECT PersonID, Address,
rn = ROW_NUMBER() OVER (PARTITION BY PersonID ORDER BY Address)
FROM dbo.tblAddress
)
SELECT PersonID, PersonName' + #col
+ ' FROM
(
SELECT p.PersonID, p.PersonName, ' + STUFF(#sel, 1, 1, '')
+ CHAR(13) + CHAR(10) + ' FROM dbo.tblPerson AS p ' + #from + '
) AS Addresses;';
PRINT #query;
--EXEC sp_executesql #query;
If you print the SQL you will see this result:
;WITH xMaster AS
(
SELECT PersonID, Address,
rn = ROW_NUMBER() OVER (PARTITION BY PersonID ORDER BY Address)
FROM dbo.tblAddress
)
SELECT PersonID, PersonName,[Address1],[Address2],[Address3] FROM
(
SELECT p.PersonID, p.PersonName,
[Address1] = x1.Address,
[Address2] = x2.Address,
[Address3] = x3.Address
FROM dbo.tblPerson AS p
LEFT OUTER JOIN xMaster AS x1 ON x1.PersonID = p.PersonID AND x1.rn = 1
LEFT OUTER JOIN xMaster AS x2 ON x2.PersonID = p.PersonID AND x2.rn = 2
LEFT OUTER JOIN xMaster AS x3 ON x3.PersonID = p.PersonID AND x3.rn = 3
) AS Addresses;
If you execute it, you will see this:
I know the query to get here is an ugly mess, but your requirement dictates it. It would be easier to return a comma-separated list as I suggested in my comment, or to have the presentation tier deal with the pivoting.