How to Convert XML Rows to Columns - sql

I have seen a few answers on how to convert rows to columns but they are rather specific to the questions and are difficult for me to transpose into my own solution.
The data starts out as a varchar, but I convert it to XML because I thought it would be easier to convert it to columns that way.
-- get xml
DECLARE #x XML
SET #x = '<ul><li>Gas grill rotisserie</li><li>Fits the Genesis E-300 gas grill</li><li>Fits the Genesis S-300 gas grill</li><li>Includes a heavy-duty electric motor</li><li>Counterbalance for smooth turning and less motor wear</li></ul>'
SELECT x.r.value('node()[1]','varchar(200)')
FROM #x.nodes('/ul/li') AS x(r)
This returns a results like the following; however, I now need to convert each row into a column.
I have tried variations using pivot and dynamic SQL, but haven't gotten very far. How can I convert each row to a column (when the number of rows will be unknown).
Ref. Convert Rows to columns using 'Pivot' in SQL Server
Ref. How to convert row values to columns with dynamic columns count?

I was SOreadytohelp myself this time :) ... The following query receives the HTML, converts it to XML, defines the column names and writes the dynamic SQL before executing it.
The end result is:
DECLARE #x XML,
#limit int = 4,
#ItemId NVARCHAR(10) = '11158',
#cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
-- get xml
SELECT #x = '<ul><li>Gas grill rotisserie</li><li>Fits the Genesis E-300 gas grill</li><li>Fits the Genesis S-300 gas grill</li><li>Includes a heavy-duty electric motor</li><li>Counterbalance for smooth turning and less motor wear</li></ul>'
-- convert rows to columns
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(name)
from
(
SELECT top (#limit)
bar.value('local-name(.)','VARCHAR(12)') + cast(row_number() over(order by bar.value('./.','VARCHAR(10)') asc) as varchar(10)) as name,
bar.value('./.','VARCHAR(255)') as value
FROM
#x.nodes('/ul/*') AS foo(bar)
) d
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
-- create dynamic sql
set #query = '
-- get xml
DECLARE #x XML
SELECT #x = ''<ul><li>Gas grill rotisserie</li><li>Fits the Genesis E-300 gas grill</li><li>Fits the Genesis S-300 gas grill</li><li>Includes a heavy-duty electric motor</li><li>Counterbalance for smooth turning and less motor wear</li></ul>''
SELECT ' + #cols + '
from
(
SELECT
bar.value(''local-name(.)'',''VARCHAR(12)'') + cast(row_number() over(order by bar.value(''./.'',''VARCHAR(10)'') asc) as varchar(10)) as name,
bar.value(''./.'',''VARCHAR(255)'') as value
FROM
#x.nodes(''/ul/*'') AS foo(bar)
) x
pivot
(
max(value)
for name in (' + #cols + ')
) p '
execute sp_executesql #query;

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

Cast column name to specific character during Pivot table creation - SQL

I have a table test which has a column Label. It has Data which is longers than 50 characters in length.
When I create a pivot table from the 'test' table , it uses those long charactered data as column name.
My requirement is to user cast function to limit the column names to say 26 characters.
I use the below script , but it doesnt work as desired.
create table V_Test as
select * from
(select * from Test) x
pivot (sum(Average) for Label in (
S03_CreatePlansdadsada,
S03_CreatePlan_T01_NavigateTosdsadsaded,
S03_CreatePlan_T03_abcdefgmanagementsdasda,
S03_CreatePlan_T16_SetStatusToOngoingasdasda,
S03_CreatePlan_T17_Ldsdssdadsadas
)
) p
Thanks in advance for the help
Since the label names are hard-coded anyway, then using aliases for them should be fine.
On MS SQL Server it would look something like this.
Just using a variable table and a temporary table for the purpose of demonstration.
DECLARE #Test TABLE (PlanID int, Label varchar(50), Average int);
insert into #Test values
(1,'S03_CreatePlansdadsada',10),
(1,'S03_CreatePlan_T01_NavigateTosdsadsaded',20),
(1,'S03_CreatePlan_T03_abcdefgmanagementsdasda',30),
(1,'S03_CreatePlan_T16_SetStatusToOngoingasdasda',40),
(1,'S03_CreatePlan_T17_Ldsdssdadsadas',50),
(1,'S03_CreatePlansdadsada',60),
(1,'S03_CreatePlan_T01_NavigateTosdsadsaded',70),
(1,'S03_CreatePlan_T03_abcdefgmanagementsdasda',80),
(1,'S03_CreatePlan_T16_SetStatusToOngoingasdasda',90),
(1,'S03_CreatePlan_T17_Ldsdssdadsadas',100);
IF OBJECT_ID('tempdb..#tmpTest') IS NOT NULL DROP TABLE #tmpTest;
select
PlanID,
S03_CreatePlansdadsada as CreatePlans,
S03_CreatePlan_T01_NavigateTosdsadsaded as T01_NavigateTo,
S03_CreatePlan_T03_abcdefgmanagementsdasda as T03_managements,
S03_CreatePlan_T16_SetStatusToOngoingasdasda as T16_SetStatusToOng,
S03_CreatePlan_T17_Ldsdssdadsadas as T17_Lsd
into #tmpTest
from #Test
pivot (sum(Average) for Label in (
S03_CreatePlansdadsada,
S03_CreatePlan_T01_NavigateTosdsadsaded,
S03_CreatePlan_T03_abcdefgmanagementsdasda,
S03_CreatePlan_T16_SetStatusToOngoingasdasda,
S03_CreatePlan_T17_Ldsdssdadsadas
)
) p;
select * from #tmpTest;
This would return the following results:
PlanID CreatePlans T01_NavigateTo T03_managements T16_SetStatusToOng T17_Lsd
1 70 90 110 130 150
The dynamic way is a bit more complicated, since the SQL needs to be constructed so it can be executed. (beware of code injection)
IF OBJECT_ID('tempdb..#tmpTestData') IS NOT NULL DROP TABLE #tmpTestData;
CREATE TABLE #tmpTestData (PlanID int, Label varchar(50), Average int);
insert into #tmpTestData values
(1,'S03_CreatePlansdadsada',10),
(1,'S03_CreatePlan_T01_NavigateTosdsadsaded',20),
(1,'S03_CreatePlan_T03_abcdefgmanagementsdasda',30),
(1,'S03_CreatePlan_T16_SetStatusToOngoingasdasda',40),
(1,'S03_CreatePlan_T17_Ldsdssdadsadas',50),
(1,'S03_CreatePlansdadsada',60),
(1,'S03_CreatePlan_T01_NavigateTosdsadsaded',70),
(1,'S03_CreatePlan_T03_abcdefgmanagementsdasda',80),
(1,'S03_CreatePlan_T16_SetStatusToOngoingasdasda',90),
(1,'S03_CreatePlan_T17_Ldsdssdadsadas',100);
declare #Columns nvarchar(max);
declare #AliasedColumns nvarchar(max);
set #Columns = STUFF((SELECT ', ' + QUOTENAME(Label) FROM #tmpTestData GROUP BY Label FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(max)') ,1,1,'');
set #AliasedColumns = STUFF((SELECT ', ' + QUOTENAME(Label) +' as '+ QUOTENAME(substring(Label,5,14)) FROM #tmpTestData GROUP BY Label FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(max)') ,1,1,'');
declare #SQL nvarchar(max);
IF OBJECT_ID('tempdb..##tmpPivotTest') IS NOT NULL DROP TABLE ##tmpPivotTest;
set #SQL = 'select PlanID, '+ #AliasedColumns + '
into ##tmpPivotTest
from #tmpTestData pivot (sum(Average) for Label in ('+ #Columns +')) p';
--select #SQL;
exec (#SQL);
select * from ##tmpPivotTest;
IF OBJECT_ID('tempdb..##tmpPivotTest') IS NOT NULL DROP TABLE ##tmpPivotTest;
For an Oracle database it would look something like this:
CREATE TABLE V_Test AS
SELECT * FROM (select Label, Average from Test)
PIVOT (
SUM(Average) AS sum_average FOR (Label) IN (
'S03_CreatePlansdadsada' as CreatePlans,
'S03_CreatePlan_T01_NavigateTosdsadsaded' as T01_NavigateTo,
'S03_CreatePlan_T03_abcdefgmanagementsdasda' as T03_Managements,
'S03_CreatePlan_T16_SetStatusToOngoingasdasda' as T16_SetStatusToOng,
'S03_CreatePlan_T17_Ldsdssdadsadas' as T17_Lsd
)
) p;

convert vertical sql result into horizontal output

I have a table, #t with 16 rows:
id int
description varchar(60)
balance decimal(6,2)
I need the description & balance data, and "select description, balance from #t order by id" will do the job. But ideally, I could do with showing the results horizontally rather than vertically.
Now I know I can build a new table with 16 columns and populate the balance for each such column using much dynamic sql, but, I'm also sure that this can be done a good deal more easily using pivot or something like that - though I dont really understand how.
Can someone please enlighted me?
Thanks
John
Assuming you are using SQL Server you can implement the PIVOT function to convert the rows of data into columns. The basic syntax will be:
select *
from
(
select description, balance
from yourtable
) d
pivot
(
sum(balance)
for description in ([desc1], desc2]) -- replace this with the names of your descriptions
) piv;
Of course if you have an unknown number of description values, then you will need to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(description)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + '
from
(
select description, balance
from yourtable
) x
pivot
(
sum(balance)
for description in (' + #cols + ')
) p '
execute sp_executesql #query

SQL: Is there a clever way to show text data in pivoted columns without having inner queries or aggregate functions?

I have a bunch of data (multiple rows for each unique reference) that needs to be in one row with multiple columns. Some of columns that need to be used have to be further split out as they hold more than one value. This has been done using an unpivot. I now have 7 columns from this 1 original column and it now needs to display statuses against the new 7 columns. I cannot however use a pivot as I need to see the various statuses in the 7 columns and not a min, max or a count.
You can perform this type of shift with a PIVOT function.
Static Pivot (See SQL Fiddle for Demo):
select *
from
(
select reference, jobtypesplit, status
from t1
) x
pivot
(
min(status)
for jobtypesplit in ([DDS], [MBN], [LPN], [WEN], [LLP], [OPE], [SSE])
) p
This can also be done dynamically (See SQL Fiddle)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(jobtypesplit)
FROM t1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT reference, ' + #cols + ' from
(
select reference, jobtypesplit, status
from t1
) x
pivot
(
min(status)
for jobtypesplit in (' + #cols + ')
) p '
execute(#query)