I have following table with some data.
CREATE TABLE #NetProfit (ID int, [Name] varchar(50),[Class] varchar(50), Balance money)
go
--Populate Sample records
INSERT INTO #NetProfit VALUES(4,'Income','No Class',303386.8462)
INSERT INTO #NetProfit VALUES(6,'Expenses','No Class',22443.5317)
INSERT INTO #NetProfit VALUES(4,'Income','2 TestUser3',0.00)
INSERT INTO #NetProfit VALUES(5,'Cost','2 TestUser3',0.3875)
INSERT INTO #NetProfit VALUES(6,'Expenses','2 TestUser3',6439.2129)
INSERT INTO #NetProfit VALUES(5,'Cost','3 TestUser3',0.1395)
INSERT INTO #NetProfit VALUES(6,'Expenses','3 TestUser3',6451.6129)
INSERT INTO #NetProfit VALUES(5,'Cost','38 Code#1012',3.0225)
INSERT INTO #NetProfit VALUES(6,'Expenses','38 Code#1012',30.225)
go
select * from #NetProfit
drop table #NetProfit
+----+----------+--------------+-------------+
| ID | Name | Class | Balance |
+----+----------+--------------+-------------+
| 4 | Income | No Class | 303386.8462 |
| 6 | Expenses | No Class | 22443.5317 |
| 4 | Income | 2 TestUser3 | 0 |
| 5 | Cost | 2 TestUser3 | 0.3875 |
| 6 | Expenses | 2 TestUser3 | 6439.2129 |
| 5 | Cost | 3 TestUser3 | 0.1395 |
| 6 | Expenses | 3 TestUser3 | 6451.6129 |
| 5 | Cost | 38 Code#1012 | 3.0225 |
| 6 | Expenses | 38 Code#1012 | 30.225 |
+----+----------+--------------+-------------+
I want to subtract [Balance] column row wise group by [Class] column.
For ex. NetProfit = (Income - Cost - Expenses) for each [Class] column with group by [Class].
Here is the output I am expecting.
+-------------+-------------+-------------+--------------+
| No Class | 2 TestUser3 | 3 TestUser3 | 38 Code#1012 |
+-------------+-------------+-------------+--------------+
| 280943.3145 | 6439.6004 | 6451.7524 | 33.2475 |
+-------------+-------------+-------------+--------------+
Any help would be appreciated. Thanks!
This could be the first step to achieve desired result:
SELECT NP.Class, ABS(ISNULL(SUM(CASE NP.Name WHEN 'Income' THEN NP.Balance END), 0) - SUM(CASE NP.Name WHEN 'Income' THEN NULL ELSE NP.Balance END)) AS Balance
FROM #NetProfit AS NP
GROUP BY NP.Class;
Then you would have to dynamically pivot these results. Statically, your query should look like that:
SELECT *
FROM (
SELECT NP.Class, ABS(ISNULL(SUM(CASE NP.Name WHEN 'Income' THEN NP.Balance END), 0) - SUM(CASE NP.Name WHEN 'Income' THEN NULL ELSE NP.Balance END)) AS Balance
FROM #NetProfit AS NP
GROUP BY NP.Class) AS SourceTable
PIVOT (MAX(Balance) FOR Class IN ([2 TestUser3], [3 TestUser3], [38 Code#1012], [No Class])) AS PivotTable;
So by plugging in dynamic SQL, This is the result:
DECLARE #SQL NVARCHAR(MAX);
DECLARE #Col NVARCHAR(MAX);
SET #Col = STUFF(( SELECT DISTINCT ',' + QUOTENAME(NP.Class)
FROM #NetProfit AS NP
FOR XML PATH('')), 1, 1, '');
SET #SQL = N'
SELECT *
FROM (
SELECT NP.Class, ABS(ISNULL(SUM(CASE NP.Name WHEN ''Income'' THEN NP.Balance END), 0) - SUM(CASE NP.Name WHEN ''Income'' THEN NULL ELSE NP.Balance END)) AS Balance
FROM #NetProfit AS NP
GROUP BY NP.Class) AS SourceTable
PIVOT (MAX(Balance) FOR Class IN (' + #Col + N')) AS PivotTable';
EXECUTE sys.sp_executesql #SQL;
That's the output:
+-------------+-------------+--------------+-------------+
| 2 TestUser3 | 3 TestUser3 | 38 Code#1012 | No Class |
+-------------+-------------+--------------+-------------+
| 6439,6004 | 6451,7524 | 33,2475 | 280943,3145 |
+-------------+-------------+--------------+-------------+
This query return calculation in rows.
With pivot table you can turn rows to columns
select isnull(c1, c2), isnull(i, 0)-isnull(e,0) from (
select * from
(select class c1, sum(balance) i
from #NetProfit
where Name = 'income'
group by class) i
full join
(
select class c2, sum(balance) e
from #NetProfit
where Name != 'income'
group by class) e on i.c1 = e.c2) t
Try this (assuming ID 4, for income, is a positive whereas the rest should be treated as negatives;
select
[Class No]=SUM(case when [Class]='No Class' then (case when ID=4 then Balance else Balance*-1 end) else 0 end),
[TestUser2]=SUM(case when [Class]='2 TestUser3' then (case when ID=4 then Balance else Balance*-1 end) else 0 end),
[TestUser3]=SUM(case when [Class]='3 TestUser3' then (case when ID=4 then Balance else Balance*-1 end) else 0 end),
[Code#1012]=SUM(case when [Class]='Code#1012' then (case when ID=4 then Balance else Balance*-1 end) else 0 end)
from
#NetProfit
Related
i have table say FIDDLE HERE
+----+------+------+-----+-----+
| id | year | sell | buy | own |
+----+------+------+-----+-----+
| 1 | 2016 | 9 | 2 | 10 |
| 1 | 2017 | 9 | | 10 |
| 1 | 2018 | | 2 | 10 |
| 2 | 2016 | 7 | 2 | 11 |
| 2 | 2017 | 2 | | |
| 2 | 2018 | | | 18 |
+----+------+------+-----+-----+
create table test(id varchar(20), year varchar(20),
sell varchar(20), buy varchar(20),
own varchar(20));
insert into test values('1', '2016','9','2','10' )
insert into test values('1', '2017','9',NULL,'10' )
insert into test values('1', '2018',NULL,'2','10' )
insert into test values('2', '2016','7','2','11' )
insert into test values('2', '2017','2',NULL,'17' )
insert into test values('2', '2018','5','2','18' )
I'm trying to PIVOT but instead of aggregate the values, i wanted to keep some letters if it is not null (S-Sell,B-Buy,O-Own). If there are values for all columns for particular year then i need S_B_O for that year. If there are values only for sell and buy then S_B etc., so Expected output is
+----+-------+------+------+
| ID | 2016 | 2017 | 2018 |
+----+-------+------+------+
| 1 | S_B_O | S_O | B_O |
+----+-------+------+------+
| 2 | S_B_O | S | O |
+----+-------+------+------+
The closest i have got is using conditional aggrgarion( MAX and concat) instead of PIVOT but this is also giving null if any one is NULL. Please suggest a solution.
select ID,
MAX(CASE WHEN Year = '2016' AND sell is not null THEN 'S_' END +
CASE WHEN Year = '2016' AND buy is not null THEN 'B_' END +
CASE WHEN Year = '2016' AND own is not null THEN 'O' END)
AS [2016],
MAX(CASE WHEN Year = '2017' AND sell is not null THEN 'S_' END +
CASE WHEN Year = '2017' AND buy is not null THEN 'B_' END +
CASE WHEN Year = '2017' AND own is not null THEN 'O' END)
AS [2017]
/* ......for all year */
from test
group by id
FIDDLE HERE
You can use CONCAT function, which will handle NULLs automatically.
select ID,
CONCAT(MAX(CASE WHEN Year = '2016' AND sell is not null THEN 'S_' END) ,
MAX(CASE WHEN Year = '2016' AND buy is not null THEN 'B_' END) ,
MAX(CASE WHEN Year = '2016' AND buy is not null THEN 'O' END))
AS [2016],
CONCAT(MAX(CASE WHEN Year = '2017' AND sell is not null THEN 'S_' END) ,
MAX(CASE WHEN Year = '2017' AND buy is not null THEN 'B_' END) ,
MAX(CASE WHEN Year = '2017' AND buy is not null THEN 'O' END))
AS [2017]
from test
group by id
+----+-------+------+
| ID | 2016 | 2017 |
+----+-------+------+
| 1 | S_B_O | S_ |
| 2 | S_B_O | S_ |
+----+-------+------+
UPDATE Dynamic query. As #Larnu told, You should have asked this as separate question. You should not change the requirement.
DECLARE #lst_Years NVARCHAR(MAX) , #query NVARCHAR(MAX)
SET #lst_Years = STUFF((SELECT distinct ',' + QUOTENAME([Year])
FROM test
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = 'SELECT * FROM
(
select ID, [Year],
CONCAT(MAX(CASE WHEN sell is not null THEN ''S_'' END) ,
MAX(CASE WHEN buy is not null THEN ''B_'' END) ,
MAX(CASE WHEN buy is not null THEN ''O'' END))
AS [Value]
from test
group by id, [year]) as t
pivot
(
max(value) FOR YEAR IN (' + #lst_Years + ')
) as pvt'
EXEC(#query)
+----+-------+------+-------+
| ID | 2016 | 2017 | 2018 |
+----+-------+------+-------+
| 1 | S_B_O | S_ | B_O |
| 2 | S_B_O | S_ | S_B_O |
+----+-------+------+-------+
I need to calculate a column that has a value column subtracted from a Total column but can skip rows until it can no longer find a smaller value. The sequence relates to dates so the order must be preserved. The value (Need) cannot be larger than the total as those are deleted prior.
This is for SQL Server 2016. My initial thought process was to use window functions and a running total, but I cannot figure out how to skip the 400 and continue to the 2 rows below. I included my attempts in the CASE statement as TransferQty and the running total as ReferenceCol.
Code to reproduce:
DECLARE #i TABLE
(
sequence INT IDENTITY(1,1)
,Total INT
,Need INT
)
INSERT INTO #i
VALUES (500,100)
,(500,200)
,(500,50)
,(500,400)
,(500,50)
,(500,50)
SELECT
sequence
,Total
,Need
,CASE
WHEN Total - SUM(Need) OVER (ORDER BY sequence) > 0
THEN Need
ELSE 0
END AS TransferQty
,Total - SUM(Need) OVER (ORDER BY sequence) as ReferenceCol
FROM #i
Current Results
+----------+-------+------+-------------+--------------+
| Sequence | Total | Need | TransferQty | ReferenceCol |
+----------+-------+------+-------------+--------------+
| 1 | 500 | 100 | 100 | 400 |
| 2 | 500 | 200 | 200 | 200 |
| 3 | 500 | 50 | 50 | 150 |
| 4 | 500 | 400 | 0 | -250 |
| 5 | 500 | 50 | 0 | -300 |
| 6 | 500 | 50 | 0 | -350 |
+----------+-------+------+-------------+--------------+
Desired Results
+----------+-------+------+-------------+--------------+
| Sequence | Total | Need | TransferQty | ReferenceCol |
+----------+-------+------+-------------+--------------+
| 1 | 500 | 100 | 100 | 400 |
| 2 | 500 | 200 | 200 | 200 |
| 3 | 500 | 50 | 50 | 150 |
| 4 | 500 | 400 | 0 | 150 | --skip calc
| 5 | 500 | 50 | 50 | 100 |
| 6 | 500 | 50 | 50 | 50 |
+----------+-------+------+-------------+--------------+
You should be able to use this code if you have a single skip, but when you have multi skips then you have to loop through and perform the delete of record based on the existence of rolling value exceeding the total.
DECLARE #i TABLE
(
sequence INT IDENTITY(1,1)
,Total INT
,Need INT
)
INSERT INTO #i
VALUES
(500,100 )
,(500,200 )
,(500,50 )
,(500,400 )
,(500,50 )
,(500,50 )
select sequence,Total,Need
into #temp_original
from #i
select
b.sequence,b.Total, SUM( a.need) rollingvalue ,
case when SUM( a.need) > b.Total
then 0
when SUM( a.need) = b.Total then SUM( a.need)
else b.Total - SUM( a.need) end how_much_needed
into #temp
from #i a
join #i b
on a.sequence < b.sequence + 1
group by b.sequence,b.Total
delete from a
from #i a
join (
select min(sequence) min_sequence
from #temp
where how_much_needed = 0
) minseq
on minseq.min_sequence = a.sequence
select
b.sequence,b.Total, SUM( a.need) rollingvalue ,
case when SUM( a.need) > b.Total
then 0
when SUM( a.need) = b.Total then SUM( a.need)
else b.Total - SUM( a.need) end how_much_needed
into #temp2
from #i a
join #i b
on a.sequence < b.sequence + 1
group by b.sequence,b.Total
select a.sequence,a.Total,a.Need, case when isnull (b.rollingvalue , 0) = 0 then 0 else case when b.rollingvalue > a.Total then 0 else a.Need end end as TransferQty , ISNULL( case when b.how_much_needed = b.Total then a.Need else b.how_much_needed end, case when ( select how_much_needed from #temp2 where sequence = a.sequence -1) = a.Total then 0 else (select how_much_needed from #temp where sequence = a.sequence -1) end ) ReferenceCol
from #temp_original a
LEFT join #temp2 b
on a.sequence = b.sequence
join #temp c
on c.sequence = a.sequence
drop table #temp
drop table #temp2
drop table
Here is the solution I went with which is based off of the "Quirky Update" from the original comment.
DROP TABLE IF EXISTS #i
GO
CREATE TABLE #i
(
sequence INT IDENTITY(1,1) PRIMARY KEY CLUSTERED
,Total INT
,Need INT
,RunningTransfer INT NULL
)
INSERT INTO #i
VALUES
(500,100,NULL)
,(500,200,NULL)
,(500,50,NULL)
,(500,400,NULL)
,(500,50,NULL)
,(500,50,NULL)
,(500,100,NULL)
,(500,49,NULL)
,(500,50,NULL)
DECLARE #TransferRunningTotal INT
UPDATE #i
SET #TransferRunningTotal = RunningTransfer = CASE
--this skips values larger than running total
WHEN #TransferRunningTotal < Need THEN #TransferRunningTotal
--this creates the running total
WHEN #TransferRunningTotal > Need THEN #TransferRunningTotal - Need
--creates the initial value
ELSE Total - Need
END
FROM #i WITH (TABLOCKX)
OPTION (MAXDOP 1)
SELECT sequence
,Total
,Need
,CASE
WHEN need <= RunningTransfer THEN Need
ELSE 0
END AS TsfQty
,RunningTransfer
FROM #i
I need to be able to show all my managers in a hierarchy in different columns. I don't know how many levels there will be.
Example: Employee – ManagerOfEmployee - TheBigBoss
I have tried the below but cant get it to work the way I want it.
I need the results to look like this:
Level1Column Level2Column Level3Column
------------------------------------------
1 2 3
Code:
CREATE TABLE #tblHRData
(
Emplid INT,
ReportsToEmplid INT
)
INSERT INTO #tblHRData (Emplid, ReportsToEmplid)
VALUES (1, 2), (2, 3)
;WITH CTE AS
(
SELECT
Emplid,
ReportsToEmplid,
1 AS level
FROM
#tblHRData
WHERE
Emplid = 1
UNION ALL
SELECT
child.Emplid,
child.ReportsToEmplid,
level + 1
FROM
#tblHRData child
JOIN
CTE parent ON child.ReportsToEmplid = parent.Emplid
)
SELECT *
FROM CTE;
With an unknown depth you will have to go the dynamic SQL route. But such cases tend to have a maximal depth. As your columns will have computable names, you can try this:
I enhanced your table a bit:
CREATE TABLE #tblHRData
(
Emplid INT,
ReportsToEmplid INT,
Descr VARCHAR(100)
)
INSERT INTO #tblHRData (Emplid, ReportsToEmplid, Descr)
VALUES (1, 2, 'lvl 3.2.1') --boss is 2
,(2, 3, 'lvl 3.2') --boss is 3
,(3,null, 'big boss')--big boss reports to no one
,(4, 3, 'lvl 3.4') --one more 2nd lvl
,(5, 4, 'lvl 3.4.5') --below 4
,(6, 4, 'lvl 3.4.6') --another one below 4
-- And I changed the recursive CTE to start off with the big boss and to build a sort string on the fly. In this case this is limited to 3 digits. You'll have to widen this with Emplids exceeding 999:
;WITH CTE AS
(
SELECT
Emplid,
ReportsToEmplid,
Descr,
0 AS EmpLvl,
CAST(REPLACE(STR(Emplid,3),' ','0') AS VARCHAR(MAX)) AS SortOrder
FROM
#tblHRData
WHERE
ReportsToEmplid IS NULL --start with the big boss
UNION ALL
SELECT
child.Emplid,
child.ReportsToEmplid,
child.Descr,
parent.EmpLvl + 1,
parent.SortOrder + REPLACE(STR(child.Emplid,3),' ','0')
FROM
#tblHRData child
JOIN
CTE parent ON child.ReportsToEmplid = parent.Emplid
)
SELECT Emplid,
SortOrder,
MAX(CASE WHEN EmpLvl=0 THEN Descr END) AS BossDescr,
MAX(CASE WHEN EmpLvl=1 THEN Descr END) AS Lvl1Descr,
MAX(CASE WHEN EmpLvl=2 THEN Descr END) AS Lvl2Descr,
MAX(CASE WHEN EmpLvl=3 THEN Descr END) AS Lvl3Descr,
MAX(CASE WHEN EmpLvl=4 THEN Descr END) AS Lvl4Descr,
MAX(CASE WHEN EmpLvl=5 THEN Descr END) AS Lvl5Descr
--add as many as you need and add some more to be future safe
FROM CTE
GROUP BY EmpLvl,Emplid,SortOrder
ORDER BY SortOrder;
GO
DROP TABLE #tblHRData
The result
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| Emplid | SortOrder | BossDescr | Lvl1Descr | Lvl2Descr | Lvl3Descr | Lvl4Descr | Lvl5Descr |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 3 | 003 | big boss | NULL | NULL | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 2 | 003002 | NULL | lvl 3.2 | NULL | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 1 | 003002001 | NULL | NULL | lvl 3.2.1 | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 4 | 003004 | NULL | lvl 3.4 | NULL | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 5 | 003004005 | NULL | NULL | lvl 3.4.5 | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 6 | 003004006 | NULL | NULL | lvl 3.4.6 | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
Some remarks:
- I use the conditional aggregation as PIVOT approach. With just one column this can be done with PIVOT() too.
- The SortOrder is important to be created in the recursion. It is kind of the path to the entry and will allow you to order your result-set.
- This path must allow alphanumerical sorting. Therefore I concatenate padded strings.
table:
+-----------+--------------+------------+------------+
| RequestID | RequestStaus | StartDate | EndDate |
+-----------+--------------+------------+------------+
| 1 | pending | 9/1/2015 | 10/2/2015 |
| 1 | in progress | 10/2/2015 | 10/20/2015 |
| 1 | completed | 10/20/2015 | 11/3/2015 |
| 1 | reopened | 11/3/2015 | null |
| 2 | pending | 9/5/2015 | 9/7/2015 |
| 2 | in progress | 9/7/2015 | 9/25/2015 |
| 2 | completed | 9/25/2015 | 10/7/2015 |
| 2 | reopened | 10/10/2015 | 10/16/2015 |
| 2 | completed | 10/16/2015 | null |
+-----------+--------------+------------+------------+
I would like to calculate the days opened but exclude the days between completed and reopened. For example, RequestID 1, the days opened will be (11/3/2015 - 9/1/2015) + (GetDate() - 11/3/2015), for request 2, the total days will be (10/7/2015 - 9/5/2015) + ( 10/16/2015 - 10/10/2015).
The result I want will be something like:
+-----------+-------------------------------+
| RequestID | DaysOpened |
+-----------+-------------------------------+
| 1 | 63 + (getdate() - 11/3/2015) |
| 2 | 38 |
+-----------+-------------------------------+
How do I approach this problem? thank you!
Tested. Works well. :)
Note:
1) I suppose the required result = (FirstCompleteEndDate - PendingStartDate)+(Sum of all the Reopen duration)
2) So I used the self joins. Table b provides the exact completed record which immediately follows the in process record for each RequestID. Table c provides Sum of all the Reopen duration.
--create tbl structure
create table #test (RequestID int, RequestStatus varchar(20), StartDate date, EndDate date)
go
--insert sample data
insert #test
select 1,'pending','09/01/2015','10/2/2015'
union all
select 1,'in progress','10/2/2015','10/20/2015'
union all
select 1,'completed','10/20/2015','11/3/2015'
union all
select 1,'reopened','11/3/2015',null
union all
select 2,'pending','09/05/2015','9/7/2015'
union all
select 2,'in progress','09/07/2015','9/25/2015'
union all
select 2,'completed','9/25/2015','10/7/2015'
union all
select 2,'reopened','10/10/2015','10/16/2015'
union all
select 2, 'completed','10/16/2015','11/12/2015'
union all
select 2,'reopened','11/20/2015',null
select * from #test
--below is solution
select a.RequestID, a.Startdate as [PendingStartDate], b.enddate as [FirstCompleteEndDate], c.startdate as [LatestReopenStartDate],
datediff(day,a.startdate,b.enddate)+c.ReopenDays as [days] from #test a
join (
select *, row_number()over(partition by RequestID,RequestStatus order by StartDate) as rid from #test
) as b
on a.RequestID = b.RequestID
join (
select distinct RequestID, RequestStatus, max(StartDate)over(partition by RequestID,RequestStatus) as StartDate,
Sum(Case when enddate is null then datediff(day,startdate,getdate())
when enddate is not null then datediff(day,startdate,enddate)
end)over(partition by RequestID,RequestStatus) as [ReopenDays]
from #test
where RequestStatus = 'reopened'
) as c
on b.RequestID = c.RequestID
where a.RequestStatus ='pending' and b.RequestStatus = 'completed' and b.rid = 1
Result:
I have data in a table in this structure:
Region | Date | Value
A | 01/01/2014 | 100
A | 01/20/2014 | 50
A | 01/02/2014 | 200
A | 01/05/2014 | 300
B | 01/01/2014 | 50
B | 02/15/2014 | 70
B | 02/25/2014 | 50
C | 05/02/2014 | 70
I am trying to create a pivot view like this using T-SQL queries:
Region | Jan-2014 | Feb-2014 | Mar-014 | Apr-2014 | May-2014 | -> thru desired month-year
A | 150 | 200 | 0 | 0 | 300 |
B | 50 | 120 | 0 | 0 | 0 |
C | 0 | 0 | 0 | 0 | 70 |
Please note, multiple values in the same month for a given region needs to be aggregated
Months that have no records should still show up as columns with zero values (Ex: March and April)
I tired using pivot options, withroll up etc., -- but can't seem to get this to work
any help is much appreciated..
Thank you.
The reason why you do not have results for those months is because you are missing the dates that you are pivoting into columns.
There are a few ways that you can do this. You can hard code all of the date values in the IN portion of your query so the columns will appear:
select Region,
isnull([Jan-2014], 0) [Jan-2014], isnull([Feb-2014], 0) [Feb-2014],
isnull([Mar-2014], 0) [Mar-2014], isnull([Apr-2014], 0) [Apr-2014],
isnull([May-2014], 0) [May-2014], isnull([Jun-2014], 0) [Jun-2014],
isnull([Jul-2014], 0) [Jul-2014], isnull([Aug-2014], 0) [Aug-2014],
isnull([Sep-2014], 0) [Sep-2014], isnull([Oct-2014], 0) [Oct-2014],
isnull([Nov-2014], 0) [Nov-2014], isnull([Dec-2014], 0) [Dec-2014]
from
(
select left(datename(month, t.date), 3) +'-'
+ cast(year(t.date) as char(4)) monthYear,
t.region,
t.value
from yt t
) src
pivot
(
sum(value)
for monthYear in ([Jan-2014], [Feb-2014], [Mar-2014], [Apr-2014],
[May-2014], [Jun-2014], [Jul-2014], [Aug-2014],
[Sep-2014], [Oct-2014], [Nov-2014], [Dec-2014])
) piv;
See SQL Fiddle with Demo.
Another way to do this is to create a table of dates that you can use in your query. Once created, then you can use a LEFT JOIN to your table so you return all of the dates that you want to appear. You can either create a recursive query to generate this list in your PIVOT query, or populate a table with a list of dates. A recursive query will be similar to this:
;with dates (startDate, endDate) as
(
select min(date), cast('2014-12-31' as date)
from yt
union all
select dateadd(m, 1, startDate), enddate
from dates
where month(startDate) + 1 <= month(enddate)
)
select startDate
from dates;
See SQL Fiddle with Demo. If you join this to your PIVOT, then the query will be:
select *
from
(
select left(datename(month, d.startdate), 3) +'-'
+ cast(year(d.startdate) as char(4)) monthYear,
t.region,
t.value
from dates d
left join yt t
on month(d.startdate) = month(t.date)
and year(d.startdate) = year(t.date)
) src
pivot
(
sum(value)
for monthYear in ([Jan-2014], [Feb-2014], [Mar-2014], [Apr-2014],
[May-2014], [Jun-2014], [Jul-2014], [Aug-2014],
[Sep-2014], [Oct-2014], [Nov-2014], [Dec-2014])
) piv
where region is not null;
See SQL Fiddle with Demo. You could use this as a part of your PIVOT query, but you would still need to hand-code all of the dates in the IN clause for the PIVOT.
I am guessing that you want to use a dynamic SQL version of this query so the dates will change based on your needs. The dynamic version will be:
DECLARE #cols AS NVARCHAR(MAX),
#colsNull AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#startdate datetime = '2014-01-01',
#enddate datetime = '2014-12-01'
;with dates (startDate, endDate) as
(
select #startdate, #enddate
from yt
union all
select dateadd(m, 1, startDate), enddate
from dates
where dateadd(m, 1, startDate) <= enddate
)
select #cols = STUFF((SELECT ',' + QUOTENAME(left(datename(month, d.startdate), 3) +'-'
+ cast(year(d.startdate) as char(4)))
from dates d
-- where startdate >= '2014-01-01' and startdate <= '2014-06-01'
group by d.startdate
order by d.startdate
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,''),
#colsNull = STUFF((SELECT ', isnull(' + QUOTENAME(left(datename(month, d.startdate), 3) +'-'
+ cast(year(d.startdate) as char(4)))+', 0) as '+QUOTENAME(left(datename(month, d.startdate), 3) +'-'
+ cast(year(d.startdate) as char(4)))
from dates d
-- where startdate >= '2014-01-01' and startdate <= '2014-06-01'
group by d.startdate
order by d.startdate
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
from dates
set #query = 'SELECT region, ' + #colsNull + ' from
(
select left(datename(month, t.date), 3) +''-''
+ cast(year(t.date) as char(4)) monthYear,
t.region,
t.value
from yt t
) x
pivot
(
sum(value)
for monthyear in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. This version uses the recursive CTE to generate the list of dates that will be used in the dynamic sql string. Even though your table of data might not exist for the months displayed, you will still have a new column.
This gives a result:
| REGION | JAN-2014 | FEB-2014 | MAR-2014 | APR-2014 | MAY-2014 | JUN-2014 | JUL-2014 | AUG-2014 | SEP-2014 | OCT-2014 | NOV-2014 | DEC-2014 |
----------------------------------------------------------------------------------------------------------------------------------------------
| A | 650 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| B | 50 | 120 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| C | 0 | 0 | 0 | 0 | 70 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |