SQL sum by year report, looking for an elegant solution - sql

I have a table with 3 columns: ItemCode, Quantity and DocDate.
I would like to create the following report in a more "elegant" way:
SELECT T0.ItemCode,
(SELECT SUM(QUANTITY) FROM MyTable T1 WHERE YEAR(T0.DocDate) = 2011 AND T0.ItemCode = T1.ItemCode) AS '2011',
(SELECT SUM(QUANTITY) FROM MyTable T1 WHERE YEAR(T0.DocDate) = 2012 AND T0.ItemCode = T1.ItemCode) AS '2012'
FROM MyTable T0
GROUP BY T0.ItemCode, YEAR(T0.DocDate)
I'm pretty sure there's a better, more efficient way to write this but I can't come up with the right syntax. Any ideas?

You can try this:
SELECT T0.ItemCode,
SUM(CASE WHEN YEAR(T0.DocDate) = 2011 THEN QUANTITY ELSE 0 END) AS '2011',
SUM(CASE WHEN YEAR(T0.DocDate) = 2012 THEN QUANTITY ELSE 0 END) AS '2012'
FROM MyTable T0
GROUP BY
T0.ItemCode

This type of data transformation is known as a PIVOT. There are several ways that you can perform this operation. You can use the PIVOT function or you can use an aggregate function with a CASE statement:
Static Pivot Version: This is where you hard-code all of the values into the query
select ItemCode, [2011], [2012]
from
(
SELECT ItemCode,
QUANTITY,
YEAR(DocDate) Year
FROM MyTable
) src
pivot
(
sum(quantity)
for year in ([2011], [2012])
) piv
See SQL Fiddle with Demo
Case with Aggregate:
SELECT ItemCode,
SUM(CASE WHEN YEAR(DocDate) = 2011 THEN QUANTITY ELSE 0 END) AS '2011',
SUM(CASE WHEN YEAR(DocDate) = 2012 THEN QUANTITY ELSE 0 END) AS '2012'
FROM MyTable
GROUP BY ItemCode;
See SQL Fiddle with Demo
Dynamic Pivot: The previous two versions will work great is you have a known number of year values to transform, but it you have an unknown number then you can use dynamic sql:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(YEAR(DocDate))
from mytable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT itemcode, ' + #cols + ' from
(
select itemcode, quantity, year(docdate) year
from mytable
) x
pivot
(
sum(quantity)
for year in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
All three versions will produce the same result:
| ITEMCODE | 2011 | 2012 |
--------------------------
| 1 | 200 | 45 |
| 2 | 89 | 0 |
| 3 | 0 | 7 |

Related

SQL Server - Convert columns to rows

I have a table on which simple select gives out put like below
I want to write a select statement to output like below
Can someone help me...
Since you are basically rotating your current columns of Sale, Income and Profit into rows and then move the month values to columns, then you will want to first unpivot the current columns, then pivot the months.
Depending on your version of SQL Server there are a few ways that you can unpivot the data. You can use the UNPIVOT function or CROSS APPLY:
select month, type, value
from yourtable
cross apply
(
select 'Sale', sale union all
select 'Income', Income union all
select 'Profit', Profit
) c (type, value)
See SQL Fiddle with Demo. This will convert your current data into:
| MONTH | TYPE | VALUE |
|-------|--------|-------|
| Jan | Sale | 100 |
| Jan | Income | 50 |
| Jan | Profit | 10 |
| Feb | Sale | 20 |
| Feb | Income | 40 |
Then you can use the PIVOT function to convert the months into your column headers.
select type, Jan, Feb, Mar, Apr
from
(
select month, type, value
from yourtable
cross apply
(
select 'Sale', sale union all
select 'Income', Income union all
select 'Profit', Profit
) c (type, value)
) d
pivot
(
sum(value)
for month in (Jan, Feb, Mar, Apr)
) piv;
See SQL Fiddle with Demo.
if you have an unknown number of months, then you can use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct N',' + QUOTENAME(Month)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT type, ' + #cols + N'
from
(
select month, type, value
from yourtable
cross apply
(
select ''Sale'', sale union all
select ''Income'', Income union all
select ''Profit'', Profit
) c (type, value)
) x
pivot
(
sum(value)
for month in (' + #cols + N')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo
You Can Use UNPIVOT then PIVOT
THE BEST IS TO DO QUERY EMBEDED SQL
create DISTINCT COLUMS of months with STUFF function then
replace FOR oMonth IN ([January-2013], [February-2013], [March-2013], [April-2013])
here core query
SELECT
*
FROM
( SELECT
oMonth, value,col
from (
select DATENAME(month,oDate) + '-' + CAST(YEAR( oDate) as varchar) as oMonth, Sales ,Income,Profit
FROM SalesSource
)A
unpivot
(
value for col in ( Sales ,Income,Profit)
) u
) as sourceTable
PIVOT
(
sum( value)
FOR oMonth IN ([January-2013], [February-2013], [March-2013], [April-2013])
) AS PivotTable;

SUMIFS (Sum if with multiple conditions) with SQL Server

This should be done easy in Excel, however, I would like to have this kind of calculation done via SQL. I could use the GROUP BY , OVER() to calculate the SUM and % of a single year. But I failed to present the data 3 years at once. Any help will be appreciated.
Since you are using SQL Server, if you are using SQL Server 2005+ then you can use the PIVOT function to get the result. This solution implements both an unpivot and a pivot process to get the result. The starting point for this result is to calculate the total percent and total by type:
select type, year, total,
round(total / sum(total) over(partition by year)*100.0, 1) t_per,
sum(total) over(partition by type) t_type,
round(sum(total) over(partition by type)*100.0/sum(total) over(), 1) tot_per
from tablea
See SQL Fiddle with Demo. This will give a result with multiple columns that you want to pivot so you can unpivot the data into multiple rows using CROSS APPLY:
select type,
col = cast(year as varchar(4))+'_'+col,
value,
t_type
from
(
select type, year, total,
round(total / sum(total) over(partition by year)*100.0, 1) t_per,
sum(total) over(partition by type) t_type,
round(sum(total) over(partition by type)*100.0/sum(total) over(), 1) tot_per
from tablea
) d
cross apply
(
select 'total', total union all
select 't_per', t_per
) c (col, value);
See Demo. Finally you can apply the PIVOT function to the values in col:
select type,
[2010_total], [2010_t_per],
[2011_total], [2011_t_per],
[2012_total], [2012_t_per],
t_type,
tot_per
from
(
select type,
col = cast(year as varchar(4))+'_'+col,
value,
t_type,
tot_per
from
(
select type, year, total,
round(total / sum(total) over(partition by year)*100.0, 1) t_per,
sum(total) over(partition by type) t_type,
round(sum(total) over(partition by type)*100.0/sum(total) over(), 1) tot_per
from tablea
) d
cross apply
(
select 'total', total union all
select 't_per', t_per
) c (col, value)
) s
pivot
(
max(value)
for col in ([2010_total], [2010_t_per],
[2011_total], [2011_t_per],
[2012_total], [2012_t_per])
) piv
See SQL Fiddle with Demo. This could be refactored to use a CTE instead of the subqueries and this could also be converted to use dynamic SQL if the year will be unknown.
If you have an unknown number of values, then the dynamic SQL code will be:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(cast(year as varchar(4))+'_'+col)
from tablea
cross apply
(
select 'total', 1 union all
select 't_per', 2
) c (col, so)
group by year, col, so
order by year, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT type,' + #cols + ', t_type, tot_per
from
(
select type,
col = cast(year as varchar(4))+''_''+col,
value,
t_type,
tot_per
from
(
select type, year, total,
round(total / sum(total) over(partition by year)*100.0, 1) t_per,
sum(total) over(partition by type) t_type,
round(sum(total) over(partition by type)*100.0/sum(total) over(), 1) tot_per
from tablea
) d
cross apply
(
select ''total'', total union all
select ''t_per'', t_per
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
See Demo. Both the static version and the dynamic version give the result:
| TYPE | 2010_TOTAL | 2010_T_PER | 2011_TOTAL | 2011_T_PER | 2012_TOTAL | 2012_T_PER | T_TYPE | TOT_PER |
---------------------------------------------------------------------------------------------------------
| A | 1 | 16.7 | 1 | 16.7 | 1 | 16.7 | 3 | 16.7 |
| B | 2 | 33.3 | 2 | 33.3 | 2 | 33.3 | 6 | 33.3 |
| C | 3 | 50 | 3 | 50 | 3 | 50 | 9 | 50 |
SUMIF can be replicated in SQL with SUM(case statement):
SELECT Type
,SUM(CASE WHEN Year = '2010' THEN Total ELSE 0 END)'2010 Total'
,SUM(CASE WHEN Year = '2010' THEN Total ELSE 0 END)*1.0/SUM(SUM(CASE WHEN Year = '2010' THEN Total ELSE 0 END)) OVER () '2010 Percent of Total'
,SUM(CASE WHEN Year = '2011' THEN Total ELSE 0 END)'2011 Total'
,SUM(CASE WHEN Year = '2011' THEN Total ELSE 0 END)*1.0/SUM(SUM(CASE WHEN Year = '2011' THEN Total ELSE 0 END)) OVER () '2011 Percent of Total'
,SUM(CASE WHEN Year = '2012' THEN Total ELSE 0 END)'2012 Total'
,SUM(CASE WHEN Year = '2012' THEN Total ELSE 0 END)*1.0/SUM(SUM(CASE WHEN Year = '2012' THEN Total ELSE 0 END)) OVER () '2012 Percent of Total'
,SUM(Total) 'Total'
,SUM(Total)*1.0/SUM(SUM(Total)) OVER () 'Percent of Total'
FROM Table
GROUP BY Type
For the sake of simplicity I would prefer to show this result vertically:
SELECT
Type,
Year,
SUM(Total) as Dollars,
ROUND(SUM(Total) * 100 / (SELECT SUM(TOTAL) FROM TableA t2 WHERE t2.Year = t1.Year),1) as Per
FROM TableA t1
Group By Type, Year
Output:
TYPE YEAR DOLLARS PERCENT
A 2010 1 16.7
B 2010 2 33.3
C 2010 3 50
A 2011 1 16.7
B 2011 2 33.3
C 2011 3 50
A 2012 1 16.7
B 2012 2 33.3
C 2012 3 50
Sql Fiddle Demo

Is it possible to have multiple pivots using the same pivot column using SQL Server

I am facing the following challenge. I need to rotate table data twice over the same column.
Here's a screenshot of the data.
I want to have one row for each Item ID containing both the purchasing value and the selling value for each year.
I tried doing this by selecting the "year" column twice, formatting it a bit so each selling year gets prefixed with a "S" and each purchasing year begins with a "P", and using 2 pivots to rotate around the 2 year columns. Here's the SQL query (used in SQL Server 2008):
SELECT [Item ID],
[P2000],[P2001],[P2002],[P2003],
[S2000],[S2001],[S2002],[S2003]
FROM
(
SELECT [Item ID]
,'P' + [Year] AS YearOfPurchase
,'S' + [Year] AS YearOfSelling
,[Purchasing value]
,[Selling value]
FROM [ItemPrices]
) AS ALIAS
PIVOT
(
MIN ([Purchasing value]) FOR [YearOfPurchase] in ([P2000],[P2001],[P2002],[P2003])
)
AS pvt
PIVOT
(
MIN ([Selling value]) FOR [YearOfSelling] in ([S2000],[S2001],[S2002],[S2003])
)
AS pvt2
The result is not exactly what I was hoping for (see image below):
As you can see, there are still more than one row for each item ID. Is there a way to reduce the number of rows to exactly one per item? So that it looks a bit like the Excel screenshot below?
My suggestion would be to apply both the UNPIVOT and the PIVOT functions to get the result.
The UNPIVOT will turn the PurchasingValue and SellingValue columns into rows. Once this is done, then you can pivot the data into your result.
The code will be:
select *
from
(
select itemid,
case
when col = 'PurchasingValue' then 'P'
when col = 'SellingValue' then 'S'
end + cast(year as varchar(4)) new_col,
value
from yourtable
unpivot
(
value
for col in ([PurchasingValue], [SellingValue])
) unpiv
) src
pivot
(
max(value)
for new_col in (P2000, P2001, P2002, P2003,
S2000, S2001, S2002, S2003)
) piv;
See SQL Fiddle with Demo. The result is:
| ITEMID | P2000 | P2001 | P2002 | P2003 | S2000 | S2001 | S2002 | S2003 |
--------------------------------------------------------------------------
| 1 | 1000 | 1100 | 1200 | 1300 | 900 | 990 | 1080 | 1170 |
| 2 | 500 | 550 | 600 | 650 | 450 | 495 | 540 | 585 |
In SQL Server 2008+ you can use CROSS APPLY with VALUES along with the PIVOT function:
select *
from
(
select itemid,
col+cast(year as varchar(4)) new_col,
value
from yourtable
cross apply
(
VALUES
(PurchasingValue, 'P'),
(SellingValue, 'S')
) x (value, col)
) src
pivot
(
max(value)
for new_col in (P2000, P2001, P2002, P2003,
S2000, S2001, S2002, S2003)
) piv
See SQL Fiddle with Demo
One easy way to pivot multiple columns is to just use Aggregate(Case) expressions.
SELECT [Item ID],
[P2000] = SUM(CASE WHEN [Year] = 2000 THEN [Purchasing value] END),
[P2001] = SUM(CASE WHEN [Year] = 2001 THEN [Purchasing value] END),
[P2002] = SUM(CASE WHEN [Year] = 2002 THEN [Purchasing value] END),
[P2003] = SUM(CASE WHEN [Year] = 2003 THEN [Purchasing value] END),
[S2000] = SUM(CASE WHEN [Year] = 2000 THEN [Selling value] END),
[S2001] = SUM(CASE WHEN [Year] = 2001 THEN [Selling value] END),
[S2002] = SUM(CASE WHEN [Year] = 2002 THEN [Selling value] END),
[S2003] = SUM(CASE WHEN [Year] = 2003 THEN [Selling value] END)
FROM ItemPrices
GROUP BY [Item ID]
Use a GROUP BY ItemID, with aggregate function SUM(isnull(value,0)) on each of the results columns.

SQL, multiple rows to one column

So I have the following date
ID NAME MONTH COUNT
1 David December2012 500
2 Rob December2012 320
1 David January2013 400
2 Rob January2013 280
I am trying to make this.......
ID Name December2012 January2013
1 David 500 400
2 Rob 320 280
Where I get confused is how I want to keep two of the columns and just pivot the two other fields. Anyone know how I would do this.
Thank you so much for your help/time. I have never posted one of these, and responses are greatly appreciated!
You did not specify what RDBMS you are using. You can pivot the data in all databases using an aggregate function with a CASE expression:
select id, name,
sum(case when month = 'December2012' then "count" end) December2012,
sum(case when month = 'January2013' then "count" end) January2013
from yourtable
group by id, name
See SQL Fiddle with Demo
If you are using SQL Server 2005+ or Oracle 11g then you can use the PIVOT function:
select *
from
(
select id, name, month, [count]
from yourtable
) src
pivot
(
sum([count])
for month in (December2012, January2013)
) piv
See SQL Fiddle with Demo.
In SQL Server, if the values of the month are unknown then you can use dynamic SQL similar to this:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(month)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, name,' + #cols + ' from
(
select id, name, month, [count]
from yourtable
) x
pivot
(
sum([count])
for month in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
All versions yield the result:
| ID | NAME | DECEMBER2012 | JANUARY2013 |
-------------------------------------------
| 1 | David | 500 | 400 |
| 2 | Rob | 320 | 280 |
Since you didn't specify what RDBMS you are using, then you can do this:
SELECT
ID,
NAME,
MAX(CASE WHEN MONTH = 'December2012' THEN "COUNT" END) AS "December2012",
MAX(CASE WHEN MONTH = 'January2013' THEN "COUNT" END) AS "January2013"
FROM Tablename
GROUP BY ID, Name;

rows into columns [duplicate]

This question already has answers here:
SQL turning values returned in 11 rows into 89 total columns
(2 answers)
Closed 9 years ago.
this is my query
select * from dbo.tblHRIS_ChildDetails where intSID=463
output:
intCHID intsid nvrchildname nvrgender dttchildDOB Occupation
3 463 SK Female 2001-12-11 00:00:00.000 Studying
4 463 SM Male 2007-10-08 00:00:00.000 Student
i need the output like this this is query is dynamic it may return n number of rows based on the intSID
chidname1 gender DOB childoccupation1 chidname2 gender DOB childoccupation2
SK female 2001-12-11 00:00:00.000 studying SM Male 2007-10-08 00:00:00.000 Student
For this type of data, you will need to implement both the UNPIVOT and then the PIVOT functions of SQL Server. The UNPIVOT takes your data from the multiple columns and place it into two columns and then you apply the PIVOT to transform the data back into columns.
If you know all of the values that you want to transform, then you can hard-code it, similar to this:
select *
from
(
select value, col+'_'+cast(rn as varchar(10)) col
from
(
select nvrchildname,
nvrgender,
convert(varchar(10), dttchildDOB, 120) dttchildDOB,
occupation,
row_number() over(partition by intsid order by intCHID) rn
from tblHRIS_ChildDetails
where intsid = 463
) src
unpivot
(
value
for col in (nvrchildname, nvrgender, dttchildDOB, occupation)
) unpiv
) src1
pivot
(
max(value)
for col in ([nvrchildname_1], [nvrgender_1],
[dttchildDOB_1], [occupation_1],
[nvrchildname_2], [nvrgender_2],
[dttchildDOB_2], [occupation_2])
) piv
See SQL Fiddle with Demo
Now, if you have an unknown number of values to transform, then you can use dynamic SQL for this:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX)
select #colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('tblHRIS_ChildDetails') and
C.name not in ('intCHID', 'intsid')
for xml path('')), 1, 1, '')
select #colsPivot = STUFF((SELECT ','
+ quotename(c.name
+'_'+ cast(t.rn as varchar(10)))
from
(
select row_number() over(partition by intsid order by intCHID) rn
from tblHRIS_ChildDetails
) t
cross apply sys.columns as C
where C.object_id = object_id('tblHRIS_ChildDetails') and
C.name not in ('intCHID', 'intsid')
group by c.name, t.rn
order by t.rn
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select *
from
(
select col+''_''+cast(rn as varchar(10)) col, value
from
(
select nvrchildname,
nvrgender,
convert(varchar(10), dttchildDOB, 120) dttchildDOB,
occupation,
row_number() over(partition by intsid order by intCHID) rn
from tblHRIS_ChildDetails
where intsid = 463
) x
unpivot
(
value
for col in ('+ #colsunpivot +')
) u
) x1
pivot
(
max(value)
for col in ('+ #colspivot +')
) p'
exec(#query)
See SQL Fiddle with Demo
The result of both queries is:
| NVRCHILDNAME_1 | NVRGENDER_1 | DTTCHILDDOB_1 | OCCUPATION_1 | NVRCHILDNAME_2 | NVRGENDER_2 | DTTCHILDDOB_2 | OCCUPATION_2 |
-----------------------------------------------------------------------------------------------------------------------------
| SK | Female | 2001-12-11 | Studying | SM | Male | 2007-10-08 | Student |
You have to provide distinct column names but besides that, it's simple. But as others stated, this is not commonly done and looks like if you want print some report with two columns.
select
max(case when intCHID=1 then nvrchildname end) as chidname1,
max(case when intCHID=1 then gender end) as gender1,
max(case when intCHID=1 then dttchildDOB end) as DOB1,
max(case when intCHID=1 then Occupation end) as cildOccupation1,
max(case when intCHID=2 then nvrchildname end) as chidname2,
max(case when intCHID=2 then gender end) as gender2,
max(case when intCHID=2 then dttchildDOB end) as DOB2,
max(case when intCHID=2 then Occupation end) as cildOccupation2
from
dbo.tblHRIS_ChildDetails
where
intSID=463