SQL Dynamic Pivot - how to order columns - sql-server-2005

I'm working on a dynamic pivot query on a table that contains:
OID - OrderID
Size - size of the product
BucketNum - the order that the sizes
should go
quantity - how many ordered
The size column contains different sizes depending upon the OID.
So, using the code found here, I put this together:
DECLARE #listCol VARCHAR(2000)
DECLARE #query VARCHAR(4000)
SELECT #listCol = STUFF(( SELECT distinct '], [' + [size]
FROM #t
FOR
XML PATH('')
), 1, 2, '') + ']'
SET #query = 'SELECT * FROM
(SELECT OID, [size], [quantity]
FROM #t
) src
PIVOT (SUM(quantity) FOR Size
IN (' + #listCol + ')) AS pvt'
EXECUTE ( #query )
This works great except that the column headers (the sizes labels) are not in the order based upon the bucketnum column. The are in the order based upon the sizes.
I've tried the optional Order By after the pivot, but that is not working.
How do I control the order in which the columns appear?
Thank you

You need to fix this:
SELECT #listCol = STUFF(( SELECT distinct '], [' + [size]
FROM #t
FOR
XML PATH('')
), 1, 2, '') + ']'
To return the columns in the right order. You might have to do something like this instead of using DISTINCT:
SELECT [size]
FROM #t
GROUP BY [size]
ORDER BY MIN(BucketNum)

SELECT #listCol = STUFF(
(SELECT DISTINCT ',' + QUOTENAME(size) AS [size]
FROM #t
ORDER BY [size]
FOR XML PATH('')

I saw this link just today, which uses a CTE to build the column list (which, presumably, you could order) on the fly without the need for dynamic sql:
http://blog.stevienova.com/2009/07/13/using-ctes-to-create-dynamic-pivot-tables-in-sql-20052008/

I had the same problem and tried the solution suggested above but, probably due to my level of understanding, couldn't get it to work. I found a simple hack was to create a Temp table with the column headers ordered correctly using Order by statements and then pull in that list to the variable that sets the dynamic pivot query column names.
e.g.
SELECT WeekNum INTO #T3
FROM #T2
GROUP BY WeekNum
ORDER BY MIN(WeekNum)
SELECT #ColumnName1 = ISNULL(#ColumnName1 + ',','') + QuoteName(WeekNum)
FROM (SELECT WeekNum From #T3) AS WeekNum
Worked a treat.
Hope that helps someone.

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

SQL Pivot Table

Hey guys I have tried to convert this query into a pivot table and I have searched everywhere but can't seem to apply it. The following is my code.
SELECT ClientPartner, ClientManager, WIPAmount
FROM tblTranWIP
WHERE (WIPDate>={ts '2013-04-01 00:00:00'}
AND WIPDate<{ts '2014-03-31 00:00:01'})AND TransTypeIndex=1 AND ContIndex<900000
The following screenshot shows my output. I basically want to have Managers and Partners as the First Column and Row and then have WipAmount be in the middle as ClientPartners and Managers match id's The screen shot shows it easier than I explain. The link is a screenshot, the system won't let me add embed images
There is at least one way I know of to get the information you are looking for, and that is to use a PIVOT:
SELECT pvt.*
FROM
(
SELECT ClientPartner, ClientManager, WIPAmount FROM tblTranWIP
) AS tranwip
PIVOT
(
SUM(WIPAmount) FOR ClientPartner IN ([46], [58], [177], [207])
) AS pvt
Here is a SQL Fiddle.
this was a great help, upon reading many tutorials. I came up with the following code for what I needed, which was a dynamic pivot as I don't know every single column code.
DECLARE #sql varchar(MAX)
DECLARE #columns TABLE (COL varchar(50))
declare #columnscsv varchar(MAX)
insert into #columns
select distinct ClientPartner from tblTranWip
select #columnscsv = COALESCE(#columnscsv + '],[','') + COL from #columns
set #columnscsv = '[' + #columnscsv + ']'
SET #sql = 'Select ClientManager ' + #columnscsv + ' FROM(SELECT ClientPartner, ClientManager, WipAmount FROM tblTranWip) a
PIVOT (MAX(WipAmount) for ClientPartner in (' + #columnscsv + ')) AS PVT ORDER by ClientManager'
EXEC (#sql)

Convert a row as column and merge two column as its value

I have stuck in a select statement, converting rows into columns. I have tried with PIVOT, i was able to convert the single column. But my requirement is little different. I have explained the requirement below.
I have a table structure as below,
I want to select the data as below,
The values in the table are dynamic, which is not a problem for me to deal with that. But i need a way to get the below result.
Could someone please give me a hint on doing it, may be a way to modify the PIVOT below.
select *
from
(
select TSID,AID,Count,BID
from tbl TS
WHERE TS.TPID = 1
) src
pivot
(
sum(Count)
for AID in (AID1,AID2,AID3)
) piv
Thank you..
You may check this fiddle
EDIT
This will work for not previously known column names
DECLARE #Columns AS VARCHAR(MAX)
DECLARE #SQL AS VARCHAR(MAX)
SELECT #Columns = STUFF(( SELECT DISTINCT ',' + AID
FROM Table1
FOR
XML PATH('')
), 1, 1, '')
SET #SQL = '
;WITH MyCTE AS
(
SELECT TSID,
AID,
STUFF(( SELECT '','' + CONVERT(VARCHAR,[Count] )
FROM Table1 I Where I.TSID = O.TSID
FOR
XML PATH('''')
), 1, 1, '''') AS CountList
FROM Table1 O
GROUP BY TSID,
AID
)
SELECT *
FROM MyCTE
PIVOT
(
MAX(CountList)
FOR AID IN
(
' + #Columns + '
)
) AS PivotTable'
EXEC(#SQL)

Dynamic Pivot table, how to remove NULL values without knowing column names?

Ok, so I got myself needing a dynamic pivot table, and that was fine.
I needed to create a dynamically sized temporary table to hold these values, eventually I worked that one out. Called it #table
Can I get rid of nulls after the insert without doing it dynamically? I really don't want even more horrific red stuff.
#table
Year CashflowID1 CashflowID2 CashflowID3........CashflowIDn
1 NULL -4 1.23............... etc
2 43 78 -34 ............... NULL
Each cashflow id might have data for a different set of years, resulting in a bunch of nulls.
Something simple like
SELECT ISNULL(*,0)
FROM #table
but, you know, that is clever and actually works. As an aside I tried setting up #table with default values and non nullable columns but that just broke the insert.
Shout if I have missed anything obvious, or failed to provide necessary info.
Cheers.
so, this gets a little messy, but here's the idea
For this i'm querying out of the master table and pivoting on a variate of received (datetime).
declare #columns varchar(max)
declare #columnsisnull varchar(max)
declare #sql nvarchar(max)
SELECT #columns = STUFF(( SELECT DISTINCT TOP 100 PERCENT '],[' + CONVERT(VARCHAR(7), m1.received, 120)
FROM master m1 where m1.received between DATEADD(year, -1, getdate()) and GETDATE()
ORDER BY '],[' + CONVERT(VARCHAR(7), m1.received, 120) desc
FOR XML PATH('')), 1, 2, '') + ']'
SELECT #columnsisnull = STUFF(( SELECT DISTINCT TOP 100 PERCENT ', isnull([' + CONVERT(VARCHAR(7), m1.received, 120) + '],0)'
FROM master m1 where m1.received between DATEADD(year, -1, getdate()) and GETDATE()
--ORDER BY ', isnull([' + CONVERT(VARCHAR(7), m1.received, 120) + '],0)'
FOR XML PATH('')), 1, 2, '')
this looks basically like your code for getting the cols, with the difference being the #columnsisnull where i just append the isnull function into the columns
then for your #sql
set #sql = N'SELECT name, ' + #columnsisnull + ' from master ) p
pivot(sum(amount) for received in ( '+#columns+')) as pvt'
execute(#sql)
I would recommend to make one more attempt with default values, this feature should work fine for your case. It is more sophisticated solution since you will not depend on logic in your query and will not duplicate replacement with NULL.