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

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

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 dynamic unpivot table as function

I currently have a query which I am using to unpivot an existing table.
Some background information on the table - each year a new column is added to the table to indicate the $ values for a project ID for that year. With every column added one will be dropped. All these columns are prefixed with 'YR_' followed by the new year. There are constantly 20 'YR_' columns.
I am required to unpivot the 'YR_' columns so that they appear as per below, allowing me to utilize the information easier for several reports -
Before unpivot -
ProjectID YR_16 YR_17 YR_18 YR_19 YR_20
10 0 100 20 25 100
After unpivot -
ProjectID YR Value
10 YR_16 0
10 YR_17 100
10 YR_18 20
10 YR_19 25
10 YR_20 100
Below is the query I am using to create the unpivot table, which will dynamically pick up columns as they are added/dropped
declare #query as NVARCHAR(max);
Declare #cols as NVARCHAR(250) = STUFF((
Select distinct ',' + QUOTENAME(Column_Name)
From INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'F1BWK_PLM_CAPEX'
And COLUMN_NAME like 'YR_%'
For XML Path(''), type)
.value('.','NVARCHAR(MAX)')
,1,1,'');
Select #query = 'With Unpivoted as
(
Select * from F1BWK_PLM_CAPEX U
Unpivot (
Val
For Yr in (' + #cols + ')
)
As UnpivotTable
)
Select U.*
From Unpivoted U
inner join [dbo].[F1_SYPAR_CTL] CTL
on CTL.value = U.WS_VERS
and CTL.PARAM_NAME like ''MBRC_CURR_PLM_BUDVER''
inner join [dbo].[F1_SYPAR_CTL] CTL2
on CTL2.value = U.WS_NAME
and CTL2.PARAM_NAME like ''MBRC_CURR_PLM_BUDWSH''';
Exec(#query);
I am having issues in turning this query into a function so that I can call upon it and save it in SQL Server so that other members of my team can use it when they require it.
This is my first time using unpivot tables and creating functions. Open to suggestions on changing my dynamic unpivot query to best suits my needs.
Thanks in advance
In SQL Server you are not allowed to have dynamic SQL in functions.
You can create an SP that will return a record set generated using your code above.
UPDATE:
For the sake of completeness:
You can also call the above SP using (OPENQUERY) which would allow you to join to other tables without having to save to a temp table first but IMO it is an ugly way do this.
END UPDATE:
Another way is to create a VIEW that would un-pivot this table and create an SP that would regenerate this view after the process that adds a new column is completed e.g.
CREATE TABLE F1BWK_PLM_CAPEX( Val INT, Yr_17 INT, Yr_18 INT )
INSERT INTO F1BWK_PLM_CAPEX
SELECT 1, 10, 12
CREATE VIEW F1BWK_PLM_CAPEX_UNPIVOTED
AS
SELECT *
FROM F1BWK_PLM_CAPEX AS U
UNPIVOT (
Val2 FOR Yr IN (Yr_17, Yr_18)
)
AS UnpivotTable;
CREATE PROCEDURE dbo.Create_F1BWK_PLM_CAPEX_UNPIVOTED
AS
SET NOCOUNT ON
DECLARE #query as NVARCHAR(max);
DECLARE #cols as NVARCHAR(250) =
STUFF((
SELECT distinct ',' + QUOTENAME( Column_Name )
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'F1BWK_PLM_CAPEX'
AND COLUMN_NAME like 'YR_%'
For XML Path(''), type)
.value('.','NVARCHAR(MAX)' )
,1,1,'');
SELECT #query = '
ALTER VIEW F1BWK_PLM_CAPEX_UNPIVOTED
AS
SELECT *
FROM F1BWK_PLM_CAPEX AS U
UNPIVOT (
Val2 FOR Yr IN ( ' + #cols + ' )
)
AS UnpivotTable
';
EXEC(#query);
RETURN;
Now your query would become this:
SELECT *
FROM F1BWK_PLM_CAPEX_UNPIVOTED AS U
INNER JOIN [dbo].[F1_SYPAR_CTL] AS CTL
ON CTL.value = U.WS_VERS and CTL.PARAM_NAME = 'MBRC_CURR_PLM_BUDVER'
INNER JOIN [dbo].[F1_SYPAR_CTL] AS CTL2
ON CTL2.value = U.WS_NAME AND CTL2.PARAM_NAME = 'MBRC_CURR_PLM_BUDWSH'
At the end of the year a process runs that adds a new column:
ALTER TABLE F1BWK_PLM_CAPEX
ADD Yr_19 INT NOT NULL DEFAULT( 10 )
This process needs to run this SP to regenerate the view:
EXEC Create_F1BWK_PLM_CAPEX_UNPIVOTED
Notes:
I have replaced LIKE with = as your query matches on a full value. Only use LIKE when you need to specify wild cards.
I have also fixed a syntax error by changing Val to Val2 in the UNPIVOT query

Change Rows to Columns in SQL Server

I've been working on this query for an hour and half but I can't get it done,
First, this is my query:
SELECT
Questions, PossibleAnswer,
((COUNT(PossibleAnswer) + 0.0) / 10 ) * 100 AS Percentage
FROM
(SELECT
A.AnswerID, B.Questions, B.QuestionID, C.PossibleAnswer
FROM
TblSurveyCustomerAnswers A
INNER JOIN
TblSurveyQuestion B ON A.QuestionID = B.QuestionID
INNER JOIN
TblSurveyAnswer C ON A.AnswerID = C.AnswerID
WHERE
A.CustomerID = 1) AS SOURCE
GROUP BY
Questions, PossibleAnswer
The result is below:
Now, I want the rows for column name PossibleAnswer to be converted in columns, so I did a research and found the PIVOT command (I need dynamic since it's a possible answers field) and this is my code
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME(PossibleAnswer)
FROM
(
SELECT DISTINCT X.*
FROM
(
SELECT Questions,PossibleAnswer, ((COUNT(PossibleAnswer) + 0.0) / 10 ) * 100 AS Percentage
FROM
(
SELECT A.AnswerID,B.Questions, B.QuestionID, C.PossibleAnswer
FROM TblSurveyCustomerAnswers A
INNER JOIN TblSurveyQuestion B
ON A.QuestionID = B.QuestionID
INNER JOIN TblSurveyAnswer C
ON A.AnswerID = C.AnswerID
WHERE A.CustomerID = 1
) AS SOURCE
GROUP BY Questions, PossibleAnswer
) X
) AS B
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
'SELECT Questions, ' + #ColumnName + '
FROM TblSurveyCustomerAnswers A
INNER JOIN TblSurveyQuestion B
ON A.QuestionID = B.QuestionID
PIVOT(Max(Questions)
FOR PossibleAnswer IN (' + #ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
And I can't get the pivot work, need help. I'm stuck. See this error:
In general for questions like these you should provide sample data, table definitions and expected output so people can take your script, fiddle with it and produce something that works. See How to post a T-SQL question on a public forum for one way to do this.
Since it is hard to look at a dynamic script, not having the table structures, and point at what your problem is, let me give you the following advice:
Instead of taking your big query that produces the output and form queries around that big query, first insert the output of that query into a temporary table. You can do this by placing an INTO #temp_table clause after the SELECT clause. This creates a new temporary table #temp_table containing the output of the query.
SELECT --your select columns
INTO #p_in -- creates a temporary table #p_in that contains the output
FROM --the rest of your query
Determine the pivot columns based on the newly created temporary table. It'll be a lot more conscise and easier to spot errors
Write your Dynamic SQL using the temporary table (again it'll be a lot more conscise and easier to spot errors)
Don't forget to DROP the temporary table after executing the dynamic SQL.
I just try to solved problem without temporary table. You may edit query as your requirement.
--For PIVOT column
DECLARE #ColumnName AS NVARCHAR(MAX)
SELECT #ColumnName ''''+ PossibleAnswer + '''' + ' , ' + #ColumnName
FROM
(
SELECT
DISTINCT PossibleAnswer
FROM
(
SELECT
A.AnswerID, B.Questions, B.QuestionID, C.PossibleAnswer
FROM
TblSurveyCustomerAnswers A
INNER JOIN
TblSurveyQuestion B ON A.QuestionID = B.QuestionID
INNER JOIN
TblSurveyAnswer C ON A.AnswerID = C.AnswerID
WHERE
A.CustomerID = 1
) AS SOURCE
)B
--For removing last comma
IF #ColumnName != ''
BEGIN
SET #ColumnName = SUBSTRING(#ColumnName, 1, LEN(#ColumnName)-1)
END
-- Make result
SELECT *
FROM
(
SELECT Questions,PossibleAnswer, ((COUNT(PossibleAnswer) + 0.0) / 10 ) * 100 AS Percentage
FROM
(
SELECT A.AnswerID,B.Questions, B.QuestionID, C.PossibleAnswer
FROM TblSurveyCustomerAnswers A
INNER JOIN TblSurveyQuestion B
ON A.QuestionID = B.QuestionID
INNER JOIN TblSurveyAnswer C
ON A.AnswerID = C.AnswerID
WHERE A.CustomerID = 1
) AS SOURCE
GROUP BY Questions, PossibleAnswer
)C
PIVOT
( Max(Questions)
FOR PossibleAnswer IN (#ColumnName)
) AS PVTTable

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)