Tranposing Rows to Columns (PIVOT) SQL - sql

I have a table of data that I am attempting to transpose/pivot rows to columns with.
I'm au fait with PIVOT/UNPIVOT and I tried to do this however due to the aggregation used with PIVOT I was only returning 1 result which was the first Endorsement "M06" and not the other 2 Endorsements for that PolRef#.
Here is my data example:
CREATE TABLE #temptable (
[B#] int,
[key#] varchar(24),
[Ref#] varchar(6),
[PolRef#] varchar(10),
[Sequence#] int,
[Date] datetime,
[Endnumber] varchar(4),
[Desc] varchar(76),
[Value] int,
[Specdrivers] varchar(76),
[Reg] varchar(76)
)
INSERT INTO #temptable VALUES
( 6, '484F445830314D4330310132', 'HODX01', 'HODX01MC01', 1050, N'2019-09-20T00:00:00', 'M06', 'Garaging/storage', 0, NULL, 'All' ),
( 6, '484F445830314D433031013C', 'HODX01', 'HODX01MC01', 1060, N'2019-09-20T00:00:00', '046', 'NCB deleted', 0, NULL, 'All' ),
( 6, '484F445830314D4330310146', 'HODX01', 'HODX01MC01', 1070, N'2019-09-20T00:00:00', '099', 'Limited mileage', 1500, NULL, 'All' )
DROP TABLE #temptable
Essentially I need a column for each row that says "Applicable Endorsement" for just the Endnumber value.
Output would look like:
| B# | PolRef# | Applicable Endorsement | Applicable Endorsement | Applicable Endorsement |
| 6 | HODX01MC01 | M06 | 046 | 099 |
Any thoughts on how I can go about this, note there can be any number of endorsements it isn't a fixed amount.

You can use the following PIVOT:
;WITH PrePivot AS
(
SELECT
T.B#,
T.PolRef#,
T.Endnumber,
PivotRanking = ROW_NUMBER() OVER (
PARTITION BY
T.B#,
T.PolRef#
ORDER BY
(SELECT NULL)) -- Determine the order here, maybe T.Date?
FROM
#temptable AS T
)
SELECT
P.B#,
P.PolRef#,
[Applicable Endorsement] = P.[1],
[Applicable Endorsement] = P.[2],
[Applicable Endorsement] = P.[3],
[Applicable Endorsement] = P.[4],
[Applicable Endorsement] = P.[5]
FROM
PrePivot AS V
PIVOT (
MAX(V.Endnumber) FOR V.PivotRanking IN ([1],[2],[3],[4],[5])
) AS P
I've written up to 5 endorsements, you can add as many as you want. If you want a dynamic amount, you need to use a dynamic pivot.
B# PolRef# Applicable Endorsement Applicable Endorsement Applicable Endorsement Applicable Endorsement Applicable Endorsement
6 HODX01MC01 M06 046 099 NULL NULL

Try this Dynamic Sql
IF OBJECT_ID('tempdb..#FormatedTable')IS NOT NULL
DROP TABLE #FormatedTable
Go
SELECT ROW_NUMBER()OVER(ORDER BY (SELECT 1)) As SeqId,'Applicable Endorsement'+CAST(ROW_NUMBER()OVER(ORDER BY (SELECT 1)) AS VARCHAR(100)) AS ReqColumn,*
INTO #FormatedTable
FROM #temptable
DECLARE #Sql nvarchar(max),
#DynamicColumn nvarchar(max),
#MaxDynamicColumn nvarchar(max)
SELECT #DynamicColumn = STUFF((SELECT ', '+QUOTENAME(ReqColumn)
FROM #FormatedTable FOR XML PATH ('')),1,1,'')
SELECT #MaxDynamicColumn = STUFF((SELECT ', '+'MAX('+(QUOTENAME(ReqColumn))+') AS '+QUOTENAME(CAST(ReqColumn AS VARCHAR(100)))
FROM #FormatedTable ORDER BY SeqId FOR XML PATH ('')),1,1,'')
SELECT #MaxDynamicColumn
SET #Sql=' SELECT [B#],[PolRef#], '+ #MaxDynamicColumn+'
FROM
(
SELECT * FROM #FormatedTable
) AS src
PIVOT
(
MAX(Endnumber) FOR [ReqColumn] IN ('+#DynamicColumn+')
) AS Pvt
GROUP BY [B#],[PolRef#]
'
PRINT (#Sql)
EXEC (#Sql)
Result
B# PolRef# Applicable Endorsement1 Applicable Endorsement2 Applicable Endorsement3
--------------------------------------------------------------------------------------------
6 HODX01MC01 M06 046 099

Related

Dynamically create table columns with values from Pivot Table

I have a dynamic query that utilizes a pivot function and the following is an example of data in my table.
Status 1 | Week 1 |25
Status 1 | Week 1 |25
Status 1 | Week 2 |25
Status 2 | Week 1 | 2
Status 2 | Week 1 | 8
Status 2 | Week 1 | 10
Status 2 | Week 1 | 10
and this is an example of how the data is returned.
Week 1 Week 2
Status 1 | 50 25
Status 2 10 20
For my query I am passing in a week and I want to pivot on the following 5 weeks, so example, if I pass in 1, I expect to have columns from week 1 to week 6.
To help facilitate that I have written the following query.
--EXEC usp_weekReport #weeks=1, #year='2019'
ALTER PROC usp_weekReport
(
#weeks INT,
#year NVARCHAR(4)
)
AS
DECLARE #columns NVARCHAR(MAX), #sql NVARCHAR(MAX), #csql NVARCHAR(MAX);
SET #columns = N'';
SELECT #columns += N', p.' + QUOTENAME([week])
FROM (
SELECT p.[week]
FROM [Housing_support_DB].[dbo].[Invoices] P
WHERE DATEPART(YEAR,P.date)='2019'--#year
AND
([week] IN (1)
OR
[week] IN (1+1)
OR
[week] IN (1+2)
OR
[week] IN (1+3)
OR
[week] IN (1+4)
OR
[week] IN (1+5)
)
GROUP BY P.[week]
) AS x;
SET #sql = N'
SELECT p.[statusName],' + STUFF(#columns, 1, 2, '') + '
FROM
(
SELECT
SUM(CAST(REPLACE(REPLACE(A.amount,'','',''''),''$'','''') AS FLOAT)) as sumInvoice,
A.invoiceStatusID_FK,
B.statusName,
-- C.programme,
[week]
FROM [dbo].[Invoices] A
INNER JOIN invoiceStatus B
ON A.invoiceStatusID_FK=B.invoiceStatusID
-- INNER JOIN CapitalAccountBalances C
-- ON C.accountBalanceID=A.accountBalanceID_FK
-- WHERE A.accountBalanceID_FK=5
GROUP BY invoiceStatusID_FK,B.statusName,[week]--,C.programme
) AS j
PIVOT
(
SUM(sumInvoice) FOR [week] IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
) AS p;';
--PRINT #sql;
EXEC sp_executesql #sql;
--SET #csql = N'
--CREATE TABLE ##reportResult
--(
--statusName nvarchar(50),'+
CREATE TABLE ##reportResult
(
statusName nvarchar(50),
weekA INT DEFAULT 0,
weekB int DEFAULT 0--,
--weekC int DEFAULT 0,
--weekD int DEFAULT 0,
--weekE int DEFAULT 0,
--weekF int DEFAULT 0
)
INSERT into ##reportResult Exec(#sql)
--INSERT ##reportResult Exec(#sql)
--SELECT statusName, weekA,weekB,weekC,weekD,weekE,weekF -- here you have "static SELECT with field names"
--FROM ##reportResult
--DROP TABLE ##reportResult
Problem
The huge problem that I have here is that, I need to send the result of this query to a tempTable...#reportResult. As a result, I need to create the table. However, if I attempt to create the table with the max amount of columns anticipated (6) I will get an invalid number of columns error. For example, in my database I only have two weeks, that's why I can only create the table with columns weekA and weekB. I also cannot do a select into.
Presently, I am trying to find a way to either create the table dynamically depending on the amount of weeks from the first part of the pivot table. Or, to manipulate the first part of the pivot to select week,week+1 etc as columns when run so that way , I can create the column with all fields.
Appreciate any help that could be provided.
You required dynamic SQL in your case as the column name is need to generate based on th Input week number. Below I have give you the script I created with your sample data using CTE. You just need to updated the script based on your table and requirement.
You can test the code changing the value of Week_No
For your final query, just use the SELECT part after removing the CTE code
DECLARE #Week_No INT = 2
DECLARE #Loop_Count INT = 1
DECLARE #Column_List VARCHAR(MAX) = '[Week '+CAST(#Week_No AS VARCHAR) +']'
WHILE #Loop_Count < 5
BEGIN
SET #Column_List = #Column_List +',[Week '+CAST(#Week_No+#Loop_Count AS VARCHAR) +']'
SET #Loop_Count = #Loop_Count + 1
END
--SELECT #Column_List
EXEC
('
WITH your_table(Status,Week_No,Val)
AS
(
SELECT ''Status 1'',''Week 1'',25 UNION ALL
SELECT ''Status 1'',''Week 1'',25 UNION ALL
SELECT ''Status 1'',''Week 2'',25 UNION ALL
SELECT ''Status 2'',''Week 1'',2 UNION ALL
SELECT ''Status 2'',''Week 1'',8 UNION ALL
SELECT ''Status 2'',''Week 1'',10 UNION ALL
SELECT ''Status 2'',''Week 1'',10
)
SELECT * FROM
(
SELECT * FROM your_table
) AS P
PIVOT
(
SUM(val)
FOR Week_No IN ('+#Column_List+')
)PVT
')

Pivot SQL query output

I have a requirement where my query will return output something like this:
PermissionType IsAllowed
-------------------------
IsEdit | 1
IsDelete | 0
isRemove | 1
isPrint | 1
isReport | 0
-- | -
-- | -
-- | -
--------------------------
These rows can be dynamic depending upon the filter criteria I will pass.
So now I want to convert the above resultset to the following:
IsEdit | IsDelete | IsRemove | IsPrint | IsReport | -- | -- | --
--------------------------------------------------------------------
1 | 0 | 1 | 1 | 0 | - | - | -
I tried to use the pivot here but it asks the Column Names to be pivoted into the output and this is dynamic in my case, also it needed an aggregate function for FOR but I don't have any calculation in my case.
Anyone please help me on this.
Then try this Dynamic sql
IF OBJECT_ID('dbo.temp')IS NOT NULL
DROP TABLE temp
;WITH Cte(PermissionType, IsAllowed)
AS
(
SELECT 'IsEdit' , 1 UNION ALL
SELECT 'IsDelete' , 0 UNION ALL
SELECT 'isRemove' , 1 UNION ALL
SELECT 'isPrint' , 1 UNION ALL
SELECT 'isReport' , 0
)
SELECT *,ROW_NUMBER()OVER(ORDER BY (SELECT 1)) AS Seq INTO
temp FROM Cte
DECLARE #Sql nvarchar(max),
#Sqlcol nvarchar(max),
#ISNULLSqlcol nvarchar(max)
SELECT #Sqlcol=STUFF((SELECT ', '+QUOTENAME(PermissionType)
FROM temp ORDER BY Seq FOR XML PATH ('')),1,1,'')
SELECT #ISNULLSqlcol=STUFF((SELECT ', '+'MAX('+QUOTENAME(PermissionType) +') AS '+QUOTENAME(PermissionType)
FROM temp ORDER BY Seq FOR XML PATH ('')),1,1,'')
SET #Sql='
SELECT '+#ISNULLSqlcol+'
FROM(
SELECT * FROM temp
)AS SRC
PIVOT
(
MAX(IsAllowed) FOR PermissionType IN ('+#Sqlcol+')
) AS PVT '
PRINT #Sql
EXEC (#Sql)
IsEdit IsDelete isRemove isPrint isReport
--------------------------------------------------
1 0 1 1 0
With pivot and dynamic sql you can create a query that will include a different number of columns:
if OBJECT_ID('Test') is not null
drop table [dbo].[Test]
CREATE TABLE [dbo].[Test](PermissionType varchar(20), IsAllowed int)
insert into [dbo].[Test] values
('IsEdit' , 1)
,('IsDelete', 0)
,('isRemove', 1)
,('isPrint' , 1)
,('isReport', 0)
--this variable holds all the dates that will become column names
declare #permissionTypes nvarchar(max) = ''
--this variable contains the TSQL dinamically generated
declare #sql nvarchar(max) = ''
select #permissionTypes = #permissionTypes + ', ' + quotename(PermissionType)
from [dbo].[Test]
set #permissionTypes = RIGHT(#permissionTypes, len(#permissionTypes)-2)
set #sql = concat(
'select *
from [dbo].[Test]
pivot
(
max(isallowed)
for PermissionType in (', #permissionTypes, ')
) piv '
)
exec(#sql)
Result:
Adding a new row:
insert into [dbo].[Test] values
('IsNew' , 1)
Causes a new column to be created:

sql server sort dynamic pivot on large set of data

I am having trouble sorting a pivot based on a quite large set of data. I have looked at many examples, but none of them seems to address the issue of volume - or perhaps I am just missing something. I have had a very good look here: Sort Columns For Dynamic Pivot and PIVOT in sql 2005 and found much good advise, but I still cannot find the correct way to sort my pivot.
I am using the following sql. It pivots the columns, but the result needs to be sorted for readability:
SELECT a.* INTO #tempA
FROM (SELECT top (5000) id, email, CONVERT(varchar,ROW_NUMBER() OVER
(PARTITION BY email ORDER BY id)) AS PIVOT_CODE FROM Email) a
order by PIVOT_CODE
DECLARE #cols AS NVARCHAR(MAX),
#sql AS NVARCHAR(MAX)
SELECT #cols =STUFF((SELECT DISTINCT ', ' + QUOTENAME(col)
FROM #tempA WITH (NOLOCK)
cross apply
(
SELECT 'id_' + PIVOT_CODE, id
) c (col, so)
group by col, so
--order by col
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #sql = 'SELECT email, '
+#cols+
'INTO ##AnotherPivotTest FROM
(
SELECT email,
col,
value
FROM #tempA WITH (NOLOCK)
cross apply
(
values
(''id_'' + PIVOT_CODE, id)
) c (col, value)
) d
pivot
(
max(value)
for col in ('
+ #cols+
')
) piv'
EXEC (#sql)
SELECT * FROM ##AnotherPivotTest
The result is a chaos to look at:
==============================================================================================
| email | id_19 | id_24 | id_2 | id_16 | id_5 | id_9 | id_23 | .... | id_1 | .... | id_10 |
==============================================================================================
| xx#yy.dk | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 1234 | NULL | NULL |
==============================================================================================
I would very much like the Ids to be sorted - beginning with id_1.
As you can see, I have attempted to place an 'order by' in the selection for 'cols', but that gives me the error: "ORDER BY items must appear in the select list if SELECT DISTINCT is specified." And without DISTINCT, I get another error: "The number of elements in the select list exceeds the maximum allowed number of 4096 elements."
I'm stuck, so any help will be greatly appreciated!
Not sure what causes the problem but I've solved my order problem in my pivot table by inserting the data coming from tempA into another temp table and ordering them there
INSERT INTO #tempB
SELECT * FROM #tempA
ORDER BY PIVOT_CODE
Then selecting distinct ones like so:
SELECT #cols = #cols + QUOTENAME(PIVOT_CODE) + ',' FROM (SELECT DISTINCT PIVOT_CODE FROM #tempB ORDER BY PIVOT_CODE)
SELECT #cols = SUBSTRING(#cols, 0, LEN(#cols)) --trims "," at end
You can also just use a cursor to determine your cols and the order them
Cursor with cols ordered
declare #gruppe nvarchar(max)
declare #gruppeSql nvarchar(max)
declare #SQL nvarchar(max)
DECLARE myCustomers CURSOR FOR
select top 10 FirstName from [dbo].[DimCustomer] Order by FirstName
set #gruppeSql = ''
OPEN myCustomers
FETCH NEXT FROM myCustomers INTO #gruppe
IF (##FETCH_STATUS>=0)
BEGIN
SET #gruppeSql = #gruppeSql +'[' +#gruppe+']'
FETCH NEXT FROM myCustomers INTO #gruppe
END
WHILE (##FETCH_STATUS<>-1)
BEGIN
IF (##FETCH_STATUS<>-2)
SET #gruppeSql = #gruppeSql + ',[' +#gruppe+']'
FETCH NEXT FROM myCustomers INTO #gruppe
END
CLOSE myCustomers
DEALLOCATE myCustomers
SET #gruppeSql = replace(#gruppesql,'''','')
/*Select to preview your cols*/
select #gruppeSql
Dynamic pivot
SET #SQL = '
 Select *
from
(
SELECT SalesAmount, FirstName
FROM [AdventureWorksDW2014].[dbo].[FactInternetSales] a inner join dbo.DimCustomer b on a.CustomerKey = b.CustomerKey
) x
pivot
(
sum(SalesAmount)
for FirstName in ('+#gruppesql+')
) p'
print #sql
exec(#sql)

Split comma separated value from table column into rows using mssql?

I would like to get some data into mssql view by splitting their sources. I have some columns where phone numbers are stored as comma separated values (each contains a phone contact). I neet to work with each "phone contact", so I would like to see them in rows each one. And also each row has to contain an order of the contact from the splitting.
Source:
Department | SaleMngrs | Operators | Secretary
----------------------------------------------------------
'Technics' | '123,456,77'| '+122,Line 1' | '77889,112'
'Development'| '123,3366' | null | 'Lines 7-8'
As you can see, the comma separated values are a total mess, but the spliter is , (comma).
Wanted result:
Department | TypeOfContact | Contact | ContactOrder
------------------------------------------------------
'Technics' | 'SalesManagers'| '123' | 1
'Technics' | 'SalesManagers'| '456' | 2
'Technics' | 'SalesManagers'| '77' | 3
'Technics' | 'Operators' | '+122' | 1
'Technics' | 'Operators' | 'Line 1' | 2
'Technics' | 'Secretary' | '77889' | 1
'Technics' | 'Secretary' | '112' | 2
'Development'| 'SalesManagers'| '123' | 1
'Development'| 'SalesManagers'| '3366' | 2
'Development'| 'Secretary' | 'Lines 7-8'| 1
No UDF or SP wanted. Just a SELECT please.
Please try the following (it is as pretty as the data structure) - optimized with UNPIVOT:
set nocount on
declare #source table (Department varchar(50), SaleMngrs varchar(50), Operators varchar(50), Secretary varchar(50));
insert into #source values ('Technics' , '123,456,77', '+122,Line 1' , '77889,112');
insert into #source values ('Development', '123,3366' , null , 'Lines 7-8');
;WITH cte (Department, TypeOfContact, Contact)
AS
(
SELECT Department, TypeOfContact, cast('<Contact><c>' + replace(Contact,',','</c><c>') + '</c></Contact>' as xml) AS Contact
FROM (SELECT Department, SaleMngrs AS SalesManagers, Operators, Secretary FROM #source) p
UNPIVOT (Contact FOR TypeOfContact IN (SalesManagers, Operators, Secretary)) AS unpvt
)
Select Department
, TypeOfContact
, Contact.c.value('.','varchar(20)') AS Contact
, ROW_NUMBER() OVER (PARTITION BY Department, TypeOfContact ORDER BY Department, TypeOfContact) AS ContactOrder
FROM cte CROSS APPLY Contact.nodes('/Contact/c') as Contact(c);
OUTPUT
Department TypeOfContact Contact ContactOrder
------------ ------------- -------------------- --------------------
Development SalesManagers 123 1
Development SalesManagers 3366 2
Development Secretary Lines 7-8 1
Technics Operators +122 1
Technics Operators Line 1 2
Technics SalesManagers 123 1
Technics SalesManagers 456 2
Technics SalesManagers 77 3
Technics Secretary 112 1
Technics Secretary 77889 2
EDIT: Optimized query using UNPIVOT (original below):
set nocount on
declare #source table (Department varchar(50), SaleMngrs varchar(50), Operators varchar(50), Secretary varchar(50));
insert into #source values ('Technics' , '123,456,77', '+122,Line 1' , '77889,112');
insert into #source values ('Development', '123,3366' , null , 'Lines 7-8');
;WITH cte (Department, SalesMngrs, Operators, Secretary)
AS
(
select Department
, cast('<SaleMngrs><c>' + replace(SaleMngrs,',','</c><c>') + '</c></SaleMngrs>' as xml) AS SalesMngrs
, cast('<Operators><c>' + replace(Operators,',','</c><c>') + '</c></Operators>' as xml) AS Operators
, cast('<Secretary><c>' + replace(Secretary,',','</c><c>') + '</c></Secretary>' as xml) AS Secretary
from #source
)
Select Department
, TypeOfContact
, Contact
, ROW_NUMBER() OVER (PARTITION BY Department, TypeOfContact ORDER BY Department, TypeOfContact) AS ContactOrder
FROM (
Select Department, 'SalesManagers' AS TypeOfContact, SaleMngrs.c.value('.','varchar(20)') as Contact
from cte CROSS APPLY SalesMngrs.nodes('/SaleMngrs/c') as SaleMngrs(c)
union
Select Department, 'Operators', Operators.c.value('.','varchar(20)')
from cte CROSS APPLY Operators.nodes('/Operators/c') as Operators(c)
union
Select Department, 'Secretary', Secretary.c.value('.','varchar(20)')
from cte CROSS APPLY Secretary.nodes('/Secretary/c') as Secretary(c)
) AS q;
Martin,
I know you wanted this in one SQL Statement, but if you create a Function then the SQL won't be so unpretty.
ALTER FUNCTION dbo.Split ( #InputString VARCHAR(8000), #Delimiter VARCHAR(50))
RETURNS #Items TABLE ( Item VARCHAR(8000), Rowid INT)
AS
BEGIN
IF #Delimiter = ' '
BEGIN
SET #Delimiter = ','
SET #InputString = REPLACE(#InputString, ' ', #Delimiter)
END
IF (#Delimiter IS NULL OR #Delimiter = '')
SET #Delimiter = ','
DECLARE #Item VARCHAR(8000)
DECLARE #ItemList VARCHAR(8000)
DECLARE #DelimIndex INT
declare #rowseq INT
SET #rowseq = 0
SET #ItemList = #InputString
SET #DelimIndex = CHARINDEX(#Delimiter, #ItemList, 0)
WHILE (#DelimIndex != 0)
BEGIN
SET #Item = SUBSTRING(#ItemList, 0, #DelimIndex)
SET #rowseq = #rowseq + 1
INSERT INTO #Items VALUES (#Item, #rowseq)
-- Set #ItemList = #ItemList minus one less item
SET #ItemList = SUBSTRING(#ItemList, #DelimIndex+1, LEN(#ItemList)-#DelimIndex)
SET #DelimIndex = CHARINDEX(#Delimiter, #ItemList, 0)
END -- End WHILE
IF #Item IS NOT NULL -- At least one delimiter was encountered in #InputString
BEGIN
SET #Item = #ItemList
SET #rowseq = #rowseq + 1
INSERT INTO #Items VALUES (#Item, #rowseq)
END
-- No delimiters were encountered in #InputString, so just return #InputString
ELSE INSERT INTO #Items VALUES (#InputString, 1)
RETURN
END
The above Function I found from this SO question, but i made some changes to it for your scenario. How to split a comma-separated value to rows
Then you SQL will be...
SELECT department, 'SalesManager' as TypeOfContract, s.Item as Contact , s.rowId
FROM <YOUR TABLE> t
CROSS APPLY
(SELECT * FROM dbo.Split(t.SalesMngrs, ',') where item is not null) S
UNION ALL
SELECT department, 'Operators' as TypeOfContract, s.Item as Contact , s.rowId
FROM <YOUR TABLE> t
CROSS APPLY
(SELECT * FROM dbo.Split(t.Operators, ',') where item is not null ) S
UNION ALL
SELECT department, 'Secretary' as TypeOfContract, s.Item as Contact , s.rowId
FROM <YOUR TABLE> t
CROSS APPLY
(SELECT * FROM dbo.Split(t.Secretary, ',') where item is not null) S
I hope this helps.

TSQL creating a dynamic report from two tables, one table is holds the headers, other one, data

Imagine a scenario in which I want to get a dynamic report from [FormValues] as data, based on [Title] column of [ReportItems] as header.
I'm really confused how to do it and tried many ways, but none of them work fine.
I should be able to give a procedure a [ReportID] and get the result.
[FormID] and [FieldID] are relational keys in between two tables.
Any kind help would be highly appreciated.
CREATE TABLE #ReportItems(
ReportItemID [uniqueidentifier] NOT NULL primary key,
ReportID [uniqueidentifier] NOT NULL,
FormID [uniqueidentifier] NOT NULL,
FieldID [uniqueidentifier] NOT NULL,
Title nvarchar(100) NOT NULL
)
GO
insert into #ReportItems
select '5674d274-b146-4251-be0d-a15000e7cefa', '597d37c0-563b-42f0-99be-a15000dc7a65', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7a', 'First Name'
insert into #ReportItems
select '5674d274-b146-4252-be0d-a15000e7cefa', '597d37c0-563b-42f0-99be-a15000dc7a65', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7b', 'Last Name'
insert into #ReportItems
select '5674d274-b146-4253-be0d-a15000e7cefa', '597d37c0-563b-42f0-99be-a15000dc7a65', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7c', 'Age'
GO
CREATE TABLE #FormValues(
ValueID uniqueidentifier NOT NULL primary key,
FormID uniqueidentifier NULL,
FieldID uniqueidentifier NOT NULL,
UserName nvarchar(100) NOT NULL,
Value nvarchar(max) null
)
GO
insert into #FormValues
select 'af6dc400-3972-49ff-9711-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7a', 'user 1', 'Mike'
insert into #FormValues
select 'af6dc400-3972-49ff-9721-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7b', 'user 1', 'Oscar'
insert into #FormValues
select 'af6dc400-3972-49ff-9731-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7c', 'user 1', '20'
insert into #FormValues
select 'af6dc400-3972-49ff-9741-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7a', 'user 2', 'Merry'
insert into #FormValues
select 'af6dc400-3972-49ff-9761-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7c', 'user 2', '23'
insert into #FormValues
select 'af6dc400-3972-49ff-9771-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7a', 'user 3', 'Alen'
insert into #FormValues
select 'af6dc400-3972-49ff-9781-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7b', 'user 3', 'Escott'
insert into #FormValues
select 'af6dc400-3972-49ff-9791-a1520002359e', '01304636-fabe-4a3e-9487-a14b012f9a61', 'ba6b9b1a-92ef-4905-830a-a15000d05f7c', 'user 3', '28'
GO
Select * from #ReportItems
Select * from #FormValues
GO
And I want such a report as result:
User Name | First Name | Last Name | Age
User 1 | Mike | Oscar | 20
User 2 | Merry | | 23
User 3 | Alen | Escott | 28
User n | ... | ... | ...
drop table #ReportItems
drop table #FormValues
To get the result that you want, you will need to use the PIVOT function.
If all of your values (title) are known ahead of time, then you can hard-code a static query:
select *
from
(
select r.Title, f.UserName, f.Value
from ReportItems r
left join FormValues f
on r.FormID = f.FormID
and r.FieldID = f.FieldID
) src
pivot
(
max(value)
for title in ([First Name], [Last Name], Age)
) piv;
See SQL Fiddle with Demo.
But it sounds like you will have an unknown number of titles that you want to turn into columns. If that is the case, then you will want to use dynamic sql:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(Title)
from ReportItems
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT UserName,' + #cols + ' from
(
select r.Title, f.UserName, f.Value
from ReportItems r
left join FormValues f
on r.FormID = f.FormID
and r.FieldID = f.FieldID
) x
pivot
(
max(value)
for Title in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
The result for both would be:
| USERNAME | FIRST NAME | LAST NAME | AGE |
-------------------------------------------
| user 1 | Mike | Oscar | 20 |
| user 2 | Merry | (null) | 23 |
| user 3 | Alen | Escott | 28 |
If you have a specific SortOrder that you need and you have it stored in a table, then when you are getting your list of columns, you will use the following and it will return the columns in the correct order:
select #cols = STUFF((SELECT ',' + QUOTENAME(Title)
from ReportItems
group by Title, sortorder
order by sortorder
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
See SQL Fiddle with Demo
DECLARE #SQL NVARCHAR(MAX)
SET #SQL='SELECT F.UserName'
SELECT #SQL = #SQL+', MAX(CASE WHEN FieldID='''+CONVERT(VARCHAR(50), FieldID)+''' THEN F.Value END) AS ['+Title+']
'
FROM #ReportItems
SET #SQL = #SQL+' FROM #FormValues F GROUP BY F.UserName ORDER BY 1'
--select #sql
EXEC sp_ExecuteSQL #SQL
Later edit: procedure for report based on Report ID an sort columns
CREATE PROCEDURE spReport
#ReportID uniqueidentifier,
#SortColumns NVARCHAR(MAX) --shoud be a comma separated list of ReportItems.Title
AS
BEGIN
DECLARE #SQL NVARCHAR(MAX)
SET #SQL='SELECT F.UserName'
SELECT #SQL = #SQL+', MAX(CASE WHEN F.FieldID='''+CONVERT(VARCHAR(50), FieldID)+''' THEN F.Value END) AS ['+Title+']
'
FROM ReportItems
WHERE ReportID=#ReportID --create the dynamic sql only for the items in your report
SET #SQL = #SQL+' FROM FormValues F
JOIN ReportItems R ON F.FormID=R.FormID
WHERE R.ReportID = #ReportID
GROUP BY F.UserName '
IF #SortColumns<>''
SET #SQL = #SQL + 'ORDER BY '+#SortColumns -- beware of SQL injection.
select #sql
EXEC sp_ExecuteSQL #SQL, N'#ReportID uniqueidentifier', #ReportID=#ReportID
END
But, and I can't stress this enough, you have to pay special attention to then #SortColumns parameter, because you're opening yourself to SQL Injection.