Order by with variable Coalesce - sql

I have the following sql command
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',['+ cast(Month as nvarchar(2))+']' , '['+ Cast(Month as nvarchar(2))+']')
FROM (select distinct Month from Employee WHERE Year*100+Month BETWEEN 201704 and 201712 ) as e
PRINT #cols
The result was
[9],[12],[6],[7],[10],[4],[5],[11],[8]
But I really want it to result in sort order
[4],[5],[6],[7],[8],[9],[10],[11],[12]

Variable coalescing is documented as being non-deterministic, and may cause incorrect results, in particular in the presence of ORDER BY. You can also not place ORDER BY in a derived table or view, for obvious reasons: the final ordering is determined only by the outer query.
You should instead just use STRING_AGG to aggregate. You can use WITHIN GROUP (ORDER BY to get the ordering.
Note also:
Always use QUOTENAME to get brackets round your column names, instead of doing it yourself, as escaping can be complex.
It's better to make exact comparisons on columns, rather than doing calculations on them and then comparing, as then you can hit indexes (sarge-ability).
It's probably better to store dates in actual date columns, rather than messing around with multiple columns, but I will leave database redesign to you
DECLARE #cols NVARCHAR(MAX) = (
SELECT STRING_AGG(QUOTENAME(Month), N',') WITHIN GROUP (ORDER BY Month)
FROM (
select distinct
Month
from Employee
WHERE Year = 2017
AND Month BETWEEN 4 AND 12
) as e
);
For SQL Server 2016 and earlier, you can use the old FOR XML method:
DECLARE #cols NVARCHAR(MAX) = STUFF((
SELECT N',' + QUOTENAME(Month)
FROM (
select distinct
Month
from Employee
WHERE Year = 2017
AND Month BETWEEN 4 AND 12
) as e
ORDER BY Month
FOR XML PATH(''), TYPE
).value('text()[1]','nvarchar(max)')
, 1, LEN(N','), N'');

Related

SQL Sort / Order By pivoted fields while COALESCE function

I have some rates for resources for all countries
The rows will be Resource IDs
Columns should be Country Codes
Challenge here, I cannot sort the Country Codes in ASC
It would be so grateful if you could help me on this.
When I query, I get the list of country codes, but not sorted. i.e., USA,BRA,ARG etc. But the expected result should be ARG,BRA,USA in columns of the pivot.
Here is my code:
DECLARE #idList nvarchar(MAX)
SELECT
#idList = COALESCE(#idList + ',', '') + CountryCodeISO3
FROM
(
SELECT
DISTINCT CountryCodeISO3
FROM
Published.RateCardsValues
WHERE
CardID = 55
) AS SRC
DECLARE #sqlToRun nvarchar(MAX)
SET
#sqlToRun = '
SELECT *
FROM (
SELECT
[ResourceCode]
,[TITLES]
,[MostRepresentativeTitle]
,[ABBR_RES_DESC]
,[TypicalJobGrade]
,[BidGridResourceCode]
,[OpUnit]
,[PSResType]
,[JobGradeORResCat]
,[CountryCodeISO3]
--,[CurrencyCode]
,[RateValue]
FROM
[Published].[RateCardsValues] rc
WHERE
CardID = 55) As src
PIVOT (
MAX(RateValue) FOR [CountryCodeISO3] IN (' + #idList + ')
) AS pvt'
EXEC (#sqlToRun)
As you have discovered, PIVOT in T-SQL requires you to know at development time what the values will be that you will be pivoting on.
This is limiting, because if you want something like "retrieve data for all the countries where Condition X is true, then pivot on their IDs!", you have to resort to dynamic SQL to do it.
If Condition X is constant -- I'm guessing that belonging to CardID = 55 doesn't change often -- you can look up the values, and hardcode them in your code.
If the CardID you're looking up is always 55 and you have relatively few countries in that category, I'd actually advise doing that.
But if your conditions for picking countries can change, or the number of columns you want can vary -- something like "all the countries where there were sales of product Y, for month Z!" -- then you can't predict them, which means that the T-SQL PIVOT can't be set up (without dynamic SQL.)
In that case, I'd strongly suggest that you have whatever app you plan to use the data in do the pivoting, not T-SQL. (SSRS and Excel can both do it themselves, and code can be written to do it in .NET langauges.) T-SQL, as you have seen, does not lend itself to dynamic pivoting.
What you have will "work" in the sense that it will execute without errors, but there's another downside, in the next stage of your app: not only will the number of columns potentially change over time, the names of the columns will change, as countries move in and out of Card ID 55. That may cause problems for whatever app or destination you have in mind for this data.
So, my two suggestions would be: either hard-code your country codes, or have the next stage in your app (whatever executes the query) do the actual pivoting.
You need to sort the columns while creating the dynamic SQL
Also:
Do not use variable coalescing, use STRING_AGG or FOR XML instead
Use QUOTENAME to escape the column names
sp_executesql allows you to pass parameters to the dynamic query
DECLARE #idList nvarchar(MAX)
SELECT
#idList = STRING_AGG(QUOTENAME(CountryCodeISO3), ',') WITHIN GROUP (ORDER BY CountryCodeISO3)
FROM
(
SELECT
DISTINCT CountryCodeISO3
FROM
Published.RateCardsValues
WHERE
CardID = 55
) AS SRC;
DECLARE #sqlToRun nvarchar(MAX);
SET
#sqlToRun = '
SELECT *
FROM (
SELECT
[ResourceCode]
,[TITLES]
,[MostRepresentativeTitle]
,[ABBR_RES_DESC]
,[TypicalJobGrade]
,[BidGridResourceCode]
,[OpUnit]
,[PSResType]
,[JobGradeORResCat]
,[CountryCodeISO3]
--,[CurrencyCode]
,[RateValue]
FROM
[Published].[RateCardsValues] rc
WHERE
CardID = 55) As src
PIVOT (
MAX(RateValue) FOR [CountryCodeISO3] IN (' + #idList + ')
) AS pvt'
EXEC sp_executesql #sqlToRun;
On earlier versions of SQL Server, you cannot use STRING_AGG. You need to hack it with FOR XML. You need to also use STUFF to strip off the first separator.
DECLARE #idList nvarchar(MAX)
DECLARE #separator nvarchar(20) = ',';
SET #idList =
STUFF(
(
SELECT
#sep + QUOTENAME(CountryCodeISO3)
FROM
Published.RateCardsValues
WHERE
CardID = 55
GROUP BY
CountryCodeISO3
ORDER BY
CountryCodeISO3
FOR XML PATH(''), TYPE
).value('text()[1]','nvarchar(max)'),
1, LEN(#separator), '')
;
DECLARE #sqlToRun nvarchar(MAX);
SET
#sqlToRun = '
SELECT *
FROM (
SELECT
[ResourceCode]
,[TITLES]
,[MostRepresentativeTitle]
,[ABBR_RES_DESC]
,[TypicalJobGrade]
,[BidGridResourceCode]
,[OpUnit]
,[PSResType]
,[JobGradeORResCat]
,[CountryCodeISO3]
--,[CurrencyCode]
,[RateValue]
FROM
[Published].[RateCardsValues] rc
WHERE
CardID = 55) As src
PIVOT (
MAX(RateValue) FOR [CountryCodeISO3] IN (' + #idList + ')
) AS pvt'
EXEC sp_executesql #sqlToRun;

Convert Rows to Columns SQL

I am trying to convert rows to columns in SQL server. I am trying to convert the value's a product gets while being tested during quality. I have tried the pivot function but having trouble doing so as the same values do get repeated and it can not be easily sorted into rows. The table I am trying to pivot holds ~30K data row's so hoping to find a dynamic solution for this.
The maximum number of new columns is 30 but sometimes a product doesn't get tested as much so it can be less. The new column would be based off my inspection_unit_number field. Is this possible to achieve in SQL
Current data
What I hope to achieve
Current Attempt
SELECT BATCH , characteristic, [1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23],[24],[25],[26],[27],[28],[29],[30]
from
(
select inspection_lot ,node_number ,characteristic ,inspector ,inspection_unit_number ,start_date ,measured_value ,original_value ,material_no ,batch
from stg.IQC_Tensile_TF
) d
pivot
(
max(measured_value)
for
INSPECTION_UNIT_NUMBER in ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23],[24],[25],[26],[27],[28],[29],[30])
) piv;
You will have to go for a dynamic query, check if this will suit your needs.
I created a common table expression to be able to use distinct and then order by in the stuff function:
DECLARE #QUERY NVARCHAR(MAX)
DECLARE #Columns NVARCHAR(MAX)
WITH cte_unique_inspection_unit_number AS
(
SELECT DISTINCT QUOTENAME('TestResults' + CAST(inspection_unit_number AS VARCHAR)) TestResultsN,
inspection_unit_number
FROM IQC_Tensile_TF
)
SELECT #Columns = STUFF((SELECT ', ' + TestResultsN
FROM cte_unique_inspection_unit_number
ORDER BY inspection_unit_number
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,2,''),
#query = 'SELECT batch, node_number, characteristic, ' + #Columns + ' from
(
select batch,
node_number,
characteristic,
measured_value,
''TestResults'' + CAST(inspection_unit_number AS VARCHAR) TestResultsN
from IQC_Tensile_TF
) x
pivot
(
max(measured_value)
for TestResultsN in (' + #Columns + ')
) p '
EXEC(#query)
To view the execution in fiddle:
https://dbfiddle.uk/?rdbms=sqlserver_2014&fiddle=7898422e4422faacb25d7f3c2285f14a
If you find my answer useful, i would appreciate if you vote up and mark as accepted =D

Columns to Rows using SQL PIVOT... SQL Server 2008 R2

I have to transpose all the Rows from a table to columns in SQL Server 2008 R2... I have used Pivot to transpose multiple rows of one column into one row with multiple columns. I am not sure how I can use pivot in this scenario...
I would like to pivot the table based on the "EXPENSE" column
SQL Fiddle
The desired output is
Meanwhile I will try to explore the related posts suggested....
Thank you so much for the suggestions...
Based on your desired results it looks like you need to do an unpivot transform followed by a pivot, like this:
select
YEAR,
[Bps on Assets],[Setup Fee],[Account Min],[BAA Fees],[RedTail Fees (CRM)],
[RedTail Fees (Per User)],[External IT],[External IT Setup]
from (
select Expense, value, year
from SM_TechBundleExpnsRates
unpivot (
value FOR year IN ([Year1], [Year2], [Year3], [Year4], [Year5])
) up
) a
pivot (
sum(value) for expense in
(
[Bps on Assets],[Setup Fee],[Account Min],
[BAA Fees],[RedTail Fees (CRM)],
[RedTail Fees (Per User)],[External IT],[External IT Setup]
)
) p
Sample SQL Fiddle
Note that this isn't dynamic in any way, but rather uses hard coded column values for the years and expenses. It's possible to generate the code in a dynamic fashion - if you want to know how there are plenty of good answers showing how to do dynamic pivot with SQL Server.
Edit: did the dynamic version for fun, it might not be perfect but it should work:
DECLARE #sql AS NVARCHAR(MAX)
DECLARE #year_cols AS NVARCHAR(MAX)
DECLARE #expe_cols AS NVARCHAR(MAX)
SELECT #expe_cols= ISNULL(#expe_cols + ',','') + QUOTENAME(Expense)
FROM (SELECT DISTINCT Expense FROM SM_TechBundleExpnsRates) AS Expenses
SELECT #year_cols= ISNULL(#year_cols + ',','') + QUOTENAME(year)
FROM (
SELECT c.name AS year
FROM sys.tables t JOIN sys.columns c ON t.object_id = c.object_id
WHERE t.name = 'SM_TechBundleExpnsRates' AND c.name LIKE '%Year%'
) AS Years
SET #sql = N'
SELECT
Year, ' + #expe_cols + '
FROM (
SELECT Expense, Value, Year
FROM SM_TechBundleExpnsRates
UNPIVOT ( Value FOR Year IN (' + #year_cols + ') ) AS up
) a PIVOT ( SUM(Value) FOR Expense IN (' + #expe_cols + ') ) p'
EXEC sp_executesql #sql

SQL dynamic pivot after CTE

I hope this is more specific? sorry if I am unclear, kind of new to this. Thank you for the help!!
I'm trying to get a dynamic pivot to work on a CTE. I have looked around a bit and I have a couple of problems. For what I fount, it seems that something like the following post is pretty standard for a dynamic sql:
Pivot Table and Concatenate Columns
I have the following columns in my table with trades:
Date | product | time | price | volume |
I want to get the average price for each quarter of the day, so I want to pivot the time column after rounding it down to the nearest quarter time. and taking the Weighted average price per product and date.
so I use one CTE to create the pivot list:
DECLARE #pivot_list as varchar(max)
;with startquarter(starttradequarter)
AS
(
SELECT cast(DATEadd(mi,(datediff(mi,0,Time))/15*15,0)as varchar)
from [table]
where date > '2014-04-15'
),
PIVOT_CODES(PIVOT_CODE)
AS
(
SELECT DISTINCT starttradequarter AS PIVOT_CODE
from startquarter
)
SELECT #pivot_list = COALESCE(#pivot_list + ',[' + PIVOT_CODE + ']','[' + PIVOT_CODE + ']')
FROM PIVOT_CODES
then I want to use this variable in a pivot of the table:
;With productselector(Date,startquarter,product,volume,price)
as
(
SELECT [Date]
,cast(DATEadd(mi,(datediff(mi,0,Time))/15*15,0)as varchar) as startquarter
,[product]
,[Volume]
,[Price]
FROM [table]
where DelDate = '2014-01-06' and product = 'x'
),
WAPricequarter(startquarter,date,sumvolume,WAPq,product)
AS
(
SELECT startquarter
,Date
,sum(volume) as sumvolume
,round(sum(volume*price)/sum(volume),2) as WAPq
,product
from productselector
group by date, startquarter, product
)
SELECT date, product, + #pivot_list
from WAPricequarter
PIVOT (
SUM([sumvolume])
FOR startquarter IN (#pivot_list)
) AS pvt
So I see in all dynamic pivots the second statement first put in a variable and then executed, is this necessary?
If not how do I get the pivot to work on the columns in the #pivot_list, it now gives an incorrect syntax error that I can't get to work.
If it is necessary to put it in a variable and then execute, how can I then still filter for product or date inside that variable since I have to use '' around it.

Get a substring from a column and perform a groupBy and count

I have a table that stores data about a large number of files, such as their language, unique ID, file path etc. I want to be able to get the sub-string from the unique ID which gives me the asset type, this is always the first 2 letters of the ID. I then want to group these asset types by language and have a count for how many of each type every language has. So at the end I would ideally like a table that has a language column and then a column for each substring (asset type).
I have tried to create a large switch statement but this isn't very reliable and I was told maybe linq would be better. I don't have much experience with linq or sql and I have a couple of sql queries I've tried that gets me one part of the desired results, but I was hoping maybe someone who has more experience might know how to group these functions into one statement.
SELECT
LCID,
SUBSTRING(AssetID,1,2)
FROM [table]
this gets me the correct substrings, but I have multiple rows for each language. Is there any way to group the same languages into one column and then count how many of each type there are? Thanks
Sounds like you want a COUNT and a GROUP BY:
SELECT
SUBSTRING(AssetID,1,2),
COUNT(*) Total
FROM [table]
GROUP BY SUBSTRING(AssetID,1,2)
You did not specify what database but, if you are using SQL Server and LCID is in your SELECT statement, then you will need to include it in your GROUP BY clause.
If the LCID value is unique for each row then you will get multiple records for each AssetID because it will try to group the unique values together. As a result, I removed the LCID.
If it is not unique, then you can use:
SELECT LCID,
SUBSTRING(AssetID,1,2),
COUNT(*) Total
FROM [table]
GROUP BY LCID, SUBSTRING(AssetID,1,2)
Based on the edits that you made, you want a PIVOT which transforms the data from rows into columns. For a PIVOT you will use:
select LCID, HA, HT, HP, FH, FX
from
(
SELECT LCID,
SUBSTRING(AssetID,1,2) AssetID
FROM [table]
) src
pivot
(
count(AssetID)
for AssetID in (HA, HT, HP, FH, FX) -- place more values here
) piv
If the values are unknown that you want to transform into columns, then you will need to use dynamic SQL similar to this:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(SUBSTRING(AssetID,1,2))
from [table]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT LCID, ' + #cols + ' from
(
SELECT LCID,
SUBSTRING(AssetID,1,2) AssetID
FROM [table]
) x
pivot
(
count(AssetID)
for AssetID in (' + #cols + ')
) p '
execute(#query)