SQL Query to make dynamic columns - sql

Hi I have a query that gets the total count of incidents per month and year
I wanted a result that shows all distinct years in columns
Could you please advise how to make this query dynamic?
Expected Result:
Month 2013 2014 2015
January 8 0 12
February 9 6 10
March 12 1 9
April 10 13 27
May 9 22 15
June 27 4 20
July 15 12 22
August 20 2 2
September 22 5 10
October 10 8 12
November 0 7 0
December 0 15 0
Query
select DATENAME(MONTH,DateOpened) as Month,
sum(case when year(DateOpened) = '2015' then 1 else 0 end) as [2015],
sum(case when year(DateOpened) = '2014' then 1 else 0 end) as [2014]
from Incidents
group by DATENAME(MONTH,DateOpened), MONTH(DateOpened)
order by MONTH(DateOpened)
Thanks for your help!

You can use the PIVOT table operator instead, something like this:
SELECT *
FROM
(
SELECT
DATENAME(MONTH,DateOpened) as Month,
DATENAME(Year,DateOpened) AS Year,
DateOpened
FROM Incidents
) AS t
PIVOT
(
COUNT(DateOpened)
FOR Year IN([2013], [2014], [2015])
) AS p;
SQL Fiddle Demo
If you don't need to write the list of years and do it dynamically for any year, you have to use dynamic SQL to run the query dynamically, like this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct ',' +
QUOTENAME(DATENAME(Year,DateOpened))
from Incidents AS t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = 'SELECT * , '+ #cols + '
FROM
(
select
DATENAME(MONTH,DateOpened) as Month,
DATENAME(Year,DateOpened) AS Year,
DateOpened
FROM Incidents
) AS t
PIVOT
(
COUNT(DateOpened)
FOR Year IN(' + #cols + ')' +
') p';
execute(#query);
SQL Fiddle Demo
This will give you something like this:
| Month | 2014 | 2015 | 2014 | 2015 |
|-----------|------|------|------|------|
| April | 0 | 3 | 0 | 3 |
| August | 1 | 1 | 1 | 1 |
| December | 1 | 0 | 1 | 0 |
| February | 0 | 1 | 0 | 1 |
| July | 1 | 1 | 1 | 1 |
| September | 1 | 0 | 1 | 0 |
Note that: Both the queries won't list any month that has no dates on the original table. If you want to list any month that is not listed on the table with counts 0, you have to modify the anchor query so that it lists all the months even if not listed:
SELECT
m.Name as Month,
i.Year,
i.DateOpened
FROM
(
VALUES ('Janurary'), ('February'), ('March'),
('April'), ('May'), ('June'),
('July'), ('August'), ('September'),
('October'), ('November'), ('December')
) AS m(Name)
LEFT JOIN
(
SELECT
DATENAME(MONTH,DateOpened) as Month,
DATENAME(Year,DateOpened) AS Year,
DateOpened
FROM Incidents
) AS i ON i.Month = m.Name
and replace it in the dynamic query.
Updated SQL Fiddle
This will give you the missing months with zeros results:
| Month | 2014 | 2015 | 2014 | 2015 |
|-----------|------|------|------|------|
| Janurary | 0 | 0 | 0 | 0 | <<
| February | 0 | 1 | 0 | 1 |
| March | 0 | 0 | 0 | 0 | <<
| April | 0 | 3 | 0 | 3 |
| May | 0 | 0 | 0 | 0 | <<
| June | 0 | 0 | 0 | 0 | <<
| July | 1 | 1 | 1 | 1 |
| August | 1 | 1 | 1 | 1 |
| September | 1 | 0 | 1 | 0 |
| October | 0 | 0 | 0 | 0 | <<
| November | 0 | 0 | 0 | 0 | <<
| December | 1 | 0 | 1 | 0 |

A simple create table something like this..
CREATE TABLE #tmpIncidents
(
IncidentName NVARCHAR(50)
, DateOpened DATETIME
)
INSERT INTO #tmpIncidents (IncidentName,DateOpened) VALUES
('Test1',GETDATE()),
('Test1',DATEADD(YEAR,-1,GETDATE())),
('Test1',DATEADD(YEAR,-2,GETDATE())),
('Test1',DATEADD(YEAR,2,GETDATE())),
('Test1',DATEADD(YEAR,1,GETDATE())),
('Test1',DATEADD(YEAR,3,GETDATE()))
Then a build up query for multiple years
DECLARE #columnVar NVARCHAR(4000)
SELECT #columnVar =
(SELECT DISTINCT
'[' + CONVERT(NVARCHAR(150),DATEPART(YEAR,DateOpened)) + '],' AS [text()]
FROM #tmpIncidents
FOR XML PATH('')
)
SET #columnVar = (SELECT LEFT(#columnVar,LEN(#columnVar)-1))
--SELECT #columnVar --so you can see how it looks..
Then execute your query.
EXEC ('
SELECT
pv.*
FROM
(
SELECT DATENAME(MONTH,DateOpened) AS [DateName], DATEPART(YEAR,DateOpened) AS [YEAR], IncidentName FROM #tmpIncidents
) src
PIVOT
(
COUNT(IncidentName)
FOR [YEAR] IN (' + #columnVar + ')
) pv;
')

Related

Sum case from previous month

I couldn't find the answer to this on here or on google.
This is part of the main table
+---+-------+----------------+--------------+
| | Acct | Last_trans_date|Last_transpay |
+---+-------+----------------+--------------+
| 1 | ABC | July 31 | Nov 5 |
| 2 | DEF | Mar 1 | Aug 8 |
| 3 | GFH | Mar 9 | Feb 7 |
+---+------+-----------------+--------------+
I want the total account for the previous month that includes last_trans_date and Last_transpay = previous month as count.
I used this
Select
year(open)
sum(case when month(last_trans_date) = month(current date - 1) and month(last_transpay) = month(current_date - 1) then 1 else 0 end) as activity
from table
group by 1.
I don't think it's outputting the correct amount
SELECT Count(*)
FROM [table]
WHERE
CHARINDEX(#PrevMonth, Last_trans_date) = 1
AND CHARINDEX(#PrevMonth, Last_transpay) = 1

Convert SQL Query to Table Using Pivot Table

Can I know how can I modify the SQL Query below so that the result will turn into expected result using pivot table where () in For MonthYear = Start Date and End Date selected by user ?
SQL Query : SQL Query Result (Image)
Expected Result : Table (Image)
SELECT * FROM
(
SELECT WR.Work_Type,
LEFT(DATENAME(MONTH,WR.Request_Date),3)+' '+STR(YEAR(WR.Request_Date),4) AS MonthYear,
CONVERT(INT,STR(YEAR(WR.Request_Date),4)+REPLACE(STR(MONTH(WR.Request_Date),2),' ','0') ) AS MonthYearOrder,
COUNT(ISNULL(WR.Request_ID,0)) AS Total
FROM tblWork_Request WR
INNER JOIN vWorkTypeByPropertyLevel WT ON WR.CoID=WT.CoID AND WR.Work_Type=WT.Work_Type AND WT.IsWorkRequest=1
LEFT JOIN [UBERIQ_1.5_Property].[dbo].[tblProperty] P ON WR.Property_ID=P.Property_ID
WHERE WR.CoID='59' and WR.Property_ID='MCST001'
GROUP BY WR.Work_Type,LEFT(DATENAME(MONTH,WR.Request_Date),3)+' '+STR(YEAR(WR.Request_Date),4),
CONVERT(INT,STR(YEAR(WR.Request_Date),4)+REPLACE(STR(MONTH(WR.Request_Date),2),' ','0') )
) t
pivot (
SUM(Total)
FOR MonthYear IN ()
) as pivot_table
Result :-
Work_Type | MonthYear | MonthYearOrder | Total
----------------------------------------------------------
A.1 Problem Code 1 | Jun 2020 | 202006 | 3
AHU faulty | Nov 2020 | 202011 | 6
AirCondition | May 2020 | 202005 | 1
Exterior | Jun 2020 | 202006 | 2
Expected Result :-
Work Type | Jan 2020 | Feb 2020 | Mar 2020 | Apr 2020 | May 2020 | Jun 2020 | Jun 2020 | Jul 2020 | Aug 2020 | Sep 2020 | Oct 2020 | Nov 2020 | Dec 2020 |
-----------------------------------------------------------------------------------------------------------------------------------------------------------
A.1 Problem Code 1 | 0 | 0 | 0 | 0 | 0 | 3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
AHU Faulty | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 6 | 0 |
AirCondition | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Exterior | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Please advise. Thank you.
--Variables for dynamic columns and query
DECLARE #columns NVARCHAR(MAX) = ''
DECLARE #ifnullcolumns NVARCHAR(MAX) = ''
DECLARE #query NVARCHAR(MAX)
;WITH months(YearMonth) AS
(
SELECT 202001
UNION ALL
SELECT YearMonth + 1
FROM months
WHERE YearMonth < 202012
)
SELECT
#columns += QUOTENAME(YearMonth) + ',',
#ifnullcolumns += 'ISNULL(' + QUOTENAME(YearMonth) + ', 0) AS ' + QUOTENAME(YearMonth)+','
FROM
months
-- remove last , from the column list
SET #columns = LEFT(#columns, LEN(#columns) - 1)
SET #ifnullcolumns = LEFT(#ifnullcolumns, LEN(#ifnullcolumns) - 1)
-- Building dynamic query with pivot
SET #query ='
SELECT Work_Type, '+ #ifnullcolumns +' FROM (
select Work_Type, MonthYerOrder, Total
from Sample) t
PIVOT(
SUM(Total)
FOR MonthYerOrder IN ('+ #columns +')
) AS pivot_table;'
EXECUTE sp_executesql #query
Result of this query: http://sqlfiddle.com/#!18/96333/63

SQL Pivot with Month and Year

I'm trying to sort some shipment data using a SQL Pivot but i can not figure it out.
I've the data sorted in this way (one row with the total items shipped for a family for each month of each year starting from 2015 to ):
TABLE A
Year | Month | ItemFamilyCode | TotalShipped
2018 | 9 | FA01 | 5
2018 | 9 | FA04 | 4
2018 | 10 | FA01 | 2
2018 | 11 | FA02 | 1
2018 | 12 | FA03 | 3
2019 | 1 | FA04 | 7
and so on. I want to achieve the following result:
ItemFamilyCode | 2018-9 | 2018-10 | 2018-11 | 2018-12 | 2019-1 | [..]
FA01 | 5 | 2 | 0 | 0 | 0 |
FA02 | 0 | 0 | 1 | 0 | 0 |
FA03 | 0 | 0 | 0 | 3 | 0 |
FA04 | 4 | 0 | 1 | 0 | 7 |
and so on ... the family code in order and all the values for each month of each year, from the older month/year to now. Is it possible? Thanks to anyone who can help.
If you want to use it as view :
SELECT * FROM
(
SELECT
Concat([Year],'-', [Month]) as [Date],
ItemFamilyCode,
TotalShipped
FROM Shipping -- Or any Table Name
) t
PIVOT(
Sum(TotalShipped)
FOR [Date] IN (
[2018-9],
[2018-10],
[2018-11],
[2018-12],
[2019-1],
[2019-2] -- You have to type all months until today
)
) AS pivot_table;
And, dynamic sql if you can use it in stored procedure :
Make a table with the content of date list to generate date list string
DECLARE
#columns NVARCHAR(MAX) = '',
#sql NVARCHAR(MAX) = '';
-- select the category names
SELECT
#columns+=QUOTENAME(Date) + ','
FROM
DateList
ORDER BY
DateList;
-- remove the last comma
SET #columns = LEFT(#columns, LEN(#columns) - 1);
-- construct dynamic SQL
SET #sql ='
SELECT * FROM
(
SELECT
Concat([Year],'-', [Month]) as [Date],
ItemFamilyCode,
TotalShipped
FROM Shipping -- Or any Table Name
) t
PIVOT(
Sum(TotalShipped)
FOR [Date] IN ('+ #columns +')
) AS pivot_table;';
-- execute the dynamic SQL
EXECUTE sp_executesql #sql;
Source : sqlservertutorial

Dynamically month and year pivot with bad sorting

From the answer from here I build a solution that is good for me but I have still one problem.
I had table:
ID | Year | Month | Multiply | Future | Current
123 | 2017 | 1 | 1.0 | 25 | null
123 | 2017 | 2 | 1.0 | 19 | 15
123 | 2017 | 3 | 1.0 | 13 | 0
123 | 2017 | 4 | 1.0 | 22 | 14
123 | 2017 | 5 | 1.0 | 13 | null
... | .... | ... | ... | .. | ..
123 | 2018 | 1 | 1.0 | 25 | 10
123 | 2018 | 2 | 1.0 | 25 | 10
... | .... | ... | ... | .. | ..
124 | 2017 | 1 | 1 | 10 | 5
124 | 2017 | 2 | 1 | 15 | 2
... | .... | ... | ... | .. | ..
124 | 2018 | 1 | 1 | 20 | 0
I build this view to concatenate Year + Month and make IF statement:
value in the new Value column I'm getting from Future and Current column - when the Current value is null get the Future value and multiply by Multiply, else get Current value and multiply by Multiply (even 0). Next to it I need to add a 'F' prefix when the value is got from Future column.
ID | Date | Value |
123 | 2017 - 1 | F25 |
123 | 2017 - 2 | 15 |
123 | 2017 - 3 | 0 |
.. | .. | .. |
Code for it:
SELECT ID = ID,
[Date] = [Date],
[Value] = [Value]
FROM ( SELECT ID,
cast([Year] as varchar(30)) + ' - ' + cast([Month]as varchar(30)) as [Date],
[Multiply],
case when [Current] IS NULL /*OR [Current] = 0*/
then 'F' + CAST([Future] * [Multiply] as varchar(30))
else CAST([Current] * [Multiply] as varchar(30))
end as Value
FROM dbo.CurrentFuture
) AS t
And from this I make this view via dynamically pivot.
ID | 2017 - 1 | 2017 - 10 | 2017 - 11 | 2017 - 12 | 2017 - 2 | ... | 2018 - 1 | ...
123 | F25 | .. | .. | .. | 15 | ... | 10 | ...
124 | 5 | 2 | .. | .. | .. | ... | 0 | ...
Code for it:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME([Date])
from dbo.UpperView
group by [Date]
order by [Date]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [ID],' + #cols + ' from
(
select [ID], [Date],[Value]
from [dbo].[UpperView]
) x
pivot
(
max([Value])
for [Date] in (' + #cols + ')
) p '
execute(#query);
As you can see columns in the new view are not sorting in a good way.. instead of 2017 - 1, 2017 - 2, 2017 - 3 I have 2017 - 1, 2017 - 10, 2017 - 11, 2017 - 12, 2017 - 2. Can you help me how to sort it properly?
From the limited information, What you want is the ordering of the column based on the Concatenated string of Year+ Month.
What you need is to prefix the month with "0" for January - September and no prefix for October-December.
so in effect you will achieve this.
ID | 2017 - 01 | 2017 - 02 | 2017 - 03 | ..... | 2017 - 09 |2018 - 10 |2018 - 11||2018 - 12|
SELECT ID = ID,
[Date] = [Date],
[Value] = [Value]
FROM
(
SELECT ID,
CAST([Year] AS VARCHAR(30))+' - '+RIGHT('0'+CAST([Month] AS VARCHAR(30)), 2) AS [Date],
[Multiply],
CASE
WHEN [Current] IS NULL
/*OR [Current] = 0*/
THEN 'F'+CAST([Future] * [Multiply] AS VARCHAR(30))
ELSE CAST([Current] * [Multiply] AS VARCHAR(30))
END AS Value
FROM dbo.CurrentFuture
) AS t;
Add new column to UpperView for sorting like this
cast([Year] as varchar(30)) + RIGHT('0' + cast([Month] as varchar(30)), 2) as [DateOrder]
and use this column for sorting at your column query instead of [Date]
select #cols = STUFF((SELECT ',' + QUOTENAME([Date])
from dbo.UpperView
group by [Date], [DateOrder]
order by [DateOrder]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')

Get list of counts by date

I have two tables. One containing a list of applications. And another one containing counts associated to them every week. Now I want to get as a result the app name and the count for this week and the previous. Let me explain this.
app:
+----+-------------+
| id | name |
+----+-------------+
| 1 | Office 2007 |
+----+-------------+
| 2 | Office 2010 |
+----+-------------+
| 3 | Office 2013 |
+----+-------------+
count:
+----+--------+-------+------------+
| id | app_id | count | date |
+----+--------+-------+------------+
| 1 | 1 | 200 | 2016-01-11 |
+----+--------+-------+------------+
| 2 | 2 | 500 | 2016-01-11 |
+----+--------+-------+------------+
| 3 | 3 | 750 | 2016-01-11 |
+----+--------+-------+------------+
| 4 | 1 | 180 | 2016-01-18 |
+----+--------+-------+------------+
| 5 | 2 | 378 | 2016-01-18 |
+----+--------+-------+------------+
| 6 | 3 | 1000 | 2016-01-18 |
+----+--------+-------+------------+
And this is the result I need. I need all the applications with the count of this week and the previous:
+-------------+-----------------+-----------------+
| app | count_this_week | count_prev_week |
+-------------+-----------------+-----------------+
| Office 2007 | 180 | 200 |
+-------------+-----------------+-----------------+
| Office 2010 | 378 | 500 |
+-------------+-----------------+-----------------+
| Office 2013 | 1000 | 750 |
+-------------+-----------------+-----------------+
A script runs every week which fills the count table. And now I need to get a report also on a weekly basis.
Honestly I'm a bit lost as I don't know how to declare the conditions for the columns.
You can try to group first by DATEPART(WEEK,C.date),name and then split the counts into 2 columns using another GROUP BY. Something like this
EDIT
If there are exactly 1 record per week per app, you can do with just one group by like this.
SELECT
appname,
SUM(CASE WHEN weekno = 0 THEN sumcount ELSE 0 END) as thisweek,
SUM(CASE WHEN weekno = 1 THEN sumcount ELSE 0 END) as lastweek
FROM
(
SELECT
DATEPART(WEEK,CURRENT_TIMESTAMP) - DATEPART(WEEK,C.date) as weekno,
name as appname,
count as sumcount
FROM App A
INNER JOIN CountTable C ON A.[id] = C.[app_id]
WHERE DATEPART(WEEK,C.date) BETWEEN DATEPART(WEEK,CURRENT_TIMESTAMP) - 1 AND DATEPART(WEEK,CURRENT_TIMESTAMP)
)T
GROUP BY appname
Query
SELECT
appname,
SUM(CASE WHEN weekno = 0 THEN sumcount ELSE 0 END) as thisweek,
SUM(CASE WHEN weekno = 1 THEN sumcount ELSE 0 END) as lastweek
FROM
(
SELECT
DATEPART(WEEK,CURRENT_TIMESTAMP) - DATEPART(WEEK,C.date) as weekno,
name as appname,
SUM(count) as sumcount
FROM App A INNER JOIN CountTable C ON A.[id] = C.[app_id]
WHERE DATEPART(WEEK,C.date) BETWEEN DATEPART(WEEK,CURRENT_TIMESTAMP) - 1 AND DATEPART(WEEK,CURRENT_TIMESTAMP)
GROUP BY DATEPART(WEEK,C.date),name
) AS T
GROUP BY appname
SQL Fiddle
Output
| appname | thisweek | lastweek |
|-------------|----------|----------|
| Office 2007 | 180 | 200 |
| Office 2010 | 378 | 500 |
| Office 2013 | 1000 | 750 |
You can use this generic query with a variable for the current week day:
DECLARE #week date = '2016-01-18';
WITH data AS (
SELECT a.name, c.[count]
, w = CASE WHEN c.[date] = #week THEN 0 ELSE 1 END
FROM #Counts c
INNER JOIN #Apps a ON c.app_id = a.id
WHERE [date] = #week OR [date] = DATEADD(day, -7, #week)
)
SELECT App = name, count_this_week = [0], count_prev_week = [1]
FROM data d
PIVOT (
MAX([count])
FOR w IN ([0], [1])
) p
Output:
App count_this_week count_prev_week
Office 2007 180 200
Office 2010 378 500
Office 2013 1000 750
Your data:
DECLARE #Apps TABLE ([id] int, [name] varchar(11));
DECLARE #Counts TABLE([id] int, [app_id] int, [count] int, [date] date);
INSERT INTO #Apps([id], [name])
VALUES
(1, 'Office 2007'),
(2, 'Office 2010'),
(3, 'Office 2013')
;
INSERT INTO #Counts([id], [app_id], [count], [date])
VALUES
(1, 1, 200, '2016-01-11'),
(2, 2, 500, '2016-01-11'),
(3, 3, 750, '2016-01-11'),
(4, 1, 180, '2016-01-18'),
(5, 2, 378, '2016-01-18'),
(6, 3, 1000, '2016-01-18')
;
SELECT *
FROM count
JOIN app ON app.id=count.app_id
WHERE date BETWEEN '2016-01-18' AND '2016-01-11'