Unpivot Data with Multiple Columns - Syntax Help Please - sql

I have the following data in which I would like to unpivot
I created this query to unpivot the 'Actual' rows but can't seem to figure out the syntax for unpivoting the 'Plan' and 'PriorYear' as well.
SELECT FiscalYear, Period, MetricName, ActualValue
FROM vw_ExecSummary
UNPIVOT
(ActualValue FOR MetricName IN ( [Net Revenue], [Total C.S. Salaries]
)) AS unpvt
WHERE [Type] = 'Actual'
The unpivoted data looks like this but I want to also add the Plan and PriorYear columns to the right of the ActualValue column below
Any help would be greatly appreciated. Thanks.

I can't test this at the moment, but I think it works like this. First, you need to UNPIVOT all the data:
SELECT fiscalYear, period, type, metricName, metricValue
FROM vw_ExecSummary
UNPIVOT (metricValue FOR metricName IN ([Net Revenue], [Total C.S. Salaries])) unpvt
Which should result in a table that looks something like this:
fiscalYear period type metricName metricValue
===================================================================
15 1 'Actual' 'Net Revenue' 3676798.98999997
15 1 'Actual' 'Total C.S. Salaries' 1463044.72
15 1 'Plan' 'Net Revenue' 3503920.077405
...................... (remaining rows omitted)
We could then PIVOT the rows as normal to get the new columns (that's what it's for):
SELECT fiscalYear, period, metricName,
[Actual] AS actualValue, [Plan] AS planValue, [PriorYear] AS priorYearValue
FROM <previous_data>
PIVOT (SUM(metricValue) FOR (type IN ([Actual], [Plan], [PriorYear]) pvt
(the SUM(...) shouldn't actually do anything here, as presumable the other columns comprise a unique row, but we're required to use an aggregate function)
...which should yield something akin to the following:
fiscalYear period metricName actualValue planValue priorYearValue
======================================================================================
15 1 'Net Revenue' 3676798.98999997 3503920.077405 40436344.4499999
...................................... (remaining rows omitted)
So putting it together would look like this:
SELECT fiscalYear, period, metricName,
[Actual] AS actualValue, [Plan] AS planValue, [PriorYear] AS priorYearValue
FROM (SELECT fiscalYear, period, type, metricName, metricValue
FROM vw_ExecSummary
UNPIVOT (metricValue FOR metricName IN ([Net Revenue], [Total C.S. Salaries])) unpvt) unpvt
PIVOT (SUM(metricValue) FOR type IN ([Actual], [Plan], [PriorYear])) AS pvt
SQL Fiddle Example
I have just one concern, though: values like 3676798.98999997, 3503920.077405, etc, make me think those columns are floating point (ie, REAL or FLOAT),but the values are named for monetary uses. If this is the case.... you are aware floating-point values can't store things like .1 exactly, right (ie, you can't actually add a dime to a value)? And that, when values get large enough, you can't add 1 anymore either? Usually when dealing with monetary values you should be using something based on a fixed-point type, like DECIMAL or NUMERIC.

This is a situation for Itzig Ben-Gan's cross apply values pivoting:
create table #data (FiscalYear smallint, Period tinyint, ValueType nvarchar(25), NetRevenue float, Salaries float);
insert into #data values
(15,1,N'Actual',3676798.98999,1463044.71999),
(15,1,N'Plan',3503920.977405,1335397.32878),
(15,1,N'PriorYear',4043634.449,1543866.89);
select d.FiscalYear, d.Period,
ActualNetRevenue = sum(v.ActualNetRevenue), ActualSalaries = sum(v.ActualSalaries),
PlanNetRevenue = sum(v.PlanNetRevenue), PlanSalaries = sum(v.PlanSalaries),
PriorYearNetRevenue = sum(v.PriorYearNetRevenue), PriorYearSalaries = sum(v.PriorYearSalaries)
from #data d
cross apply
(values
(N'Actual',d.NetRevenue,d.Salaries,0,0,0,0),
(N'Plan',0,0,d.NetRevenue,d.Salaries,0,0),
(N'PriorYear',0,0,0,0,d.NetRevenue,d.Salaries))
v (ValueType, ActualNetRevenue, ActualSalaries, PlanNetRevenue, PlanSalaries, PriorYearNetRevenue, PriorYearSalaries)
where d.ValueType = v.ValueType
group by d.FiscalYear, d.Period;
drop table #data;

Related

How to make rows into columns in SQL

I have this table
SELECT PolicyID, ItemID, Period, Inventory1, Inventory2
FROM tblInventory
I want to convert this table into this:
The period will be converted as Column such as Inventory1 -1 and Inventory2 -1 until 4th period, per period there are two columns included: inventory1 and inventory2.
I would like to ask help on how to code this in SQL. Thank you!
To use PIVOT with your example you want to UNPIVOT the data first.
SELECT
*
FROM
(
SELECT
UP.PolicyId
, UP.ItemId
, CONCAT(UP.Inventories, '-', UP.Period) AS Inventories
, UP.Inventory
FROM
tblInventory AS TI
UNPIVOT
(
Inventory FOR Inventories IN (Inventory1, Inventory2)
) AS UP
) AS UNP
PIVOT
(
MAX(Inventory)
FOR Inventories IN
(
[Inventory1-1], [Inventory1-2], [Inventory1-3], [Inventory1-4]
, [Inventory2-1], [Inventory2-2], [Inventory2-3], [Inventory2-4]
)
) AS PVT

Different results in SQL based on what columns I display

I am trying to run a query to gather the total items on hand in our database. However it seems i'm getting incorrect data. I am selecting selecting just the amount field and summing it using joins from separate tables based on certain parameters, however if I display additional fields such as order number, and date all of a sudden im getting different data, even though those fields are being used as filters in the query. Is it because its not in the select statement? If it needs to be in the select statement is it possible to not display them?
Here are the two queries.
-- Items On Hand
select CONVERT(decimal(25, 2), SUM(tw.amount)) as 'Amt'
from [Sales Header] sh
join
(
select *
from TWAllOrders
where [Status] like 'Released'
) tw
on tw.[Order Nb] = sh.No_
join
(
select *
from OnHand
) oh
on tw.No_ = oh.[Item No_]
where sh.[Requested Delivery Date] < getdate()
HAVING SUM(tw.Quantity) <= SUM(oh.Qty)
providing a sum of 21667457.20
and with the added columns
-- Items On Hand
select CONVERT(decimal(25, 2), SUM(tw.amount)) as 'Amt', [Requested Delivery Date], sh.No_, tw.[Status]
from [Sales Header] sh
join
(
select *
from TWAllOrders
where [Status] like 'Released'
) tw
on tw.[Order Nb] = sh.No_
join
(
select *
from OnHand
) oh
on tw.No_ = oh.[Item No_]
where sh.[Requested Delivery Date] < getdate()
group by sh.[Requested Delivery Date], sh.No_, tw.[Status]
HAVING SUM(tw.Quantity) <= SUM(oh.Qty)
order by sh.[Requested Delivery Date] ASC
Providing a sum of 12319998
I'm self taught in SQL so I may be misunderstanding something obvious, thanks for the help.
With no sample data, I am going to have to demonstrate this in principle. In the latter query you have a GROUP BY meaning the scope of the values in the HAVING will differ, and thus the filtering from said HAVING will be different.
Let's take the following sample data:
CREATE TABLE dbo.MyTable (Grp char(1),
Quantity int,
Required int);
INSERT INTO dbo.MyTable (Grp, Quantity, [Required])
VALUES('a',2,7),
('a',14,2),
('b',4, 7),
('b',3,4),
('c',17,5);
Now we'll perform an overly simplified version of your query:
SELECT SUM(Quantity)
FROM dbo.MyTable
HAVING SUM(Quantity) > SUM(Required);
This brings back the value 40; which is the SUM of all the values in Quantity. A value is returned because the total SUM of Required is 25.
Now let's add a GROUP BY like your second query:
SELECT SUM(Quantity)
FROM dbo.MyTable
GROUP BY Grp
HAVING SUM(Quantity) > SUM(Required);
Now we have 2 rows, with the values 16 and 17 giving a total value of 33. That's because the rows where Grp have a value of 'B' are filtered out, as the SUM of Quantity is lower that Required for 'B'.
The same is happening in your data; in the grouped data you have groups where the HAVING condition isn't met, so those rows aren't returned.

Transpose Columns or unpivot SQL Query

I am currently attempting to transpose some data inside an SQL query however I can not seem to find a solution using un-pivot. Example of the Data I am working with is
SELECT * FROM (SELECT 'ACCOUNTS' AS Dept
, DATENAME(MONTH, GETDATE()) AS [Month]
, '3254' AS [1st Letter]
, '2544' AS [2nd Letter]
, '1254' AS [3rd Letter]
, '64' AS [4th Letter]
) AS t
I will admit I don't fully understand PIVOT and UNPIVOT fully, however I can not seem to work out if it will work in this query? The desired output would be
Dept |ACCOUNTS
Month |May
1st Letter |3254
2nd Letter |2544
3rd Letter |1254
4th Letter |64
I have seen a lot of solutions on Google but not really for what I am looking for, would a unpivot do the below for me.
Yes. It just works.
declare #t table (Dept varchar(20), Month varchar(20), [1st letter]varchar(20),[2nd letter]varchar(20),[3rd letter]varchar(20),[4th letter]varchar(20))
insert #t
SELECT 'ACCOUNTS' AS Dept
, DATENAME(MONTH, GETDATE()) AS [Month]
, '3254' AS [1st Letter]
, '2544' AS [2nd Letter]
, '1254' AS [3rd Letter]
, '64' AS [4th Letter]
SELECT * FROM #t AS t
unpivot (item for value in (Dept, Month, [1st letter],[2nd letter],[3rd letter],[4th letter])) u
SELECT
unpvt.[key],
unpvt.[value]
FROM
t
UNPIVOT
(
[key] FOR [value] IN ([Dept],[Month],[1st letter],[2nd letter],[3rd letter],[4th letter])
)
AS unpvt
The UNPIVOT effectively joins on a new table of two columns. In the case the [key] and the [value].
[key] is the string representation of the field name.
[value] is the value that was stored in that field.
The IN list allows you to specify which fields are being pivoted.
NOTE: This does mean that you need to know the full list of field names in advance; it won't dynamically adjust to include more fields if you add them to the table. Also, take care when your fields have different data-types (though this is not the case in your example).

How to pivot 2 columns into one row

I am trying to pivot a table that has both Units and Base Units for a certain product. I am trying to make my end result one line only.
Product Unit BaseUnit
Gas MMcf Mcf
Oil Mbbl bbl
Water Mgal gal
My output table should look like this:
GasUnit GasBaseUnit OilUnit OilBaseUnit WaterUnit WaterBaseUnit
MMcf Mcf Mbbl bbl Mgal gal
I have done a pivot table before but never had to pivot 2 columns at the same time and move them into one row.
Here is what I have so far. Can I use 2 aggregates? I am not sure how to approach this.
SELECT *
from
(
select Product, Unit, BaseUnit
from t
) x
pivot
(
sum(BaseUnit)
for Product in ([Gas], [Oil], [Water])
) p
With static data this will get your output table
WITH prep AS (
SELECT Product + ' Unit' TYPE, Unit Value
FROM utilities
UNION ALL
SELECT Product + ' BaseUnit' TYPE, BaseUnit Value
FROM utilities
)
SELECT [Gas Unit], [Gas BaseUnit]
, [Oil Unit], [Oil BaseUnit]
, [Water Unit], [Water BaseUnit]
FROM (SELECT TYPE, Value
FROM prep
) a
PIVOT
(MAX(Value)
FOR TYPE IN ([Gas Unit], [Gas BaseUnit]
, [Oil Unit], [Oil BaseUnit]
, [Water Unit], [Water BaseUnit])
) pvt
Demo: SQLFiddle
With dynamic data you'll need to use a dynamic query to create the PIVOT columns list

Inserting and transforming data from SQL table

I have a question which has been bugging me for a couple of days now. I have a table with:
Date
ID
Status_ID
Start_Time
End_Time
Status_Time(seconds) (How ling they were in a certain status, in seconds)
I want to put this data in another table, that has the Status_ID grouped up as columns. This table has columns like this:
Date
ID
Lunch (in seconds)
Break(in seconds)
Vacation, (in seconds) etc.
So, Status_ID 2 and 3 might be grouped under vacation, Status_ID 1 lunch, etc.
I have thought of doing a Case nested in a while loop, to go through every row to insert into my other table. However, I cannot wrap my head around inserting this data from Status_ID in rows, to columns that they are now grouped by.
There's no need for a WHILE loop.
SELECT
date,
id,
SUM(CASE WHEN status_id = 1 THEN status_time ELSE 0 END) AS lunch,
SUM(CASE WHEN status_id = 2 THEN status_time ELSE 0 END) AS break,
SUM(CASE WHEN status_id = 3 THEN status_time ELSE 0 END) AS vacation
FROM
My_Table
GROUP BY
date,
id
Also, keeping the status_time in the table is a mistake (unless it's a non-persistent, calculated column). You are effectively storing the same data in two places in the database, which is going to end up resulting in inconsistencies. The same goes for pushing this data into another table with times broken out by status type. Don't create a new table to hold the data, use the query to get the data when you need it.
This type of query (that transpose values from rows into columns) is named pivot query (SQL Server) or crosstab (Access).
There is two types of pivot queries (generally speaking):
With a fixed number of columns.
With a dynamic number of columns.
SQL Server support both types but:
Database Engine (query language: T-SQL) support directly only pivot
queries with a fixed number of columns(1) and indirectly (2)
Analysis Services (query language: MDX) support directly both types (1 & 2).
Also, you can query(MDX) Analysis Service data sources from T-SQL using OPENQUERY/OPENROWSET functions or using a linked server with four-part names.
T-SQL (only) solutions:
For the first type (1), starting with SQL Server 2005 you can use the PIVOT operator:
SELECT pvt.*
FROM
(
SELECT Date, Id, Status_ID, Status_Time
FROM Table
) src
PIVOT ( SUM(src.Status_Time) FOR src.Status_ID IN ([1], [2], [3]) ) pvt
or
SELECT pvt.Date, pvt.Id, pvt.[1] AS Lunch, pvt.[2] AS [Break], pvt.[3] Vacation
FROM
(
SELECT Date, Id, Status_ID, Status_Time
FROM Table
) src
PIVOT ( SUM(src.Status_Time) FOR src.Status_ID IN ([1], [2], [3]) ) pvt
For a dynamic number of columns (2), T-SQL offers only an indirect solution: dynamic queries. First, you must find all distinct values from Status_ID and the next move is to build the final query:
DECLARE #SQLStatement NVARCHAR(4000)
,#PivotValues NVARCHAR(4000);
SET #PivotValues = '';
SELECT #PivotValues = #PivotValues + ',' + QUOTENAME(src.Status_ID)
FROM
(
SELECT DISTINCT Status_ID
FROM Table
) src;
SET #PivotValues = SUBSTRING(#PivotValues,2,4000);
SELECT #SQLStatement =
'SELECT pvt.*
FROM
(
SELECT Date, Id, Status_ID, Status_Time
FROM Table
) src
PIVOT ( SUM(src.Status_Time) FOR src.Status_ID IN ('+#PivotValues+') ) pvt';
EXECUTE sp_executesql #SQLStatement;