Error in Dynamic PIVOT Table in SQL - sql

I am getting error in Dynamic PIVOT Table.
I have a Tables like
TT_Child
TTChild_ID TT_ID Roll_No Std_Reg_ID Attendance
1 3 1 22 1
2 3 2 23 0
and Table
TT_Master
TT_ID Attend_date Time_from Time_To Course_ID Faculty_ID Acad_Year Subject_ID
1 2014-03-01 10:00 11:00 1 16 2013-2014 34
2 2014-03-02 10:00 11:00 1 16 2013-2014 34
3 2014-03-03 10:00 11:00 1 16 2013-2014 34
Student_Registration_Master
Std_Reg_ID Stud_FNAME stud_MNAME Stud_MNAME
--I am using this PIVOT query but getting this error. I have to use Attend_Date and Time as a Column Name.
Create PROCEDURE [dbo].[Attendance_Test]
#courseid as int=null, #acadyear nvarchar(15)=null
AS
Declare #colList varchar(max)
Declare #qry varchar(max)
SET #colList = STUFF((SELECT distinct ',' + QUOTENAME(CONVERT(VARCHAR(19), TTM.Attend_Date, 103)) + ' '+ QUOTENAME(TTM.Time_From)
FROM TT_Child SA
inner join TT_Master TTM on SA.TT_ID = TTM.TT_ID
where (TTM.Course_ID = #courseid)
FOR XML PATH(''), TYPE).value('/', 'NVARCHAR(MAX)') ,1,1,'')
SET #qry = 'SELECT SA.Reg_ID, STUD_FNAME + STUD_MNAME + STUD_LNAME as [Student Name], '+#colList+'
FROM
(
select SA.Reg_ID, SR.STUD_FNAME, SR.STUD_MNAME, SR.STUD_LNAME, TTM.Attend_Date , SA.Attendance from TT_Child SA
inner join TT_Master TTM on SA.TT_ID = TTM.TT_ID
inner join STUDENT_Registration_MASTER SR on CR.Reg_ID = SR.STUD_Reg_ID
where (TTM.Course_ID = '+cast(#courseid as varchar(50))+ ') and (TTM.Acad_Year = '''+#acadyear+''')
group by SA.Reg_ID, SR.STUD_FNAME, SR.STUD_MNAME, SR.STUD_LNAME, TTM.Attend_Date, SA.Attendance
) as s
PIVOT
(
MAX(Attendance) FOR Attend_Date IN (' + #colList + ')
) pvt'
print(#qry)
Exec(#qry)
-- I am getting this error message
SELECT SA.Reg_ID, STUD_FNAME + STUD_MNAME + STUD_LNAME as [Student Name], [01/03/2014] [10:00:00.0000000],[02/03/2014] [10:00:00.0000000],[03/03/2014] [10:00:00.0000000],[05/03/2014] [10:00:00.0000000],[05/03/2014] [11:00:00.0000000]
FROM
(
select SA.Reg_ID, SR.STUD_FNAME, SR.STUD_MNAME, SR.STUD_LNAME, TTM.Attend_Date , SA.Attendance from TT_Child SA
inner join TT_Master TTM on SA.TT_ID = TTM.TT_ID
inner join STUDENT_Registration_MASTER SR on CR.Reg_ID = SR.STUD_Reg_ID
where (TTM.Course_ID = 1) and (TTM.Acad_Year = '2013-2014')
group by SA.Reg_ID, SR.STUD_FNAME, SR.STUD_MNAME, SR.STUD_LNAME, TTM.Attend_Date, SA.Attendance
) as s
PIVOT
(
MAX(Attendance) FOR Attend_Date IN ([01/03/2014] [10:00:00.0000000],[02/03/2014] [10:00:00.0000000],[03/03/2014] [10:00:00.0000000],[05/03/2014] [10:00:00.0000000],[05/03/2014] [11:00:00.0000000])
) pvt
Error Message
Msg 102, Level 15, State 1, Line 12
Incorrect syntax near '10:00:00.0000000'.
Plz give solution

When you review this line of your code you will see the error:
MAX(Attendance)
FOR Attend_Date IN ([01/03/2014] [10:00:00.0000000],[02/03/2014] [10:00:00.0000000],
[03/03/2014] [10:00:00.0000000],[05/03/2014] [10:00:00.0000000],
[05/03/2014] [11:00:00.0000000])
The date and the time have each been wrapped in square brackets separately so you will get an error.
You need to concatenate the date and time together first, then wrap in square brackets using QUOTENAME. You should be able to change the code to:
SET #colList
= STUFF((SELECT distinct ',' + QUOTENAME(CONVERT(VARCHAR(19), TTM.Attend_Date, 103) + ' '+ TTM.Time_From)
FROM TT_Child SA
inner join TT_Master TTM on SA.TT_ID = TTM.TT_ID
where (TTM.Course_ID = #courseid)
FOR XML PATH(''), TYPE).value('/', 'NVARCHAR(MAX)') ,1,1,'')
See SQL Fiddle with Demo of your version and this new version.

Related

Count distinct in for xml path

I am using the below query to get count of total number of employees that visited the outlet in each day of the month so the output will be like below
Outlet 12/01/2017 12/02/2017 -- rest days of the month
Outlet1 6 5
Outlet2 4 3
but the issue i have some duplicate values so i have to use count distinct
in the below query but it give the following error
Msg 156, Level 15, State 1, Line 1
Incorrect syntax near the keyword 'distinct'.
Note if i removed distinct the query works fine
Query
DECLARE #cols AS nvarchar(max),
#query AS nvarchar(max)
SELECT
#cols = STUFF((SELECT
',' + QUOTENAME(LogDate)
FROM dbo.AccessLog
WHERE month(CONVERT(datetime,LogDate)) = 12
and year(CONVERT(datetime,LogDate)) = 2017
and AccessLog.InOut = 0
GROUP BY LogDate
ORDER BY LogDate
FOR xml PATH (''), TYPE)
.value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #query = 'SELECT abr as Outlet,' + #cols +
' from (select abr,LogDate,TerminalID,EmployeeID
from AccessLog
INNER JOIN dbo.Outlet
ON dbo.Outlet.Code = dbo.AccessLog.TerminalID
where AccessLog.InOut=0
and month(CONVERT(datetime,LogDate)) = ''12''
and year(CONVERT(datetime,LogDate)) = ''2017'') x
pivot (count(distinct EmployeeID)
for LogDate in (' + #cols + ') ) p '
EXECUTE (#query);

SQL Pivot - Dynamic Columns, No Aggregation

I'm trying to do a Pivot, but I'm not very experienced with pivots and I'm stuck - I can't figure out how to structure the query.
What I have:
Data Types (types of measurements that are recorded)
Locations
Data Sources (things at each location that will be measured)
Data Readings (measurements of the sources)
Additional information:
The number of Sources at any one Location can change
There will never be more than 5 sources at a single Location
Only 1 Reading is saved per Source/Type/date
In the returned table:
Table shows Data_Type info and Readings for a single Location and date
Columns: Data_Name, Units, Is_Required (from Data_Type table), plus one column for each Source
Rows: one row for each Data_Type
Rows should be ordered by Type_Display_Order
Sources (extra columns) should be ordered by Source_Display_Order
Some Readings are optional, and some Sources aren't measured daily - these still need to be included in the table
Example:
Table: Data_Type
Data_Type_ID Data_Name Units Is_Required (BIT) Type_Display_Order
-----------------------------------------------------------------------
1 Height In. 1 2
2 Length In. 0 3
3 Weight Lbs. 1 1
Table: Location
Location_ID Location
-----------------------
1 West
2 East
Table: Data_Source
Data_Source_ID Location_ID Source_Name Source_Display_Order
----------------------------------------------------------------
1 1 WCS 2
2 2 ECS 1
3 1 WBN 1
Table: Data_Reading
Data_Reading_ID Data_Type_ID Data_Source_ID Reading Reading_Date
----------------------------------------------------------------------
1 1 1 5 6/3/2016
2 3 2 3 5/1/2016
3 1 1 7 5/1/2016
4 2 3 2 6/3/2016
5 3 1 4 6/3/2016
Desired results from query for Location = "West", Date = 6/3/2016:
Data_Type_ID Data_Name Units Is_Required WBN WCS
---------------------------------------------------------
3 Weight Lbs. 1 NULL 4
1 Height In. 1 NULL 5
2 Length In. 0 NULL NULL
This solution seems to be similar: Pivot Dynamic Columns, no Aggregation but I'm still having some problems.
This is what I have so far:
DECLARE #date DATE, #locationID INT
SET #date = CAST('6/3/2016' AS DATE)
SET #locationID = 1
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(s.Source_Name)
FROM Data_Source s
WHERE s.Location_ID = #locationID
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = 'SELECT Data_Type_ID, Data_Name, Units, Is_Required, ' + #cols +
' FROM
(
SELECT
t.Data_Type_ID
, t.Data_Name
, t.Units
, t.Is_Required
, r.Reading
, s.Source_Name
FROM
Data_Type t
LEFT JOIN
Data_Reading r ON t.Data_Type_ID = r.Data_Type_ID
LEFT JOIN
Data_Source s ON r.Data_Source_ID = s.Data_Source_ID
WHERE
r.Reading_Date = CAST(CAST(' + #date + ' AS NVARCHAR(10)) AS DATE)
AND s.Location_ID = CAST(' + #locationID + ' AS INT)
) x
PIVOT
(
MIN(Reading)
for Source_Name in (' + #cols + ')
) p '
I have the query working properly now, but I still have a few problems:
#cols is not sorted by Source_Display_Order
rows are not sorted by Type_Display_Order (I did have ORDER BY in the inner SELECT statement for part X, but I was getting errors saying I can't have an ORDER BY clause there)
Date comparison in WHERE statement doesn't work - for some reason, it always computes as False, even when the dates are the same
Solved!
DECLARE #date DATE, #locationID INT
SET #date = CAST('6/3/2016' AS DATE)
SET #locationID = 1
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SET #cols = STUFF((SELECT ',' + QUOTENAME(s.Source_Name)
FROM Data_Source s
WHERE s.Location_ID = #locationID
ORDER BY s.Source_Display_Order
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query =
'SELECT
Data_Type_ID
, Data_Name
, Units
, Is_Required
, ' + #cols + '
FROM
(
SELECT
t.Data_Type_ID
, t.Data_Name
, t.Units
, t.Is_Required
, r.Reading
, s.Source_Name
, t.Type_Display_Order
FROM
Data_Type t
LEFT JOIN
Data_Reading r ON t.Data_Type_ID = r.Data_Type_ID
LEFT JOIN
Data_Source s ON r.Data_Source_ID = s.Data_Source_ID
WHERE
r.Reading_Date = ''' + CAST(#date AS NVARCHAR(10)) + '''
AND s.Location_ID = ' + CAST(#locationID AS NVARCHAR(20)) + '
) x
PIVOT
(
MIN(Reading)
for Source_Name in (' + #cols + ')
) p
ORDER BY
Type_Display_Order'
EXECUTE(#query)
To fix my problems:
Convert #date to NVARCHAR before adding to #query string and include extra quotes to surround the new NVARCHAR in quotes within #query
Remove DISTINCT clause from #cols and add ORDER BY (all of the names in my table are unique, so the DISTINCT is unnecessary)
Add Type_Display_Order to the inner SELECT statement, and add ORDER BY after the PIVOT statement

creating a pivot table using sql

I am trying to create a pivot table in sql but am having difficulties. Here is my problem: I have a column in my database called 'statusreason', and I need to provide a sum of each statusreason for the past week. My set is as follows:
I need to pivot this table so that it appears like the following:
There are a number of statusreasons that are not represented in the above table, since they did not occur in the past week.
The query used to generate the result set is:
select inv.statusreason
, count(inv.statusreason) as 'StatusCount'
from invoicetbl inv (nolock)
inner join trucktbl tru (nolock) on inv.tru_key = tru.tru_key
where inv.client_key = 123
and inv.createdate > getdate() - 7
group by inv.statusreason
If this isn't enough information, please advise what I could add to improve the question.
Thank you for any assistance you can provide.
Since you want to convert your rows of data into columns, you need to PIVOT the data. This can be done a number of ways.
If you have a limited number of values that you are going to be returning, then you can use an aggregate function with a CASE expression:
select
count(case when statusreason = 181 then 1 end) [181],
count(case when statusreason = 20 then 1 end) [20],
count(case when statusreason = 212 then 1 end) [212],
count(case when statusreason = 232 then 1 end) [232]
from
(
select inv.statusreason
from invoicetbl inv (nolock)
inner join trucktbl tru (nolock)
on inv.tru_key = tru.tru_key
where inv.client_key = 123
and inv.createdate > getdate() - 7
) d;
Or you can use the PIVOT function:
select [181], [20], [212], [232]
from
(
select inv.statusreason
from invoicetbl inv (nolock)
inner join trucktbl tru (nolock)
on inv.tru_key = tru.tru_key
where inv.client_key = 123
and inv.createdate > getdate() - 7
) d
pivot
(
count(statusreason)
for statusreason in ([181], [20], [212], [232])
) p;
If you have an unknown number of values that will be returned, then you will want to look at using dynamic SQL. This creates a sql string that will then be executed.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(statusreasons )
from statusreasontbl
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + '
from
(
select inv.statusreason
from invoicetbl inv (nolock)
inner join trucktbl tru (nolock)
on inv.tru_key = tru.tru_key
where inv.client_key = 123
and inv.createdate > getdate() - 7
) x
pivot
(
count(statusreason)
for statusreason in (' + #cols + ')
) p '
execute sp_executesql #query;

sql query to dynamically add fiscal month using pivot

ALTER PROCEDURE [dbo].[_sp_GetDMActivityTrackerReport]
#CoachId VARCHAR(7),
#Month INT,
#FiscalYear INT
AS
BEGIN
INSERT #FiscalMonth (ID,Month,NbHolidays,MonthDate,TotalDays)
EXECUTE dbo._sp_GetFiscalMonths #Month, #FiscalYear
SELECT PreparationID,CoachId,UserID, MemberID,
[Rep Name], isnull(April,0) April, isnull(May,0) May, isnull(June,0)June,
isnull(July,0) July, isnull(August,0) August, isnull(September,0) September,
isnull(October,0) October, isnull(November,0) November,
isnull(December,0) December, isnull(January,0) January, isnull(February,0) February,
isnull(March,0) March,isnull((isnull(November,0) + isnull(December,0) +
isnull(January,0) + isnull(February,0) + isnull(March,0) + isnull(April,0) +
isnull(May,0) + isnull(June,0) + isnull(July,0) + isnull(August,0) +
isnull(September,0) + isnull(October,0)),0) as [Total Field TIME]
FROM
(
SELECT up.PreparationID,tt.UserId [CoachId],up.UserID, utm.MemberID,
(ui.FirstName + ' ' + ui.LastName) AS [Rep Name],DateName(Month,nft.MonthPeriodStart) [Month], sum(nft.Quantity) [Days]
FROM TransferedTime tt
INNER JOIN UPreparation up ON tt.PreparationID = up.PreparationID
RIGHT JOIN UTeamMembers utm ON tt.UserId = utm.CoachID AND utm.MemberID = up.UserID
INNER JOIN UserInfo ui ON utm.MemberID = ui.UserID
LEFT JOIN NonFieldTime nft ON nft.UserId = tt.UserId
AND tt.MonthPeriodFrom = nft.MonthPeriodStart
AND datename(Month,nft.MonthPeriodStart) + '-'+ substring(datename(Year,nft.MonthPeriodStart),3,2) IN
(SELECT Month +'-' +substring(datename(Year,MonthDate),3,2) [Months] FROM #FiscalMonth)
WHERE utm.MemberID IN (SELECT MemberID FROM UTeamMembers WHERE CoachID = #CoachId)
GROUP BY up.PreparationID,tt.UserId,up.UserID, utm.MemberID,
(ui.FirstName + ' ' + ui.LastName),DateName(Month,nft.MonthPeriodStart)) src
pivot
(
sum(Days)
for Month in (April, May, June, July, August, September, October,November, December, January, February, March)
)
piv
#Fiscalmonth returns:
Id, Month, NbHolidays, MonthDate
1 April 1 4/1/2012
2 May 2 5/1/2012
3 June 3 6/1/2012
4 July 4 7/1/2012
5 August 5 8/1/2012
6 September 6 9/1/2012
7 October 7 10/1/2012
8 November 8 11/1/2012
9 December 9 12/1/2012
10 January 10 1/1/2013
11 February 11 2/1/2013
12 March 12 3/1/2013
I have a stored procedure which generate report according to the fiscal year.
here fiscal year and fiscal month comes from the database now I am facing problem in generating this report dynamically as you can see i had fixed the months year which is not a good practice i want it to some way that if i changed the fiscal month in the database then my report reflect accordingly.
You will need to use dynamic SQL to do this. The rough code is going to be similar to this:
ALTER PROCEDURE [dbo].[_sp_GetDMActivityTrackerReport]
#CoachId VARCHAR(7),
#Month INT,
#FiscalYear INT
AS
BEGIN
INSERT #FiscalMonth (ID,Month,NbHolidays,MonthDate,TotalDays)
EXECUTE dbo._sp_GetFiscalMonths #Month, #FiscalYear
DECLARE #cols AS NVARCHAR(MAX),
#colsNull AS NVARCHAR(MAX),
#colsSum AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(DateName(Month,nft.MonthPeriodStart))
from NonFieldTime nft
where datename(Month,nft.MonthPeriodStart) + '-'+ substring(datename(Year,nft.MonthPeriodStart),3,2)
IN (SELECT Month +'-' +substring(datename(Year,MonthDate),3,2) [Months]
FROM +#FiscalMonth)
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
select #colsNull = STUFF((SELECT distinct ', IsNull(' + QUOTENAME(DateName(Month,nft.MonthPeriodStart))+', 0) as '+DateName(Month,nft.MonthPeriodStart)
from NonFieldTime nft
where datename(Month,nft.MonthPeriodStart) + '-'+ substring(datename(Year,nft.MonthPeriodStart),3,2)
IN (SELECT Month +'-' +substring(datename(Year,MonthDate),3,2) [Months]
FROM +#FiscalMonth)
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') ,1,1,'')
select #colsSum = STUFF((SELECT distinct '+ IsNull(' + QUOTENAME(DateName(Month,nft.MonthPeriodStart))+', 0)'
from NonFieldTime nft
where datename(Month,nft.MonthPeriodStart) + '-'+ substring(datename(Year,nft.MonthPeriodStart),3,2)
IN (SELECT Month +'-' +substring(datename(Year,MonthDate),3,2) [Months]
FROM +#FiscalMonth)
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') ,1,1,'')+' as [Total Field TIME] '
set #query = 'SELECT PreparationID,CoachId,UserID, MemberID,
[Rep Name], ' + #colsNull + ', '+ #colsSum+'
from
(
SELECT up.PreparationID,
tt.UserId [CoachId],
up.UserID, utm.MemberID,
(ui.FirstName + '' '' + ui.LastName) AS [Rep Name],
DateName(Month,nft.MonthPeriodStart) [Month],
sum(nft.Quantity) [Days]
FROM TransferedTime tt
INNER JOIN UPreparation up
ON tt.PreparationID = up.PreparationID
RIGHT JOIN UTeamMembers utm
ON tt.UserId = utm.CoachID AND utm.MemberID = up.UserID
INNER JOIN UserInfo ui
ON utm.MemberID = ui.UserID
LEFT JOIN NonFieldTime nft
ON nft.UserId = tt.UserId
AND tt.MonthPeriodFrom = nft.MonthPeriodStart
AND datename(Month,nft.MonthPeriodStart) + ''-''+ substring(datename(Year,nft.MonthPeriodStart),3,2) IN
(SELECT Month +''-'' +substring(datename(Year,MonthDate),3,2) [Months]
FROM +#FiscalMonth)
WHERE utm.MemberID IN (SELECT MemberID
FROM UTeamMembers
WHERE CoachID = '+#CoachId+')
GROUP BY up.PreparationID,tt.UserId,up.UserID, utm.MemberID,
(ui.FirstName + '' '' + ui.LastName),DateName(Month,nft.MonthPeriodStart)
) x
pivot
(
sum(Days)
for Month in (' + #cols + ')
) p '
execute(#query)
My suggestion instead of using the temp table #FiscalMonth is to create a table that is permanent for this. It will be much simpler to query against a perm table rather than the temp table when using dynamic sql. The temp table might be out of scope for the dynamic query.
There is PIVOT XML option in Oracle that allows you to avoid hard coding. I'm not sure if there is such option in SQL Server. You can write smth lk this - also Oracle query:
SELECT count(decode(state,'FL',custid)) "FL"
, count(decode(state,'NY',custid)) "NY"
, count(decode(state,'CA',custid)) "CA"
FROM scott.customer
GROUP BY state
/
Replace state with month etc...
Output:
FL NY CA
--- --- ---
0 2 0
5 0 8
Oracle example:
http://www.oracle-base.com/articles/11g/pivot-and-unpivot-operators-11gr1.php
SELECT * FROM
(
SELECT product_code, quantity
FROM pivot_test
)
PIVOT XML
(
SUM(quantity) AS sum_quantity
FOR (product_code) IN (SELECT DISTINCT product_code
FROM pivot_test
WHERE id < 10)
)
/

T:SQL: select values from rows as columns

I have a table for Profiles stores profile properties values in row style, ex:
[ProfileID] [PropertyDefinitionID] [PropertyValue]
1 6 Jone
1 7 Smith
1 8 Mr
1 3 50000
and another table for property definitions :
[PropertyDefinitionID] [PropertyName]
6 FirstName
7 LastName
8 Prefix
3 Salary
How to use PIVOT or any other way to show it in this way:
[ProfileID] [FirstName] [LastName] [Salary]
1 Jone Smith 5000
It's easy to do this without PIVOT keyword, just by grouping
select
P.ProfileID,
min(case when PD.PropertyName = 'FirstName' then P.PropertyValue else null end) as FirstName,
min(case when PD.PropertyName = 'LastName' then P.PropertyValue else null end) as LastName,
min(case when PD.PropertyName = 'Salary' then P.PropertyValue else null end) as Salary
from Profiles as P
left outer join PropertyDefinitions as PD on PD.PropertyDefinitionID = P.PropertyDefinitionID
group by P.ProfileID
you can also do this with PIVOT keyword
select
*
from
(
select P.ProfileID, P.PropertyValue, PD.PropertyName
from Profiles as P
left outer join PropertyDefinitions as PD on PD.PropertyDefinitionID = P.PropertyDefinitionID
) as P
pivot
(
min(P.PropertyValue)
for P.PropertyName in ([FirstName], [LastName], [Salary])
) as PIV
UPDATE: For dynamic number of properties - take a look at Increment value in SQL SELECT statement
It looks like you might have an unknown number of PropertyName's that you need to turn into columns. If that is the case, then you can use dynamic sql to generate the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(PropertyName)
from propertydefinitions
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT profileid, ' + #cols + ' from
(
select p.profileid,
p.propertyvalue,
d.propertyname
from profiles p
left join propertydefinitions d
on p.PropertyDefinitionID = d.PropertyDefinitionID
) x
pivot
(
max(propertyvalue)
for propertyname in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.