Sort COALESCE generated columns in a dynamic pivot table - sql-server

I would like to sort the columns, but I get the following error:
The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified.
The years select query returns them in order, but they are shuffled when handled by the COALESCE function.
How can I prevent this? Or even better, control the sorting?
NOTE: The sorting works if I put a TOP 10 in the subquery. Super weird...
-- variables
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
-- columns
SELECT -- top 10 makes the sorting work
-- [2018], [2020], [2017], [2019] -- not ordered
#pivot_list = COALESCE(#pivot_list + ', ', '') + '[' + PIVOT_COLUMN + ']',
#select_list = COALESCE(#select_list + ', ', '') + 'ISNULL([' + PIVOT_COLUMN + '], 0) as [' + PIVOT_COLUMN + ']'
FROM (
SELECT
-- 2017, 2018, 2019, 2020 -- ordered
cast(year(addate) as nvarchar(4)) as PIVOT_COLUMN
FROM tHE_Move M
where addate >= '01.01.2017'
group by year(addate)
-- order by year(addate) asc -- <-------------------------------------- doesn't work
) as PIVOT_COLUMNS
-- query
SET #sql = '
; WITH PivotData AS (
select
shop,
y AS PIVOT_COLUMN,
sum(revenueNoVAT) revenueNoVAT
from (
SELECT
SS.acName2 shop,
year(m.addate) y,
MI.anPVVATBase revenueNoVAT
FROM tHE_MoveItem MI
JOIN tHE_Move M ON MI.acKey = M.acKey
JOIN tHE_SetSubj SS ON M.acIssuer = SS.acSubject
WHERE m.acDocType in (
' + '''3210''' + ',
' + '''3220''' + ',
' + '''3230''' + ',
' + '''3240''' + '
)
) t1
group by
shop,
y
)
SELECT shop, ' + #select_list + '
FROM PivotData
PIVOT (
sum(revenueNoVAT)
FOR PIVOT_COLUMN
IN (' + #pivot_list + ')
) piv
order by shop desc
'
-- execution
EXEC (#sql)

Related

Using sub-query to generate case statements

What i am trying to accomplish is comparing two rows to each other pointing out the differences from row to row. Each row has quite a few columns and I was trying to make it easily visible for which ones had changed. Code below is my thoughts, but I know this won't work, but is a start.
SELECT
(SELECT concat('Case WHEN T1.', column_name, ' <> T2.', column_name, ' THEN ''', column_name, ' Changed Values('' + CONVERT(varchar(100), T1.', column_name, ') + '', '' + CONVERT(varchar(100), T2.', column_name, ') + '')'' ELSE '''' END AS ', column_name)
FROM information_schema.columns
WHERE table_name = 'Table')
FROM
(
SELECT * FROM Table
WHERE ID = '13'
) AS T1
JOIN
(
SELECT * FROM Table
WHERE ID = '2006'
) AS T2
ON T1.CreateTimeStamp = T2.CreateTimeStamp
I got the idea because below this works fine, but I would like this to be potentially reusable code for other table without having to type out tens or hundreds of columns each time.
SELECT
Case WHEN T1.R1<> T2.R1 THEN 'Changed Values(' + CONVERT(varchar(100),T1.R1) + ', ' + CONVERT(varchar(100),T2.R1) + ')' ELSE '' END AS R1,
Case WHEN T1.R2<> T2.R2 THEN 'Changed Values(' + CONVERT(varchar(100),T1.R2) + ', ' + CONVERT(varchar(100),T2.R2) + ')' ELSE '' END AS R2
FROM
(
SELECT * FROM Table
WHERE ID = '13'
) AS T1
JOIN
(
SELECT * FROM Table
WHERE ID = '2006'
) AS T2
ON T1.CreateTimeStamp = T2.CreateTimeStamp
For the this example please assume CreateTimeStamp always equals each other between the two rows.
You would need to create the whole query as dynamic SQL. Note that I'm using QUOTENAME() to prevent SQL Injection from weirdly named columns. I'm also trying to keep a format for the code, so I won't get headaches when debugging.
DECLARE #SQL NVARCHAR(MAX);
SELECT #SQL = N' SELECT ' + NCHAR(10)
--Concatenate all columns except ID and CreateTimeStamp
+ STUFF(( SELECT REPLACE( CHAR(9) + ',CASE WHEN T1.<<ColumnName>> <> T2.<<ColumnName>> ' + CHAR(10)
+ CHAR(9) + CHAR(9) + 'THEN ''Changed Values('' + CONVERT(varchar(100),T1.<<ColumnName>>) + '', '' + CONVERT(varchar(100),T2.<<ColumnName>>) + '')'' ' + CHAR(10)
+ CHAR(9) + CHAR(9) + 'ELSE '''' END AS <<ColumnName>>', '<<ColumnName>>', QUOTENAME(COLUMN_NAME)) + NCHAR(10)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Table'
AND COLUMN_NAME NOT IN( 'ID', 'CreateTimeStamp')
FOR XML PATH(''), TYPE).value('./text()[1]', 'nvarchar(max)'), 2, 1, '') + NCHAR(10)
--Add rest of the query
+ 'FROM Table AS T1 ' + NCHAR(10)
+ 'JOIN Table AS T2 ON T1.CreateTimeStamp = T2.CreateTimeStamp ' + NCHAR(10)
+ 'WHERE ID = #ID1 ' + NCHAR(10)
+ 'AND ID = #ID2;'
--PRINT for debugging purposes
PRINT #SQL;
--Execute the dynamic built code
EXECUTE sp_executesql #SQL,
N'#ID1 int, #ID2 int',
#ID1 = 13,
#ID2 = 2006;
The concatenation method is explained on this article.

SQL Server: I am getting an error while altering view when I try to add some virtual column which in turn based on virtual column

I have 20 databases, each with same table but different columns.
So to make the uniform we are creating views on top of each table in a database which will contain all the columns, as there will be one application accessing all the database.
In the view, I have to write the query in such a way that if I want to alter it and add any addition column for testing I should be able to do that.
Now in below query I am altering / creating query such that it takes all the columns of that table from the database, and then I append the other columns which are not present in it.
I need to add a column which will just concatenate some of the columns
ALTER VIEW [dbo].[AIV_PARKING]
AS
SELECT
*,
Cast(NULL AS [VARCHAR](20)) [ACTCODE],
Cast(NULL AS [VARCHAR](1)) [ACTIVATEINFO],
Cast(NULL AS [VARCHAR](20)) [VEHLICNOCHECK],
Cast(NULL AS [VARCHAR](40)) [ACTIVITY],
Cast(Isnull(vehlicnocheck, '') + '|' +
Isnull(officername, '') + '|' +
Isnull(locstreet, '') + '|' +
Isnull(locsideofstreet, '') + '|' +
Isnull(loccrossstreet1, '') + '|' +
Isnull(loccrossstreet2, '') + '|'
+ Isnull(locsuburb, '') + '|'
+ Isnull(locstate, '') + '|'
+ Isnull(locpostalcode, '') + '|'
+ Isnull(loclot, '') + '|'
+ Isnull(locpostalcode, '') + '|'
+ Isnull(Cast(officerid AS VARCHAR(20)), '')
+ Isnull(officername, '') + '|'
+ Isnull(Cast (issueno AS VARCHAR(100)), '') AS NVARCHAR(max)) AS SearchText
FROM
[dbo].parking
Here I added a column called SearchText which concatenates other columns, but I get an error
Invalid column name 'VehLicNoCheck'
Is there any way I can add this column to this view?
I also tried to do to something below but I got the same error:
CAST(CASE
WHEN NOT EXISTS
(
Select 1 from INFORMATION_SCHEMA.COLUMNS
Where Column_name ='VehLicNoCheck'
and table_name='Parking'
)
THEN ''
ELSE ISNULL(VehLicNoCheck,'')
END as nvarchar(max)
)
you could create a view that normalizes the uncommon columns to rows, where the values for the common columns are just repeated, e.g:
select id, col, value from parking
unpivot (value for col in (actcode, vehLicNoCheck, etc.)) x
the code to dynamically generate the view would be something like:
declare #sql varchar(max) = 'select id, col, value from parking unpivot (value for col in ('
select #sql += quotename(name) +',' from sys.columns where object_id=object_id('parking') and name not in ('id')
set #sql = substring(#sql, 1, len(#sql) - 1) + '))x'
exec(#sql)
this does not make sense at all.
the [ACTCODE], [ACTIVATEINFO] are all NULL value
so basically SearchText is just a string of '|||||'
you might as well, just do this
SELECT *,
CAST( NULL AS varchar) [ACTCODE],
CAST( NULL AS varchar) [ACTIVATEINFO],
CAST( NULL AS varchar) [VEHLICNOCHECK],
CAST( NULL AS varchar) [ACTIVITY],
'||||||' as SearchText
FROM [dbo].PARKING
Maybe if you can explain what are you trying to achieve here, we can point you to the right direction
EDIT :
You will need to use Dynamic SQL. You will need a list of all column names
-- declare a table variable for all the columns that you required
declare #columns table
(
id int identity,
name varchar(100)
)
-- for example these are the required columns
insert into #columns
values ('ACTCODE'), ('ACTIVATEINFO'), ('VEHLICNOCHECK'), ('ACTIVITY')
-- The Query to create the view
declare #sql nvarchar(max)
select #sql = N'CREATE VIEW [AIV_PARKING] AS' + char(13) + 'SELECT' + char(13)
select #sql = #sql
+ case when t.name is not null
then quotename(c.name) + ','
else 'CAST (NULL AS VARCHAR(10)) AS ' + quotename(c.name) + ','
end
+ char(13)
from #columns c
left join sys.columns t on c.name = t.name
and t.object_id = object_id('PARKING')
order by c.id
select #sql = #sql
+ case when t.name is not null
then 'ISNULL(' + quotename(c.name) + ', '''')'
else ''
end
+ ' + ''|'''
+ ' + '
from #columns c
left join sys.columns t on c.name = t.name
and t.object_id = object_id('PARKING')
order by c.id
select #sql = left(#sql, len(#sql) - 8) + ' AS SearchText' + char(13)
+ 'FROM PARKING'
-- print out to view the complete create view statement
print #sql
-- execute it
exec sp_executesql #sql

Why correlated subquery doesn't allow two expressions - SQL Server

I have query like this:
select
objectid,
(select top 1 data_source, maxspeed
from SpeedLimitData3
where way_geometry.Filter(geography::STGeomFromText('POINT (' + cast(X as varchar(15)) + ' ' + cast(Y as varchar(15)) + ')', 4326)) = 1
order by way_geometry.STDistance(geography::STGeomFromText('POINT (' + cast(X as varchar(15)) + ' ' + cast(Y as varchar(15)) + ')', 4326))
)
from
testData
Why does the SQL Server throw this error?
Msg 116, Level 16, State 1, Line 8
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
I know that this means that I need to remove one of subquery's selected columns. But why when I have single row as subquery result and not several?
The syntax of a select clause in Transact-SQL allows a <select-list> made up of various entities. A correlated subquery used in a select clause is an expression and supplies the value for a single result column.
this will work, but will output same data in otr.* part of select.
You have to use related fields in testData and applied correlation
select objectid, otr.*
from testData
outer apply
(
select top 1 data_source, maxspeed
from SpeedLimitData3
where way_geometry.Filter(geography::STGeomFromText('POINT (' + cast(X as varchar(15)) + ' ' + cast(Y as varchar(15)) + ')', 4326)) = 1
order by way_geometry.STDistance(geography::STGeomFromText('POINT (' + convert(varchar(15),X) + ' ' + convert(varchar(15),Y) + ')', 4326))
) otr
If X & Y are fields in testData
select tD.objectid, otr.*
from testData tD
outer apply
(
select top 1 data_source, maxspeed
from SpeedLimitData3
where way_geometry.Filter(geography::STGeomFromText('POINT (' + convert(varchar(15),tD.X) + ' ' + convert(varchar(15),tD.Y) + ')', 4326)) = 1
order by way_geometry.STDistance(geography::STGeomFromText('POINT (' + convert(varchar(15),tD.X) + ' ' + convert(varchar(15),tD.Y) + ')', 4326))
) otr

How to use where statement in a dynamic column

i need to make a 'where' statement in a dynamic column.
the dynamic column came from a row item.
sample as below.
SET #paramList = STUFF((
SELECT DISTINCT ',[' + parameter + ']'
FROM #tblitems FOR XML PATH('')
)
,1,1,'')
#paramList = [item1],[item2],[item3]
using the below query i need to incorporate the where statement at the end. but the column from the #paramlist should all be equal to 1 only.
SET #query ='select no,
' + #paramList + '
FROM( SELECT * FROM #tblitems)src
PIVOT
(
max(value)
for [parameter] in (' + #paramList + ')
) as piv order by item'
Create the condition string in the same way as the column list:
SET #condition = STUFF((
SELECT DISTINCT ' AND [' + parameter + '] = 1'
FROM #tblitems FOR XML PATH('')
)
,1,5,'');
Note though that you can also use the QUOTENAME function instead of enclosing the name in square brackets manually:
SET #condition = STUFF((
SELECT DISTINCT ' AND ' + QUOTENAME(parameter) + ' = 1'
FROM #tblitems FOR XML PATH('')
)
,1,5,'');
Now that you've got the condition string, you can add it to the dynamic query:
SET #query ='SELECT no,
' + #paramList + '
FROM (SELECT * FROM #tblitems) AS src
PIVOT
(
MAX(value)
FOR [parameter] IN (' + #paramList + ')
) AS piv
WHERE ' + #conditions + '
ORDER BY item;';

SQL Server Syntax Error on PIVOT

Can anyone please help me with the PIVOT table syntax error as I am using this for the First time.
DECLARE #sql AS varchar(max)<br/>
DECLARE #pivot_list AS varchar(max) <br/>
DECLARE #select_list AS varchar(max) <br/>
SELECT #pivot_list = COALESCE(#pivot_list + ', ', '') + '[' + CONVERT(varchar, STATE_NAME) + ']'<br/>
,#select_list = COALESCE(#select_list + ', ', '') + '[' + CONVERT(varchar, STATE_NAME) + '] AS [' + CONVERT(varchar, STATE_NAME) + ']'
FROM (
SELECT DISTINCT name as STATE_NAME
FROM k12_dms_states
) AS PIVOT_CODES
SET #sql = '
SELECT COUNT(k12_dms_contacts_institution_jobtitles.id) as total_count
,k12_dms_job_titles.title as job_title,' + #select_list + '
FROM k12_dms_institution_master
INNER JOIN k12_dms_contacts_institution_jobtitles ON k12_dms_institution_master.id = k12_dms_contacts_institution_jobtitles.inst_id
INNER JOIN k12_dms_job_titles ON k12_dms_job_titles.id = k12_dms_contacts_institution_jobtitles.job_title_id
GROUP BY k12_dms_job_titles.title
PIVOT (
total_count
FOR STATE_NAME IN (
' + #pivot_list + '
)
) AS pvt
'
PRINT #sql
EXEC (#sql)
I am getting this error: -
Msg 156, Level 15, State 1, Line 8
Incorrect syntax near the keyword 'PIVOT'.
PIVOT belongs in the FROM clause. It needs to occur before any GROUP BY clause.
(Further edits based on commenting, to try to correct):
SET #sql = '
SELECT
k12_dms_job_titles.title as job_title,' + #select_list + '
FROM k12_dms_institution_master
INNER JOIN k12_dms_contacts_institution_jobtitles ON k12_dms_institution_master.id = k12_dms_contacts_institution_jobtitles.inst_id
INNER JOIN k12_dms_job_titles ON k12_dms_job_titles.id = k12_dms_contacts_institution_jobtitles.job_title_id
PIVOT (
COUNT(k12_dms_contacts_institution_jobtitles.id)
FOR STATE_NAME IN (
' + #pivot_list + '
)
) AS pvt
'

Resources