Dynamic Pivot Columns with related tables in Sql Server - sql

I am attempting to use a Pivot to dynamically create columns and am not getting the proper results I am looking for. I need to show all possible answers in its own column for each question (row), and there are multiple questions in a course. How can I generate each possible answer as a column name, and filter by courseId and the questionId for each row? The number of possible answers varies based on the question. Should this be done via cursor instead of pivot?
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#courseID float(24) = 1
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(questionID)
FROM answers
INNER JOIN questions ON questions.questionID = answers.questionID
WHERE questions.courseId = #courseID
order by 1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
set #query = 'SELECT * from
(
select Q.courseId, C.courseName, Q.question, Q.questionID, A.answer
from questions Q
inner join courses C ON Q.courseId = C.courseId
inner join answers A ON A.questionID = Q.questionID
where (Q.courseId = ''' + Str(#courseID) + ''')
) x
pivot
(
max(answer)
for questionId in (' + #cols + ')
) AS p'
execute(#query)
When running the above I am getting:
[1],[10],[11],[12],[13],[14],[15],[16],[2],[3],[4],[5],[6],[7],[8],[9]
As the generated columns which are the questionIds and not 'Answer 1, Answer 2 ... etc', and they are not displaying in order.
For the sake of brevity here are the related columns in the database tables:
Courses
courseId
Questions
questionId question courseId
Answers
AnswerId questionId answer
Any help is appreciated.

You didn't provide a many details on your table structure, but if you want the answers as the columns i.e. Answer1, Answer2, etc then you need to create the columns using row_number() against the number of answers per question similar to the following:
SET #cols = STUFF((SELECT ',' + QUOTENAME('Answer'+cast(seq as varchar(10)))
FROM
(
select row_number() over(partition by q.questionid
order by a.answer) seq
from answers a
INNER JOIN questions q
ON q.questionID = a.questionID
WHERE q.courseId = #courseID
) d
group by seq
order by seq
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
This creates a sequence for each answer per question and then that sequence number is used to create the new columns names. Then you will alter your PIVOT code to:
set #query = 'SELECT *
from
(
select Q.courseId, C.className, Q.question, Q.questionID, A.answer,
''Answer''+cast(row_number() over(partition by q.questionid
order by a.answer) as varchar(10)) seq
from questions Q
inner join classes C
ON Q.courseId = C.courseId
inner join answers A
ON A.questionID = Q.questionID
where (Q.courseId = ''' + Str(#courseID) + ''')
) x
pivot
(
max(answer)
for seq in (' + #cols + ')
) AS p'
execute(#query)

Related

How to properly create a Dynamic Pivot Function in SQL? [duplicate]

This question already has answers here:
Group by column and multiple Rows into One Row multiple columns
(2 answers)
Closed 8 months ago.
I come back with my problem. I've been trying to create a dynamic pivot procedure but I have some trouble while creating the columns.
Example:
TranID
Bank Name
NetAmount
Account
01
StormWindBank
23.0$
A
02
StormWindBank
14.0$
B
03
StormWindBank
00.0$
A
04
StormWindBank
12.0$
B
Result intended :
Account
Bank Name
NetAmount
NetAmount2
A
StormWindBank
23.0$
00.0$
B
StormWindBank
14.0$
12.0$
SELECT DISTINCT [Account]
,[Currency]
,TranID
,[Bank Code]
,[Bank Name]
,[Client No_]
,[NetAmount]
INTO #TEMP
FROM [My Table]
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.NetAmount)
FROM #TEMP c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [Account], [Bank Name], ' + #cols + ' from
(
select [Account],TranID, NetAmount, [Bank Name]
from #TEMP
) x
pivot
(
max(TranID)
for NetAmount in (' + #cols + ')
) p '
So far, I have the result below:
Account
Bank Name
23.0$
00.0$
A
StormWindBank
1
2
B
StormWindBank
3
4
The 1, 2, 3, 4 are the TranID.
I'm close but I have been struggling to fix that, anyone have an idea ?
Thank you!
You need to PIVOT off a derived column using row_number()
Example
Declare #SQL varchar(max) = stuff( ( Select Distinct concat(',[NetAmount',row_number() over (partition by [Bank Name],[Account] order by [TranID]),']' )
From #TEMP For XML Path('') ),1,1,'')
Set #SQL = '
Select *
From (
Select [Account]
,[Bank Name]
,Item = concat(''NetAmount'',row_number() over (partition by [Bank Name],[Account] order by [TranID]) )
,Value = [NetAmount]
from #TEMP
) src
Pivot ( max( [Value] ) for Item in ('+ #SQL +') ) pvt
'
Exec(#SQL)
Results
Account Bank Name NetAmount1 NetAmount2
A StormWindBank 23.0$ 00.0$
B StormWindBank 14.0$ 12.0$

Show value of particular column as Header in SQL Server

I am creating a web app in which I have a requirement where I want to display a column value as a header
Example
SELECT Name, Leave
FROM tblUser
INNER JOIN tblLeaveMaster ON tblUser.EmployeeID = tblLeaveMaster.EmployeeID
From that query, I get these results:
Name Leave
---------------
Test1 5
Test2 10
test3 2
Now I want to get these values as
Test1 Test2 Test3
-----------------
5 10 2
How can I achieve this?
You can try using pivot
select pv.* from
(SELECT Name,Leave
FROM tblUser INNER JOIN tblLeaveMaster ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID
)X
pivot
(max(leave) for name in ([Test1],[Test2],[Test3])) as pv
For Dynamic PIVOT
declare #sql varchar(max)='',#col_list varchar(8000)=''
set #col_list = (select distinct quotename([Name])+',' from (SELECT Name,Leave
FROM tblUser INNER JOIN tblLeaveMaster ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID
)X
for xml path(''))
set #col_list = left (#col_list,len(#col_list)-1)
set #sql = 'select '+#col_list+' from
(SELECT Name,Leave
FROM tblUser INNER JOIN tblLeaveMaster ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID
)X
pivot (max([Leave]) for [Name] in ('+#col_list+'))pv'
exec(#sql)
try by using case when
select max( case when name='Test1' then Leave end) as test1,
max( case when name='Test2' then Leave end) as test2,
max( case when name='Test3' then Leave end) as test3 from
tblUser INNER JOIN tblLeaveMaster
ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID
You can try to use condition aggregate function. CASE WHEN with MAX or MIN
SELECT
MAX(CASE WHEN Name = 'Test1' THEN Leave END) Test1,
MAX(CASE WHEN Name = 'Test2' THEN Leave END) Test2,
MAX(CASE WHEN Name = 'Test3' THEN Leave END) Test3
FROM tblUser
INNER JOIN tblLeaveMaster ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID
EDIT
If your column want to create dynamic you can try to use Dynamic PIVOT
create your SQL statement and make condition aggregate function by connect SQL string. then use execute it Dynamically.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ', MAX(CASE WHEN Name = ''' + Name+''' THEN Leave END) ' + QUOTENAME(Name)
FROM tblUser
INNER JOIN tblLeaveMaster ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query= 'SELECT '+ #cols+'
FROM tblUser
INNER JOIN tblLeaveMaster ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID'
execute(#query)
sqlfiddle
You can find your result from the query as shown below. Here I have taken your query output in a temporary table.
Create table #finalData(ColName Varchar(30), Leave INT)
INSERT INTO #finalData Values('Test1', 5),('Test2', 10),('Test3', 2)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.ColName)
FROM #finalData c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + ' from
(
select ColName
, Leave
from #finalData
) x
pivot
(
max(Leave)
for ColName in (' + #cols + ')
) p '
execute(#query)
DROP TABLE #finalData
Hope this will help you.
The output is as shown below
Test1 Test2 Test3
5 10 2
select pv.* from
(SELECT Name,Leave
FROM tblUser INNER JOIN tblLeaveMaster ON tblUser.EmployeeID=tblLeaveMaster.EmployeeID
)X
pivot
(max(leave) for name in ([Test1],[Test2],[Test3])) as pv
Note:- this work for me but how to use where condition base on dropdown list selected value.
eg: if wants to show only 2020 and not 2019.

Restriction to use DECLARE in a SQL view

I have a sql query that I need to put into a view. I have it working as a stored procedure but my requirement is for it to be a View. This means I cant declare dynamic SQL as I have done in my stored procedure.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(t.Data_Term)
from [UD_FldValues] t
inner join Surgery p
on t.Ud_Form_Id = p.Ud_Form_Id where t.Data_term!=''
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
set #query = 'SELECT Tran_Id,Section_Id,SubSection_Id,R_Id,Hospital_Id,Surgeon_Id,Procedure_ICD,Procedure_Details,DtSurgery_TimeIn,DtSurgery_TimeOut,Intra_oper_compl_Id, p.Discharge_Date,Discharge_Status_Id,Entered_By,Entered_Date,p.Status,p.Ud_Form_Id,p.Complication_ICD,p.Complication_Name, Procedure_Name, ' + #cols + ' from
(
select t.Tran_Id
, t.[R_Id]
, p.Ud_Form_Id
,p.Procedure_Name
,p.Section_Id
,p.SubSection_Id
,p.Hospital_Id
,p.Surgeon_Id
,p.Procedure_ICD
,p.Procedure_Details
,p.DtSurgery_TimeIn
,p.DtSurgery_TimeOut
,p.Intra_oper_compl_Id
,p.Discharge_Date
,p.Discharge_Status_Id
,p.Entered_By,
p.Entered_Date
,p.Status
,p.Complication_ICD
,p.[Complication_Name]
, case when (t.Data_Type=''Text'') THEN t.Text_Value Else Convert(varchar(MAx),Num_Value) END as txtvl
,t.Data_term
from Surgery p
left outer join [UD_FldValues] t
on t.Ud_Form_Id = p.Ud_Form_Id and t.Tran_Id=p.Surgery_Id
left outer join UD_Formmap fm ON fm.Ud_Form_Id = p.Ud_Form_Id
where fm.module_id = 1
) x
pivot
(
min(txtvl)
for Data_Term in (' + #cols + ')
) p '
exec(#query)
Please help me find an alternate way, but not store procedures. I need it in view
for a view it needs to be a single select statement,
your #cols statement returns a list of columns, this is used twice within your main select statement. so replacing #cols in your main query with the query that populates this should work.
set #query and then exec(#query) isnt necessary either so you just have to tidy up and make sure the right number of brackets are in place.
and it should look something like this.
SELECT Tran_Id
,Section_Id
,SubSection_Id
,R_Id,Hospital_Id
,Surgeon_Id,Procedure_ICD
,Procedure_Details
,DtSurgery_TimeIn
,DtSurgery_TimeOut
,Intra_oper_compl_Id
,p.Discharge_Date
,Discharge_Status_Id
,Entered_By,Entered_Date
,p.Status,p.Ud_Form_Id
,p.Complication_ICD
,p.Complication_Name
,Procedure_Name,
(select STUFF((SELECT distinct ',' + QUOTENAME(t.Data_Term)
from [UD_FldValues] t
inner join Surgery p
on t.Ud_Form_Id = p.Ud_Form_Id where t.Data_term!=''
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');))
from
(
select t.Tran_Id
, t.[R_Id]
, p.Ud_Form_Id
,p.Procedure_Name
,p.Section_Id
,p.SubSection_Id
,p.Hospital_Id
,p.Surgeon_Id
,p.Procedure_ICD
,p.Procedure_Details
,p.DtSurgery_TimeIn
,p.DtSurgery_TimeOut
,p.Intra_oper_compl_Id
,p.Discharge_Date
,p.Discharge_Status_Id
,p.Entered_By,
p.Entered_Date
,p.Status
,p.Complication_ICD
,p.[Complication_Name]
, case when (t.Data_Type=''Text'') THEN t.Text_Value Else Convert(varchar(MAx),Num_Value) END as txtvl
,t.Data_term
from Surgery p
left outer join [UD_FldValues] t
on t.Ud_Form_Id = p.Ud_Form_Id and t.Tran_Id=p.Surgery_Id
left outer join UD_Formmap fm ON fm.Ud_Form_Id = p.Ud_Form_Id
where fm.module_id = 1
) x
pivot
(
min(txtvl)
for Data_Term in (
select STUFF((SELECT distinct ',' + QUOTENAME(t.Data_Term)
from [UD_FldValues] t
inner join Surgery p
on t.Ud_Form_Id = p.Ud_Form_Id where t.Data_term!=''
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');)
) p

How do i join the rows into dynamic columns in this case? [duplicate]

This question already has an answer here:
Pivot Dynamic Columns, no Aggregation
(1 answer)
Closed 9 years ago.
I'm sorry to post this, what i feel, almost duplicate. But i've tried the solutions i've found but haven't gotten it to work in my solution :(
This is how the SQL looks before i FUBARed it. This returns the data in a pretty good format. But i get duplicate rows with the same data except that the questions and answers are changed. I'd like that the Question would be the column name and the answer it's value.
SELECT c.*, sa.Question, sa.Answer
FROM Customers as c, Surveys s, SurveyAnswers sa
WHERE c.OrderID IN(SELECT id FROM #orders)
AND s.CustomerID = c.id
AND sa.SurveyID = s.ID
My SQL is weak and i got to get this done asap :( The alternative is to do the more heavy lifting in the .net app but i'd be nice to get the data directly
Best regards,
Mikael
You want to use Bluefeet's answer here to accomplish a dynamic column pivot. (I've omitted your #orders filter for brevity):
DECLARE
#cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(sa.Question)
FROM SurveyAnswers sa
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT CustomerID, Name, ' + #cols + ' from
(
select
c.ID as CustomerId,
c.Name,
sa.Question,
sa.Answer
FROM Customers as c
INNER JOIN Surveys s ON s.CustomerID = c.id
INNER JOIN SurveyAnswers sa ON sa.SurveyID = s.ID
) x
pivot
(
min(Answer)
for Question in (' + #cols + ')
) p '
execute(#query);
SqlFiddle here

SQL Pivot table returning NULL for non-existent child table values

I have a typical RDMS setup where records in a main table can have optional records in a related table via a M2M join. I'm trying to PIVOT this data but in cases where there is no relation I want to return a default value. The join I have below is returning NULL.
select *
from
(
SELECT s.Biz_Name, la.Name AS Association, ISNULL(i.Location, 'Default') as Location
FROM dbo.ShopAssociations sa
INNER JOIN dbo.LookupAssociations la
ON sa.AssociationID = la.AssociationID
RIGHT JOIN dbo.Basic_Shop_Info s
ON sa.ShopID = s.ShopID
INNER JOIN dbo.Images i
ON la.ImageID = i.ImageID
) DataTable
PIVOT
(
min(Location)
for association in
([OnCall],[OCGuy],[ASCLogo],[ASC_OtherSt],[ASE],[AASP],[AASP_PA],
[ASE_BlueSeal],[AAA],[AAA-B],[ASA],[ATRA],[ICAR],[CAA],[ACDelco],
[Cert],[ASC],[BBB],[Goodyear],[Limos],[RVs],[Bosch],[NARSA],
[DiscTire],[BigO],[Tires],[Firestone],[ASCCA],[JustTires],[ASE_Blue])
) PivotTable
The output looks like this:
BizName OnCall OCGuy ASCLogo ASC_OtherSt ASE ...
"Wonderful Biz" somevalue somevalue NULL somevalue NULL
What I am trying to achieve is if a child record doesn't exist in INNER JOIN from Basic_Shop_Info to ShopAssociations that we get "Default" instead of NULL. I've tried ISNULL(), Coalesce() and even a CASE statement, all with the same results.
Based on your comment it sounds like you found a solution. I am only answering this to provide a suggestion based on the fact you are pivoting so many columns and they are all hard-coded. You can use dynamic SQL for a PIVOT and your query would look something like this:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(Name)
from dbo.LookupAssociations
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsPivot = STUFF((SELECT distinct ', IsNull(' + QUOTENAME(Name) +', ''Default'')'
from dbo.LookupAssociations
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT Bizname, ' + #colsPivot + ' from
(
SELECT s.Biz_Name, la.Name AS Association, ISNULL(i.Location, ''Default'') as Location
FROM dbo.ShopAssociations sa
INNER JOIN dbo.LookupAssociations la
ON sa.AssociationID = la.AssociationID
RIGHT JOIN dbo.Basic_Shop_Info s
ON sa.ShopID = s.ShopID
INNER JOIN dbo.Images i
ON la.ImageID = i.ImageID
) x
pivot
(
min(Location)
for association in (' + #cols + ')
) p
'
execute(#query)
The value #colsPivot is adding the IsNull() around each of you columns so you can put in place the Default value. But this should provide the same result as your original query where everything was hard-coded.
This will get the list of columns at run-time so then you do not have to hard-code anything and it will accept new values without having to change the query.
I got this:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(c.col+cast(rn as varchar(10)))
from
(
select row_number() over(partition by person_nbr
order by person_nbr,first_name, last_name, medication_name) rn
from TA_PIVOT
) d
cross apply
(
select 'diag' col, 1 sort
) c
group by col, rn, sort
order by rn, sort
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT person_nbr, first_name, last_name,medication_name,' + #cols + '
from
(
select person_nbr,first_name,last_name,medication_name,
col+cast(rn as varchar(10)) col,
value
from
(
-- when you perform an unpivot the datatypes have to be the same.
-- you might have to cast the datatypes in this query
select person_nbr,first_name,last_name, medication_name, cast(icd_code_id as varchar(500)) diag,
row_number() over(partition by person_nbr order by person_nbr, first_name, last_name,medication_name) rn
from ta_pivot
) src
unpivot
(
value
for col in (diag)
) unpiv
) d
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);