Invalid column name after PIVOT - sql

I would like something like this:
42 | 41 | 31 | 32 | Name
----------------------------
O 42
X 41
P 32
Y 41
Z 41
The column headers that are pivoted are also the value in the name column. The various columns can have different statuses. This is what I have but I keep getting an error saying the ValveGroupName column is invalid.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = stuff((select ',' + quotename(ValveGroupName)
from dbo.adm_ValveGroup vgroup,
dbo.adm_Station station
where station.StationID = vgroup.StationID
and station.StationName in ('CBRN')
order by vgroup.ValveGroupName desc
for xml path(''), type).value('.', 'nvarchar(max)'), 1, 1, '')
set #query = 'select ValveGroupName,' + #cols + ' from
(
select vlog.status,
vgroup.ValveGroupName
from dbo.adm_Station station,
dbo.adm_ValveGroup vgroup,
dbo.valvegroup_log vlog
where station.StationID = vgroup.StationID
and vgroup.ValveGroupID = vlog.ValveGroupID
and station.StationName in (''CBRN'')
and vlog.logdate between ''2012-10-01'' and ''2012-10-30''
) x
pivot
(
max(status)
for ValveGroupName in (' + #cols + ')
) p '
execute (#query)
What's wrong with this query?

Since your ValveGroupName is becoming the new column names from the PIVOT, you normally won't include that in the final select list. Since you are basically "removing" the ValveGroupName to become the new columns, SQL Server doesn't have that column any longer so it throws an error.
The code would normally be:
set #query = 'select ' + #cols + '
from
(
select vlog.status,
vgroup.ValveGroupName
from dbo.adm_Station station
inner join dbo.adm_ValveGroup vgroup
on vgroup.ValveGroupID = vlog.ValveGroupID
inner join dbo.valvegroup_log vlog
on station.StationID = vgroup.StationID
where station.StationName in (''CBRN'')
and vlog.logdate between ''2012-10-01'' and ''2012-10-30''
) x
pivot
(
max(status)
for ValveGroupName in (' + #cols + ')
) p '
However, since you are aggregating a string that will return a single row for each ValveGroupName, if you want to return multiple rows, then you'll need to include a row_number:
set #query = 'select ' + #cols + '
from
(
select vlog.status,
vgroup.ValveGroupName,
seq = row_number() over(partition by vgroup.ValveGroupName
order by vlog.status)
from dbo.adm_Station station,
inner join dbo.adm_ValveGroup vgroup
on vgroup.ValveGroupID = vlog.ValveGroupID
inner join dbo.valvegroup_log vlog
on station.StationID = vgroup.StationID
where station.StationName in (''CBRN'')
and vlog.logdate between ''2012-10-01'' and ''2012-10-30''
) x
pivot
(
max(status)
for ValveGroupName in (' + #cols + ')
) p '
This change will allow you to return multiple rows in each column. Note: I also change the code to use INNER JOIN syntax instead of the comma joins, with the conditions in the WHERE clause.

Related

How to get columns with specific string

I am working in SQL Server 2014 and below is my database with which I am working on and need some analysis done on it.
Upon inspecting the database sample carefully we can notice a number 8777 in L9 and in L13 column.
Now I want to get only those columns which have 8777 in them and in the end a column named "count" which shows how many times the number appeared means I need in output something like this as shown below:
So far I have written this query which is giving the category and subcategory correct. But it is showing all the columns. I have no idea how to count the occurrences of a number and show its count in a count column.
select *
from Sheet2$
where '8777' IN ([L1],[L2],[L3],[L4],[L5],[L6],[L7],[L8],[L9],[L10],[L11],[L12],[L13]
To dynamically limit the columns, you would need Dynamic SQL
Example
Select *
Into #Temp
From YourTable A
Unpivot ( Value for Item in ([L1], [L2],[ L3], [L4], [L5], [L6], [L7], [L8], [L9], [L10], [L11], [L12], [L13]) ) u
Where Value = 8777
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(Item) From #Temp Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select *,[Count] = sum(1) over()
From #Temp A
Pivot (max(Value) For [Item] in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
Category SubCategory L13 L9 Count
C1 SC1 NULL 8777 2
C1 SC3 8777 NULL 2
Hmmm. I think you want the original rows with the count. I think this is:
declare #cols nvarchar(max);
declare #sql nvarchar(max);
set #cols = (select distinct ', ' + v.colname
from t cross apply
(values ('l1', l1),
('l2', l2),
('l3', l3),
('l4', l4),
('l5', l5),
('l6', l6),
('l7', l7),
('l8', l8),
('l9', l9),
('l10', l10),
('l11', l11),
('l12', l12),
('l13', l13)
) v(colname, val)
where v.val = '8777'
for xml path ('')
);
set #sql = '
select category, subcategory' + #cols + ',
count(*) over () as cnt
from t
';
exec sp_executesql #sql;
The only difference from your result is that the count is on every row. That can easily be adjusted using a case expression, but I'm not sure it is necessary.
If you want the count in only one row, then:
set #sql = '
select category, subcategory' + #cols + ',
(case when row_number() over (order by category, subcategory) = 1
then count(*) over ()
end) as cnt
from t
order by category, subcategory
';
You can try this part as a replacement of John's answer's second query to get the proper count, it does not achieve the exact thing you want but can be a work around.
Declare #sql varchar(max) = Stuff((Select Distinct ',' + QuoteName(Item)
From #Temp Order by 1 For XML Path('')),1,1,'')
print #sql;
Select #SQL = '
Select *,value=8777
From #Temp A
Pivot (Count(Value) For [Item] in (' + #sql + ') ) p'
print #sql;
Exec(#SQL);
I just used count function in pivot in place of sum.

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

Select values from multiple rows from 1 table as 1 record

I'm struggling to even explain what I need to do so please be patient with me.
I have the following table and rows in it:
TBNAME: Distances
Track, Person, Date, Distance
TRACK1, P1, 1/1/2014, 15
TRACK2, P1, 13/1/2014, 12
TRACK1, P1, 20/2/2014, 10
TRACK2, P1, 15/1/2014, 9
TRACK1, P2, 2/1/2014, 11
TRACK2, P2, 14/1/2014, 13
TRACK1, P2, 21/2/2014, 8
TRACK2, P2, 16/1/2014, 6
What I would, ideally, like to see as a result is something like this:
P1, TRACK1, 20/2/2014, 10, TRACK2, 15/1/2014, 9
P2, TRACK1, 21/2/2014, 8, TRACK2, 16/1/2014, 6
Or, in other words, for each person, the most recent date and distance for that date for each track in one row.
Hope someone can understand this and offer a solution too :)
Cheers,
Pierre
Try this:
SELECT T1.Person, T1.Track, MAX(T1.Date), MIN(T1.Distance),
T2.Track, MAX(T2.Date), MIN(T2.Distance)
FROM Distances AS T1 INNER JOIN
Distances AS T2 ON T1.Person = T2.Person
WHERE T1.Track <> T2.Track AND T1.Track = 'Track1'
GROUP BY T1.Track, T1.Person, T2.Track
The output result of the query is showing exactly the same of your expected result.
Try combining the table by itself and connecting them with the common column.
In your case you want Person.
Select t1.Person,
t1.Tract,
t1.Date,
t1.Distance,
t2.Tract,
t2.date,
t2.Distance
From table_name t1, table_name t2
WHERE t1.Person = t2.Person;
Try this:
SELECT DISTINCT ON ("Person", "Track") *
FROM "Table"
ORDER BY "Person", "Date" DESC NULLS LAST;
Here is a query to get the records needed. First get the maximum date per track and person. Then join with the table to get the complete record.
If you know beforehand which tracks you will get, you can use a pivot query for this. As I've never done this, I ask you to look this up yourself. However, as mentioned in my comment to your request, I would use a programming language (C#, Java, PHP or whatever) to care about that.
select d.track, d.person, d.date, d.distance
from
(
select track, person, max(distances.date) as `date`
from distances
group by track, person
) lastd
inner join distances d on d.track = lastd.track and d.person = lastd.person and d.date = lastd.date
order by d.track, d.person;
BTW: date is a reserved keyword. I would not recommend to use it for a column name. Whenever you use it without a qualifier you will have to use those strange quotes.
Look for ROW_NUMBER() and OVER PARITION BY.
Idea is something like (I did not try to run this query):
;WITH
data AS
(
SELECT
*,
-- returns number for each pair of person and track starting from most recent date
--Code enhanced at here
row_number() over (partition BY person, track order by dte DESC) nr
FROM distances
)
SELECT
*
FROM
data
WHERE
nr = 1 -- we want just the most recent one
ORDER BY
person, -- "group by" person
track ;
It's still doesn't support showing one row for each person...
I don't think you can do it with SQL (because of unknown number of tracks).
There is PIVOT/UNPIVOT, but I don't think it fits here.
WITH CTE AS
(
Select P1.Track,P1.Person,ROW_NUMBER() OVER (Partition by Person,Track Order by Date
Desc) AS RN1
,Date,Distance
from Distances P1
)Select T.Person,T.Track1,T.T1Date
,T.T1Distance,T.Track2,T.T2Date,T.T2Distance
From (
Select C1.Person,C1.Track AS 'Track1',C1.Date AS 'T1Date',
C1.Distance 'T1Distance',
C2.Track AS 'Track2',C2.Date As 'T2Date',C2.Distance 'T2Distance',
ROW_NUMBER() OVER (Partition BY C1.Person Order by C1.Date Desc) RNX
from
CTE C1
JOIN
CTE C2 ON C1.RN1=1 AND C2.RN1=1
AND C1.Person=C2.Person
AND C1.Track<>C2.Track
)t Where t.RNX=1
you may also use dynamic query to achieve your expected result :)
DECLARE #nCount INT
DECLARE #nStart INT
DECLARE #Query NVARCHAR(MAX) =' '
DECLARE #sPerson NVARCHAR(MAX)
DECLARE #sTrack NVARCHAR(MAX)
SET #nCount = (SELECT COUNT(DISTINCT(person)) FROM DISTANCES)
SET #nStart = 1
WHILE #nStart <= #nCount
BEGIN
SET #sPerson = (SELECT PERSON FROM (
SELECT PERSON, ROW_NUMBER() OVER (ORDER BY PERSON) RN FROM (
SELECT DISTINCT(PERSON) FROM DISTANCES
) T1
) T2 WHERE RN = #nStart
)
SET #Query = #Query + '
SELECT '''+#sPerson+''' + '','' + STUFF( '','' +(
SELECT TRACK + '', '' + DATE + '', '' + DISTANCE FROM (
SELECT TRACK, DATE,DISTANCE,
ROW_NUMBER() OVER (PARTITION BY TRACK ORDER BY DATE DESC) RN FROM (
SELECT TRACK,date,DISTANCE FROM DISTANCES WHERE PERSON = '''+#sPerson+'''
) T1
) T2
WHERE RN = 1 FOR XML PATH('''')
),1,1,''''
)
'
IF(#nStart != #nCount)
SET #Query = #Query + ' UNION ALL '
SET #nStart = #nStart + 1
END
EXEC SP_EXECUTESQL #Query
To have a general query it need to be dynamic
DECLARE #query AS NVARCHAR(MAX)
DECLARE #pivotCols AS NVARCHAR(MAX)
DECLARE #cols AS NVARCHAR(MAX)
SELECT #pivotCols = STUFF((SELECT DISTINCT ',' + QUOTENAME([Track])
FROM Distances
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') , 1, 1, '')
;WITH T AS (
SELECT Track
, _ID = CAST(Row_Number() OVER (ORDER BY Track) AS VARCHAR)
FROM Distances
GROUP BY Track
)
SELECT #Cols = STUFF((
SELECT ', Track_' + _ID + ' = ''' + Track + ''''
+ ', LastRun_' + _ID + ' = ' + QUOTENAME([Track])
+ ', Distance_' + _ID + '
= SUM(CASE WHEN d.Date = ' + QUOTENAME([Track]) + '
AND d.Track = ''' + Track + '''
THEN d.Distance ELSE NULL END)'
FROM T FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') , 1, 1, '')
SELECT #query = '
With LR AS (
SELECT Person, ' + #pivotCols + '
FROM (SELECT Track, Person, [Date] FROM Distances) d
PIVOT (MAX([Date]) FOR Track IN (' + #pivotCols + ')) pvt
)
SELECT d.Person, ' + #Cols + '
FROM Distances d
INNER JOIN LR ON d.Person = LR.Person AND d.Date IN (' + #pivotCols + ')
GROUP BY d.Person, ' + #pivotCols + ''
execute(#query);
SQLFiddle demo
The first query generate the list of field for the PIVOT.
The second one generate the fields for the compound query.
The PIVOT is used to get, for every person, the last run on every track, that is than joined back to the base data to get the distance

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);