SQL max concurrent sessions per hour of day - sql

I work in a healthcare call-centre environment. I have access to our core system's database and I want to use the data in it to determine the number of staff logged on each hour of the data in a given period that may expand over 24 hours (e.g. 24/12/2014 - 26/12/2014).
There is a userlog table that records when users log on and log off.
userlog table example
UserRef Date LogType SessionID
--------------------------------------- ----------------------- ------ -----------------------
96AD647C-D061-43F5-9F8D-FA6C74817E07 2002-10-17 14:11:25.763 LOGON 8D451569-0260-46BB-9B9E-F49B3E778161
96AD647C-D061-43F5-9F8D-FA6C74817E07 2002-10-17 18:11:32.547 LOGOFF 8D451569-0260-46BB-9B9E-F49B3E778161
60738820-5F72-4E20-A070-57E07C83B6DE 2002-10-17 14:53:31.153 LOGON C773894C-8B2D-4054-A550-3F04B4C5669F
60738820-5F72-4E20-A070-57E07C83B6DE 2002-10-17 22:55:25.607 LOGOFF C773894C-8B2D-4054-A550-3F04B4C5669F
90A55FDD-967E-4D99-96DF-96840CDB2CDF 2002-10-17 15:26:40.123 LOGON 1CE5F5A5-4E20-4D4A-BB67-EB0CB33976D7
96AD647C-D061-43F5-9F8D-FA6C74817E07 2002-10-17 15:51:28.590 LOGON 7EFDEE1C-15CF-4DE1-B59F-7AFC49B4BE73
90A55FDD-967E-4D99-96DF-96840CDB2CDF 2002-10-17 15:58:05.217 LOGOFF 1CE5F5A5-4E20-4D4A-BB67-EB0CB33976D7
96AD647C-D061-43F5-9F8D-FA6C74817E07 2002-10-17 15:58:31.013 LOGOFF 7EFDEE1C-15CF-4DE1-B59F-7AFC49B4BE73
90A55FDD-967E-4D99-96DF-96840CDB2CDF 2002-10-17 15:58:32.733 LOGON 03F56AB8-FED5-4CC7-8445-26BF55F58E60
90A55FDD-967E-4D99-96DF-96840CDB2CDF 2002-10-17 16:13:02.827 LOGOFF 03F56AB8-FED5-4CC7-8445-26BF55F58E60
Desired results (this is not intended to reflect the above sample data):
Date Hour Number of users logged in
---- ---- -------------------------
01/12/2014 0 0
01/12/2014 1 0
01/12/2014 2 0
01/12/2014 3 0
01/12/2014 4 0
01/12/2014 5 1
01/12/2014 6 1
01/12/2014 7 1
01/12/2014 8 3
01/12/2014 9 7
01/12/2014 10 7
...
01/12/2014 23 0
To be clear: I'm trying to get the hour to still display with a 0 count when there were no users logged in.
I guess what I'm looking for is the maximum concurrent sessions for each hour of the day, but I'm not overly technical or skilled in SQL (getting better bit by bit, though!) so I hope that terminology doesn't confuse things!
I've googled for this and found a few similar scenarios, but for Oracle and MySQL, or where the log table records the logon data differently. I'm sure I'll get to a point where I can successfully 'translate' other database query code to MS SQL, but I'm not there yet!
I am using: Microsoft SQL Server 2005 - 9.00.1399.06 (Intel X86).
Thanks in advance for your help.

UPDATED (v # 4):
I've added date filter at the end of the query - WHERE A.[Date] Between #X and #Y - it's not most efficient way in this case but simplest I think and less error prone for a start :
SELECT
A.[Date],
A.[Hour],
SUM(CASE WHEN (B.[SessionID] IS NULL) THEN 0 ELSE 1 END) AS [Number_of_Sessions_Per_Hour]
FROM
(
SELECT DISTINCT
CONVERT(DATETIME,
LTRIM(RTRIM(CONVERT(NVARCHAR(10), YEAR(userlog.[Date]))))
+ '-' + RIGHT('0' + LTRIM(RTRIM(CONVERT(NVARCHAR(10), MONTH(userlog.[Date])))), 2)
+ '-' + RIGHT('0' + LTRIM(RTRIM(CONVERT(NVARCHAR(10), DAY(userlog.[Date])))), 2)
, 120) AS [Date],
hours_table.[Hour]
FROM
userlog,
(
SELECT 1 AS [Hour] UNION ALL SELECT 3 AS [Hour] UNION ALL SELECT 4 AS [Hour] UNION ALL SELECT 5 AS [Hour] UNION ALL SELECT 6 AS [Hour]
UNION ALL SELECT 7 AS [Hour] UNION ALL SELECT 7 AS [Hour] UNION ALL SELECT 8 AS [Hour] UNION ALL SELECT 9 AS [Hour] UNION ALL SELECT 10 AS [Hour]
UNION ALL SELECT 11 AS [Hour] UNION ALL SELECT 12 AS [Hour] UNION ALL SELECT 13 AS [Hour] UNION ALL SELECT 14 AS [Hour] UNION ALL SELECT 15 AS [Hour]
UNION ALL SELECT 16 AS [Hour] UNION ALL SELECT 17 AS [Hour] UNION ALL SELECT 18 AS [Hour] UNION ALL SELECT 19 AS [Hour] UNION ALL SELECT 20 AS [Hour]
UNION ALL SELECT 21 AS [Hour] UNION ALL SELECT 22 AS [Hour] UNION ALL SELECT 23 AS [Hour] UNION ALL SELECT 24 AS [Hour]
) as hours_table
) AS A
LEFT OUTER JOIN
(
SELECT
userlog.SessionID,
MAX(CASE WHEN userlog.LogType = 'LOGON'
THEN CONVERT(DATETIME,
LTRIM(RTRIM(CONVERT(NVARCHAR(10), YEAR(userlog.[Date]))))
+ '-' + RIGHT('0' + LTRIM(RTRIM(CONVERT(NVARCHAR(10), MONTH(userlog.[Date])))), 2)
+ '-' + RIGHT('0' + LTRIM(RTRIM(CONVERT(NVARCHAR(10), DAY(userlog.[Date])))), 2)
, 120)
ELSE CONVERT(DATETIME, '1900-01-01', 120)
END) AS [Date_Session_START],
MAX(CASE WHEN userlog.LogType = 'LOGOFF'
THEN CONVERT(DATETIME,
LTRIM(RTRIM(CONVERT(NVARCHAR(10), YEAR(userlog.[Date]))))
+ '-' + RIGHT('0' + LTRIM(RTRIM(CONVERT(NVARCHAR(10), MONTH(userlog.[Date])))), 2)
+ '-' + RIGHT('0' + LTRIM(RTRIM(CONVERT(NVARCHAR(10), DAY(userlog.[Date])))), 2)
, 120)
ELSE CONVERT(DATETIME, '1900-01-01', 120)
END) AS [Date_Session_END],
MAX(CASE WHEN userlog.LogType = 'LOGON' THEN DATEPART(HOUR, userlog.[Date]) ELSE 0 END) AS [Hour_Session_START],
MAX(CASE WHEN userlog.LogType = 'LOGOFF' THEN DATEPART(HOUR, userlog.[Date]) ELSE 0 END) AS [Hour_Session_END],
FROM
userlog
GROUP BY
userlog.SessionID
) AS B
ON (A.[Date] >= B.[Date_Session_START] AND A.[Date] <= B.[Date_Session_END])
AND (A.[Hour] >= B.[Hour_Session_START] AND A.[Hour] <= B.[Hour_Session_END])
WHERE
A.[Date] Between #X and #Y
GROUP BY
A.[Date],
A.[Hour]

OK, taken a slightly different approach. I build a table of Logon / Log Off - which could be in a cte but Im not an oracle expert so not sure if its supported so gone for simpler SQL and a temp table. I have all built a table of static integers (also known as tally table) for 0-30 to handle a test date range in my query based on your answer above - if you have one already you could just use that ranged down and save some effort. Running the query returns a table like this (using your sample data also below)
StartDate WorkingHour LoggedInUsers
2002-10-17 00:00:00.000 10 0
2002-10-17 00:00:00.000 11 0
2002-10-17 00:00:00.000 12 0
2002-10-17 00:00:00.000 13 0
2002-10-17 00:00:00.000 14 2
2002-10-17 00:00:00.000 15 5
2002-10-17 00:00:00.000 16 3
2002-10-17 00:00:00.000 17 2
2002-10-17 00:00:00.000 18 2
2002-10-17 00:00:00.000 19 1
2002-10-17 00:00:00.000 20 1
2002-10-17 00:00:00.000 21 1
2002-10-17 00:00:00.000 22 1
2002-10-17 00:00:00.000 23 0
Code as Run in sql
DECLARE #StartDate DATETIME ,
#NoDays INT ;
select
#StartDate = '2002-10-01',
#NoDays = 20;
DECLARE #Sessions TABLE (
UserRef UNIQUEIDENTIFIER,
DATE DATETIME,
LogType VARCHAR(100),
SessionID UNIQUEIDENTIFIER
);
INSERT INTO #Sessions
SELECT '96AD647C-D061-43F5-9F8D-FA6C74817E07', '2002-10-17 14:11:25.763', 'LOGON', '8D451569-0260-46BB-9B9E-F49B3E778161'
UNION SELECT '96AD647C-D061-43F5-9F8D-FA6C74817E07', '2002-10-17 18:11:32.547', 'LOGOFF', '8D451569-0260-46BB-9B9E-F49B3E778161'
UNION SELECT '60738820-5F72-4E20-A070-57E07C83B6DE', '2002-10-17 14:53:31.153', 'LOGON', 'C773894C-8B2D-4054-A550-3F04B4C5669F'
UNION SELECT '60738820-5F72-4E20-A070-57E07C83B6DE', '2002-10-17 22:55:25.607', 'LOGOFF', 'C773894C-8B2D-4054-A550-3F04B4C5669F'
UNION SELECT '90A55FDD-967E-4D99-96DF-96840CDB2CDF', '2002-10-17 15:26:40.123', 'LOGON', '1CE5F5A5-4E20-4D4A-BB67-EB0CB33976D7'
UNION SELECT '96AD647C-D061-43F5-9F8D-FA6C74817E07', '2002-10-17 15:51:28.590', 'LOGON', '7EFDEE1C-15CF-4DE1-B59F-7AFC49B4BE73'
UNION SELECT '90A55FDD-967E-4D99-96DF-96840CDB2CDF', '2002-10-17 15:58:05.217', 'LOGOFF', '1CE5F5A5-4E20-4D4A-BB67-EB0CB33976D7'
UNION SELECT '96AD647C-D061-43F5-9F8D-FA6C74817E07', '2002-10-17 15:58:31.013', 'LOGOFF', '7EFDEE1C-15CF-4DE1-B59F-7AFC49B4BE73'
UNION SELECT '90A55FDD-967E-4D99-96DF-96840CDB2CDF', '2002-10-17 15:58:32.733', 'LOGON', '03F56AB8-FED5-4CC7-8445-26BF55F58E60'
UNION SELECT '90A55FDD-967E-4D99-96DF-96840CDB2CDF', '2002-10-17 16:13:02.827', 'LOGOFF', '03F56AB8-FED5-4CC7-8445-26BF55F58E60';
DECLARE #staticintegers TABLE (myInteger INT);
INSERT INTO #staticintegers
SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3
UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8
UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12 UNION SELECT 13
UNION SELECT 14 UNION SELECT 15 UNION SELECT 16 UNION SELECT 17 UNION SELECT 18
UNION SELECT 19 UNION SELECT 20 UNION SELECT 21 UNION SELECT 22 UNION SELECT 23
UNION SELECT 24 UNION SELECT 25 UNION SELECT 26 UNION SELECT 27 UNION SELECT 28
UNION SELECT 29 UNION SELECT 30 UNION SELECT 31 UNION SELECT 32 UNION SELECT 33;
DECLARE #sessionsOutput TABLE (SessionID UNIQUEIDENTIFIER, StartTime DATETIME, EndTime DATETIME);
INSERT INTO #sessionsOutput
(SessionID, StartTime)
SELECT
SessionID,
[date]
FROM
#Sessions
WHERE logtype = 'Logon';
UPDATE #sessionsOutput
SET EndTime = [date]
FROM #sessionsOutput aa
INNER JOIN #Sessions bb
ON aa.SessionID = bb.SessionID
WHERE bb.LogType = 'Logoff';
SELECT
DATEADD(dd, DateIntegers.myInteger, #StartDate) AS StartDate
,hoursintegers.myINteger AS WorkingHour
,COUNT(aa.SessionID) AS LoggedInUsers
FROM
#staticintegers DateIntegers
LEFT OUTER JOIN #StaticIntegers HoursIntegers
ON HoursIntegers.myInteger BETWEEN 0 AND 23
LEFT OUTER JOIN #sessionsOutput aa
ON
HoursIntegers.myInteger BETWEEN DATEPART(hh, aa.StartTime) AND DATEPART(hh, aa.endtime)
AND CAST(aa.StartTime AS DATE) = DATEADD(dd, dateintegers.myInteger, #StartDate)
GROUP BY
DATEADD(dd, DateIntegers.myInteger, #StartDate),
HoursIntegers.myInteger
ORDER BY
DATEADD(dd, DateIntegers.myInteger, #StartDate),
HoursIntegers.myInteger;

Related

Fill up date gap by month

I have table of products and their sales quantity in months.
Product Month Qty
A 2018-01-01 5
A 2018-02-01 3
A 2018-05-01 5
B 2018-08-01 10
B 2018-10-01 12
...
I'd like to first fill in the data gap between each product's min and max dates like below:
Product Month Qty
A 2018-01-01 5
A 2018-02-01 3
A 2018-03-01 0
A 2018-04-01 0
A 2018-05-01 5
B 2018-08-01 10
B 2018-09-01 0
B 2018-10-01 12
...
Then I would need to perform an accumulation of each product's sales quantity by month.
Product Month total_Qty
A 2018-01-01 5
A 2018-02-01 8
A 2018-03-01 8
A 2018-04-01 8
A 2018-05-01 13
B 2018-08-01 10
B 2018-09-01 10
B 2018-10-01 22
...
I fumbled over the "cross join" clause, however it seems to generate some unexpected results for me. Could someone help to give a hint how I can achieve this in SQL?
Thanks a lot in advance.
I think a recursive CTE is a simple way to do this. The code is just:
with cte as (
select product, min(mon) as mon, max(mon) as end_mon
from t
group by product
union all
select product, dateadd(month, 1, mon), end_mon
from cte
where mon < end_mon
)
select cte.product, cte.mon, coalesce(qty, 0) as qty
from cte left join
t
on t.product = cte.product and t.mon = cte.mon;
Here is a db<>fiddle.
Hi i think this example can help you and perform what you excepted :
CREATE TABLE #MyTable
(Product varchar(10),
ProductMonth DATETIME,
Qty int
);
GO
CREATE TABLE #MyTableTempDate
(
FullMonth DATETIME
);
GO
INSERT INTO #MyTable
SELECT 'A', '2019-01-01', 214
UNION
SELECT 'A', '2019-02-01', 4
UNION
SELECT 'A', '2019-03-01', 50
UNION
SELECT 'B', '2019-01-01', 214
UNION
SELECT 'B', '2019-02-01', 10
UNION
SELECT 'C', '2019-04-01', 150
INSERT INTO #MyTableTempDate
SELECT '2019-01-01'
UNION
SELECT '2019-02-01'
UNION
SELECT '2019-03-01'
UNION
SELECT '2019-04-01'
UNION
SELECT '2019-05-01'
UNION
SELECT '2019-06-01'
UNION
SELECT '2019-07-01';
------------- FOR NEWER SQL SERVER VERSION > 2005
WITH MyCTE AS
(
SELECT T.Product, T.ProductMonth AS 'MMonth', T.Qty
FROM #MyTable T
UNION
SELECT T.Product, TD.FullMonth AS 'MMonth', 0 AS 'Qty'
FROM #MyTable T, #MyTableTempDate TD
WHERE NOT EXISTS (SELECT 1 FROM #MyTable TT WHERE TT.Product = T.Product AND TD.FullMonth = TT.ProductMonth)
)
-- SELECT * FROM MyCTE;
SELECT Product, MMonth, Qty, SUM( Qty) OVER(PARTITION BY Product ORDER BY Product
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as 'TotalQty'
FROM MyCTE
ORDER BY Product, MMonth ASC;
DROP TABLE #MyTable
DROP TABLE #MyTableTempDate
I have other way to perform this in lower SQL Server Version (like 2005 and lower)
It's a SELECT on SELECT if it's your case let me know and i provide some other example.
You can create the months with a recursive CTE
DECLARE #MyTable TABLE
(
ProductID CHAR(1),
Date DATE,
Amount INT
)
INSERT INTO #MyTable
VALUES
('A','2018-01-01', 5),
('A','2018-02-01', 3),
('A','2018-05-01', 5),
('B','2018-08-01', 10),
('B','2018-10-01', 12)
DECLARE #StartDate DATE
DECLARE #EndDate DATE
SELECT #StartDate = MIN(Date), #EndDate = MAX(Date) FROM #MyTable
;WITH dates AS (
SELECT #StartDate AS Date
UNION ALL
SELECT DATEADD(Month, 1, Date)
FROM dates
WHERE Date < #EndDate
)
SELECT A.ProductID, d.Date, COALESCE(Amount,0) AS Amount, COALESCE(SUM(Amount) OVER(PARTITION BY A.ProductID ORDER BY A.ProductID, d.Date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),0) AS Total
FROM
(
SELECT ProductID, MIN(date) as DateStart, MAX(date) as DateEnd
FROM #MyTable
GROUP BY ProductID -- As I read in your comments that you need different min and max dates per product
) A
JOIN dates d ON d.Date >= A.DateStart AND d.Date <= A.DateEnd
LEFT JOIN #MyTable T ON A.ProductID = T.ProductID AND T.Date = d.Date
ORDER BY A.ProductID, d.Date
Try this below
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
;WITH CTE(Product,[Month],Qty)
AS
(
SELECT 'A','2018-01-01', 5 UNION ALL
SELECT 'A','2018-02-01', 3 UNION ALL
SELECT 'A','2018-05-01', 5 UNION ALL
SELECT 'B','2018-08-01', 10 UNION ALL
SELECT 'D','2018-10-01', 12
)
SELECT ct.Product,[MonthDays],ct.Qty
INTO #Temp
FROM
(
SELECT c.Product,[Month],
ISNULL(Qty,0) AS Qty
FROM CTE c
)ct
RIGHT JOIN
(
SELECT -- This code is to get month data
CONVERT(VARCHAR(10),'2018-'+ RIGHT('00'+CAST(MONTH(DATEADD(MM, s.number, CONVERT(DATETIME, 0)))AS VARCHAR),2) +'-01',120) AS [MonthDays]
FROM master.dbo.spt_values s
WHERE [type] = 'P' AND s.number BETWEEN 0 AND 11
)DT
ON dt.[MonthDays] = ct.[Month]
SELECT
MAX(Product)OVER(ORDER BY [MonthDays])AS Product,
[MonthDays],
ISNULL(Qty,0) Qty,
SUM(ISNULL(Qty,0))OVER(ORDER BY [MonthDays]) As SumQty
FROM #Temp
Result
Product MonthDays Qty SumQty
------------------------------
A 2018-01-01 5 5
A 2018-02-01 3 8
A 2018-03-01 0 8
A 2018-04-01 0 8
A 2018-05-01 5 13
A 2018-06-01 0 13
A 2018-07-01 0 13
B 2018-08-01 10 23
B 2018-09-01 0 23
D 2018-10-01 12 35
D 2018-11-01 0 35
D 2018-12-01 0 35
First of all, i would divide month and year to get easier with statistics.
I will give you an example query, not based on your table but still helpful.
--here i create the table that will be used as calendar
Create Table MA_MonthYears (
Month int not null ,
year int not null
PRIMARY KEY ( month, year) )
--/////////////////
-- here i'm creating a procedure to fill the ma_monthyears table
declare #month as int
declare #year as int
set #month = 1
set #year = 2015
while ( #year != 2099 )
begin
insert into MA_MonthYears(Month, year)
select #month, #year
if #month < 12
set #month=#month+1
else
set #month=1
if #month = 1
set #year = #year + 1
end
--/////////////////
--here you are the possible result you are looking for
select SUM(Ma_saledocdetail.taxableamount) as Sold, MA_MonthYears.month , MA_MonthYears.year , item
from MA_MonthYears left outer join MA_SaleDocDetail on year(MA_SaleDocDetail.DocumentDate) = MA_MonthYears.year
and Month(ma_saledocdetail.documentdate) = MA_MonthYears.Month
group by MA_SaleDocDetail.Item, MA_MonthYears.year , MA_MonthYears.month
order by MA_MonthYears.year , MA_MonthYears.month

How to get the date for the previous month which are closed in the current month

Iam using SSRS Report builder application to create BI Report for my System which is tracking the numbers of incidents logged and closed based on each month.
the below is the table which i need to create the query
Month Logged Received Closed Remaining
January 200 220 150 70
February 150 220 200 20
March 110 130 100 30
April 200 230 200 30
and each column define as follow:
Logged= Open Incident in the Current Month for example open from 1/1/2014 to 31/1/2014 (Contain only the current month data )
Received = Logged incident+ the remaining from the previous months which are still open not close for example the month febreuary will be 150 for the current moth+70 from previous month remaining will give me total 220 which is received.
Closed= incident which are opened in the current month and closed in the current month + the remaining from the previous month which closed in this month
Remaining= Received – closed
the code which i used is not giving me the close incident for the previous months also its only giving me which were closed in the current month
the below is the code which i used for my query:
SELECT group_id, YEAR(Opendate) AS Year, MONTH(Opendate) AS Month,
COUNT(CASE WHEN Month(Closedate) = Month(Opendate)
AND Month(closedate)> Month (opendate) THEN 1 ELSE NULL END) AS closed,
COUNT(*) AS Logged,
FROM Incidents
WHERE (Opendate >= #YearStart) AND (Opendate <= #YearEnd)
GROUP BY YEAR(Opendate), MONTH(Opendate), group_id
ORDER BY Year, Month,group_id
Logged is working fine the closed, Received and remaining i am stuck on it.
I tried to use Union and got the Logged and Closed Data
Select count(*) logged,year(opendate) as year1,MONTH(opendate) as
month1,'Logged' as status1
From Incidents
where opendate is not null
GROUP BY year(opendate),MONTH(opendate)
UNION
Select count(*) closed,year(Closedate) as year1,MONTH(Closedate) as
month1,'All_Closed' as status1
From Incidents
where Closedate is not null
GROUP BY year(Closedate),MONTH(Closedate)
UNION
Select count(*) Remaining,year(opendate) as year1,MONTH(opendate) as
month1,'Current_Month_Not_Closed' as status1
From Incidents
where Month(Closedate) > MONTH(Opendate)
GROUP BY year(opendate),MONTH(opendate)
UNION
Select count(*) Month_Closed,year(opendate) as year1,MONTH(opendate) as
month1,'Current_Month_Close' as status1
From Incidents
where MONTH(Closedate) = MONTH(Opendate)
GROUP BY year(opendate),MONTH(opendate)
order by year1,month1
the data which I received are as follow:
logged | year1 | month1 | status1
-------+-------+--------+-------------------------
1093 | 2014 | 1 | Logged
1089 | 2014 | 1 | All_Closed
997 | 2014 | 1 | Current_Month_Close
96 | 2014 | 1 | Current_Month_Not_Closed
1176 | 2014 | 2 | Logged
1176 | 2014 | 2 | All_Closed
91 | 2014 | 2 | Current_Month_Not_Closed
1085 | 2014 | 2 | Current_Month_Close
1340 | 2014 | 3 | Logged
1327 | 2014 | 3 | All_Closed
107 | 2014 | 3 | Current_Month_Not_Closed
1232 | 2014 | 3 | Current_Month_Close
116 | 2014 | 4 | Current_Month_Not_Closed
1320 | 2014 | 4 | Current_Month_Close
1424 | 2014 | 4 | All_Closed
1441 | 2014 | 4 | Logged
1167 | 2014 | 5 | Current_Month_Close
105 | 2014 | 5 | Current_Month_Not_Closed
1277 | 2014 | 5 | Logged
1283 | 2014 | 5 | All_Closed
To have a reliable data a calendar table as anchor can help, and is needed in case the tickets can be alive for months from their opening date or there can be a month without ticket created.
For example with the fake data
CREATE TABLE Incidents (
id int identity(1, 1)
, group_id nvarchar(100)
, Opendate Datetime
, Closedate Datetime
)
INSERT INTO Incidents
VALUES ('Service Desk', '20140107', '20140120')
, ('Service Desk', '20140117', '20140123')
, ('Service Desk', '20140127', '20140313')
, ('Service Desk', '20140310', '')
-- from an OP comment the open tickets have the Closedate '' (1900-01-01)
without a calendar table (or a temp, or a CTE) there is no way to add february in the resultset, even if the third record is both "Received" and "Remaining" in that month.
To create a calendar there are several way, in this case we need the some information about months but nothing about the days, so those are not generated.
declare #YearStart date = '20140101'
declare #YearEnd date = '20140430'
;WITH D(N) AS (
SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
UNION ALL SELECT 8 UNION ALL SELECT 9
)
SELECT EOM
= DATEADD(D, -1, DATEADD(M, u.N + 10 * t.N + 1
, DATEADD(Y, DATEDIFF(Y, 0, #YearStart), 0)))
, pMonth = u.N + 10 * t.N
FROM D u
CROSS JOIN D t
WHERE u.N + 10 * t.N <= DATEDIFF(M, #YearStart, #YearEnd)
Here EOM is the date of the end of the month, it'll be used to check if the incidents are closed in the month and pMonth is the progressive month starting from #YearStart.
Now we need to prepare the data in the incident table to be used
SELECT ID
, OpenDate
, Closedate = COALESCE(NULLIF(Closedate, ''), '99991231')
, pOpenDate = DATEDIFF(M, #YearStart, OpenDate)
, pClosedate = DATEDIFF(M, #YearStart
, COALESCE(NULLIF(Closedate, ''), '99991231'))
FROM Incidents
the Closedate need to always have a value higher than the OpenDate, for this is used the constant date 9999-12-31, pOpenDate and pClosedate, as pMonth before, are the progressive month starting from #YearStart respectively of OpenDate and Closedate.
Putting those togheter it's possible to create the main query
declare #YearStart date = '20140101'
declare #YearEnd date = '20140430'
;WITH D(N) AS (
SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
UNION ALL SELECT 8 UNION ALL SELECT 9
), CM AS (
SELECT EOM
= DATEADD(D, -1, DATEADD(M, u.N + 10 * t.N + 1
, DATEADD(Y, DATEDIFF(Y, 0, #YearStart), 0)))
, pMonth = u.N + 10 * t.N
FROM D u
CROSS JOIN D t
WHERE u.N + 10 * t.N <= DATEDIFF(M, #YearStart, #YearEnd)
), I AS (
SELECT ID
, OpenDate
, Closedate = COALESCE(NULLIF(Closedate, ''), '99991231')
, pOpenDate = DATEDIFF(M, #YearStart, OpenDate)
, pClosedate = DATEDIFF(M, #YearStart
, COALESCE(NULLIF(Closedate, ''), '99991231'))
FROM Incidents
)
SELECT MONTH(CM.EOM) [Month]
, Logged = SUM(CASE WHEN pOpenDate = pMonth
THEN 1
ELSE 0
END)
, Received = Count(i.id)
, Closed = SUM(CASE WHEN pClosedate = pMonth
AND i.Closedate < CM.EOM
THEN 1
ELSE 0
END)
, Remaining = SUM(CASE WHEN i.Closedate > CM.EOM
THEN 1
ELSE 0
END)
FROM CM
INNER JOIN I ON CM.pMonth
BETWEEN i.pOpenDate AND i.pClosedate
WHERE CM.EOM <= #YearEnd
GROUP BY CM.EOM
ORDER BY CM.EOM
SQLFiddle Demo
using a JOIN to get the month from the calendar table between #YearStart and #YearEnd and all the incident alive in the month. Their attribute are calculated with the CASE logic, in case of Received if a ticket is alive it's received so no logic is needed.
All the CASE can be transformed in BIT logic
declare #YearStart date = '20140101'
declare #YearEnd date = '20140430'
;WITH D(N) AS (
SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
UNION ALL SELECT 8 UNION ALL SELECT 9
), CM AS (
SELECT EOM
= DATEADD(D, -1, DATEADD(M, u.N + 10 * t.N + 1
, DATEADD(Y, DATEDIFF(Y, 0, #YearStart), 0)))
, pMonth = u.N + 10 * t.N
FROM D u
CROSS JOIN D t
WHERE u.N + 10 * t.N <= DATEDIFF(M, #YearStart, #YearEnd)
), I AS (
SELECT ID
, OpenDate
, Closedate = COALESCE(NULLIF(Closedate, ''), '99991231')
, pOpenDate = DATEDIFF(M, #YearStart, OpenDate)
, pClosedate = DATEDIFF(M, #YearStart
, COALESCE(NULLIF(Closedate, ''), '99991231'))
FROM Incidents
)
SELECT MONTH(CM.EOM) [Month]
, Logged = SUM(1 - CAST(pOpenDate - pMonth AS BIT))
, Received = Count(i.id)
, Closed = SUM(1 - CAST(pClosedate - pMonth AS BIT))
, Remaining = SUM(0 + CAST(i.pClosedate / (CM.pMonth + 1) AS BIT))
FROM CM
INNER JOIN I ON CM.pMonth
BETWEEN i.pOpenDate AND i.pClosedate
WHERE CM.EOM <= #YearEnd
GROUP BY CM.EOM
ORDER BY CM.EOM;
SQLFiddle Demo
The bit logic is base on how the CAST to BIT works:
0 go to 0
everything else go to 1
based on that (with A and B integer):
1 - CAST(A - B AS BIT) is 1 when A = B
CAST(A / (B + 1) AS BIT) is 1 when A > B (the 0 + is to force an implicit cast to INT as BIT cannot be SUMmed)
Received would be the number of tickets that were opened before the end of the month, and not closed before the start of the month.
count(case when OpenDate <= #EndOfMonth and
(#StartOfMonth >= CloseDate or CloseDate is null) then 1 end)
as Received
Closed is straightforward:
count(case when CloseDate between #StartOfMonth and #EndOfMonth
then 1 end) as Closed
You should be able to figure out how to calculate the start and end of a month using Google.

How can I determine the date of a WeekDay column in a result set with a fixed layout given the start date?

I have a result set that always appears in this format:
LocationId Wed Thu Fri Sat Sun Mon Tue
The input to the function that generates these columns accepts a start date, and it generates a 7 day quantity report. So if I input '2014-02-01' the columns will always appear in that order even though that specific date falls on a Saturday (the dates "wrap around").
I need the date for each column for the purpose of a calculating another value (called 'Fee') that is based on a start + end date for each location. For example, location 21 might have a value of 50 associated with it for dates '2014-01-01' to '2014-02-03', but from '2014-02-04' it has a value of 53. The values under the day columns refer to Sales. So if there's a value (even 0), it means the SalesPerson was present and he should receive an AppearanceFee. One of the difficulties is calculating exactly what Fee the person should receive on a particular day as the report doesn't generate dates. The only information you have is the start date.
eg.
LocationId | Value | StartDate | EndDate
-----------+-------+------------+-----------
21 | 50 | 2014-01-01 | 2014-02-03
21 | 53 | 2014-02-04 | null
To simulate one record, one can use this query:
declare #startdate datetime
select #startdate = '2014-02-01'
select *
, 0 as Fee -- How do I calculate this value?
from
(
select 21 as LocationId
, 30 as Wed
, 33 as Thu
, 36 as Fri
, NULL as Sat
, NULL as Sun
, 19 as Mon
, 24 as Tue
) record
I've thought of using a complex case statement for each day but is there a simpler method?
CASE Left(DATENAME(dw, #startdate), 3)
WHEN 'Wed' THEN
(
(SELECT IsNull(Wed, 0) * Value FROM LocationValue lv WHERE lv.LocationId = record.LocationId AND #startdate BETWEEN lv.StartDate and IsNull(lv.EndDate, '2050-12-31')) +
(SELECT IsNull(Thu, 0) * Value FROM LocationValue lv WHERE lv.LocationId = record.LocationId AND DateAdd(dd, 1, #startdate) BETWEEN lv.StartDate and IsNull(lv.EndDate, '2050-12-31')) +
(SELECT IsNull(Fri, 0) * Value FROM LocationValue lv WHERE lv.LocationId = record.LocationId AND DateAdd(dd, 2, #startdate) BETWEEN lv.StartDate and IsNull(lv.EndDate, '2050-12-31')) +
(SELECT IsNull(Sat, 0) * Value FROM LocationValue lv WHERE lv.LocationId = record.LocationId AND DateAdd(dd, 3, #startdate) BETWEEN lv.StartDate and IsNull(lv.EndDate, '2050-12-31')) +
(SELECT IsNull(Sun, 0) * Value FROM LocationValue lv WHERE lv.LocationId = record.LocationId AND DateAdd(dd, 4, #startdate) BETWEEN lv.StartDate and IsNull(lv.EndDate, '2050-12-31')) +
(SELECT IsNull(Mon, 0) * Value FROM LocationValue lv WHERE lv.LocationId = record.LocationId AND DateAdd(dd, 5, #startdate) BETWEEN lv.StartDate and IsNull(lv.EndDate, '2050-12-31')) +
(SELECT IsNull(Tue, 0) * Value FROM LocationValue lv WHERE lv.LocationId = record.LocationId AND DateAdd(dd, 6, #startdate) BETWEEN lv.StartDate and IsNull(lv.EndDate, '2050-12-31'))
)
As you can see this case statement is rather unwieldy.
I managed to solve this using a combination of PIVOTs and UNPIVOTs, and a query that generates a date range.
DECLARE #startDate DATETIME = '2014-02-01'
DECLARE #endDate DATETIME = #startDate + 7
SELECT
LocationId,
Sum(Wed) Wed,
Sum(Thu) Thu,
Sum(Fri) Fri,
Sum(Sat) Sat,
Sum(Sun) Sun,
Sum(Mon) Mon,
Sum(Tue) Tue,
Sum(Fee) Fee
FROM
(
SELECT
af.LocationId,
Calendar.Day,
Date,
Sales,
IsNumeric(Sales) * Value AS Fee
FROM
(
SELECT
Left(DateName(DW, datetable.Date), 3) Day,
Convert(DATE, datetable.Date) Date
FROM (
SELECT DATEADD(DAY, -(a.a + (10 * b.a) + (100 * c.a)), getdate()) AS Date
FROM (SELECT 0 AS a
UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6
UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS a
CROSS JOIN (SELECT 0 AS a
UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6
UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS b
CROSS JOIN (SELECT 0 AS a
UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6
UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS c
) datetable
WHERE datetable.Date BETWEEN #startDate AND #endDate
) Calendar LEFT JOIN
(
SELECT
LocationId,
Day,
Sales
FROM dbo.f_FakeReport(#startDate) AS Report
UNPIVOT
(
Sales
FOR Day IN (Wed, Thu, Fri, Sat, Sun, Mon, Tue)
) U) AS Report
ON Calendar.Day = Report.Day
LEFT JOIN AppearanceFee af
ON af.LocationId = Report.LocationId
AND date BETWEEN af.StartDate AND IsNull(af.EndDate, '2099-12-21')
) data
PIVOT
(
Sum(Sales)
FOR Day IN (Wed, Thu, Fri, Sat, Sun, Mon, Tue)
) pvt
WHERE LocationId IS NOT NULL
GROUP BY LocationId
http://sqlfiddle.com/#!3/98759/52

Dynamic PIVOT in SQL Server 2005

i rare use PIVOT in sql server but now requirement is something that i have to use PIVOT.
my table structure is something like
CurDate Warranty_Info
------- -------------
01/01/2009 50
01/05/2009 30
01/03/2009 220
01/01/2010 40
01/06/2010 10
01/02/2010 0
01/01/2011 10
01/05/2012 420
01/05/2013 130
now i have to show the data in this way
Month 2009 2010 2011 2012 2013
----- ---- ---- ---- ---- -----
JAN 10 0 11 32 98
FEB 20 10 21 11 44
MAR 0 224 33 77 31
UPTO
DEC
1) data should display month wise order....so jan first
2) if no data exist in any month then month name will come with 0 as value for that month.
i tried but fail. here is my sql by which i tried.
SELECT *
FROM (SELECT DateName(month,DateAdd(month,Month(CurDate),-1)) as [Month],
YEAR(CurDate) AS WarrantyYear,
Warranty_Info
FROM eod_main) AS D
PIVOT (
SUM(Warranty_Info)
FOR WarrantyYear IN (
[2009],[2010],[2011],[2012],[2013]
)
) AS P
ORDER BY DATENAME(MONTH,DATEADD(MONTH, [Month] - 1, 0))
and i tried to generate sql dynamically this way.
DECLARE #cols AS NVARCHAR(MAX)
DECLARE #PivotTableSQL NVARCHAR(MAX)
DECLARE #StartYear AS INT,
#EndYear AS INT
SET #StartYear=2009
SET #EndYear=2013
select #cols = STUFF((SELECT ',' + QUOTENAME(Year(CurDate))
from eod_main
WHERE Year(CurDate)>=#StartYear AND Year(CurDate) <=#EndYear
group by Year(CurDate)
order by Year(CurDate)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #PivotTableSQL = N'
SELECT * FROM (
SELECT Month(CurDate), YEAR(CurDate) AS WarrantyYear,Warranty_Info FROM eod_main) AS D
PIVOT (
SUM(Warranty_Info)
FOR WarrantyYear IN (
' + #cols + '
)
) AS PivotTable
'
print #PivotTableSQL
but some where i am facing problem like
1) display month name
2) order by month no
3) null value show show 0 instead of NULL
4) if no data exist for any month then month name should display with 0 value.
please guide me how to achieve it. thanks
UPDATE
DECLARE #query varchar(max)
DECLARE #StartYr INT
DECLARE #ENDYr INT
declare #years varchar(max), #yearsColumns varchar(max)
SET #StartYr=2011
SET #EndYr=2013
SELECT 1 mID, 'January' as month into #tempMonths UNION ALL
SELECT 2,'February' as month UNION ALL
SELECT 3,'March' as month UNION ALL
SELECT 4,'April' as month UNION ALL
SELECT 5,'May' as month UNION ALL
SELECT 6,'June' as month UNION ALL
SELECT 7,'July' as month UNION ALL
SELECT 8,'August' as month UNION ALL
SELECT 9,'September' as month UNION ALL
SELECT 10,'October' as month UNION ALL
SELECT 11,'November' as month UNION ALL
SELECT 12,'December' as month
SELECT #years=COALESCE(#years+',','') +'['+ cast(years as varchar(4))+']',
#yearsColumns=COALESCE(#yearsColumns+',','') +'isnull(['+ cast(years as varchar(4))+'],0)
as ['+cast(years as varchar(4))+']'
from (select distinct YEAR(CurDate) years from EOD_Main
WHERE YEAR(CurDate)>=#StartYr AND YEAR(CurDate)<=#EndYr
) as x
SET #query = 'Select months,'+#yearsColumns+' from (
select distinct mID, YEAR(CurDate) years,[MONTH] months,
isnull(Warranty_Info,0) as Warranty_Info from EOD_Main
right join #tempMonths on datename(month,CurDate ) =[month]
) as xx
PIVOT
(
SUM(xx.Warranty_Info) FOR years IN ('+#years+')
)
as pvt ORDER BY mID'
--PRINT #query
EXEC(#query)
drop table #tempMonths
Hi Find the Below Solution, i hope that it is help full to you
SELECT CAST('01/01/2009' AS date) CurDate , 50 Warranty_Info INto #temp UNION all SELECT
CAST('05/01/2009' AS date) , 30 UNION all SELECT
CAST('03/01/2009' AS date) , 220 UNION all SELECT
CAST('01/01/2010' AS date) , 40 UNION all SELECT
CAST('06/01/2010' AS date) , 10 UNION all SELECT
CAST('02/01/2010' AS date) , 0 UNION all SELECT
CAST('01/01/2011' AS date) , 10 UNION all SELECT
CAST('05/01/2012' AS date) , 420 UNION all SELECT
CAST('05/01/2013' AS date) , 130
SELECT 1 mID, 'January' as month into #tempMonths UNION ALL
SELECT 2,'February' as month UNION ALL
SELECT 3,'March' as month UNION ALL
SELECT 4,'April' as month UNION ALL
SELECT 5,'May' as month UNION ALL
SELECT 6,'June' as month UNION ALL
SELECT 7,'July' as month UNION ALL
SELECT 8,'August' as month UNION ALL
SELECT 9,'September' as month UNION ALL
SELECT 10,'October' as month UNION ALL
SELECT 11,'November' as month UNION ALL
SELECT 12,'December' as month
declare #years varchar(max), #yearsColumns varchar(max)
SELECT #years=COALESCE(#years+',','') +'['+ cast(years as varchar(4))+']',
#yearsColumns=COALESCE(#yearsColumns+',','') +'isnull(['+ cast(years as varchar(4))+'],0) as ['+cast(years as varchar(4))+']'
from (select distinct YEAR(CurDate) years from #temp) as x
print #years
DECLARE #query varchar(max)= '
select months,'+#yearsColumns+' from (
select distinct mID, YEAR(CurDate) years,[MONTH] months, isnull(Warranty_Info,0) as Warranty_Info from #temp
right join #tempMonths on datename(month,CurDate ) =[month]
) as xx
PIVOT
(
SUM(xx.Warranty_Info) FOR years IN ('+#years+')
)
as pvt ORDER BY mID'
PRINT #query
EXEC(#query)
output islike below
months 2009 2010 2011 2012 2013
--------- ----------- ----------- ----------- ----------- -----------
January 50 40 10 0 0
February 0 0 0 0 0
March 220 0 0 0 0
April 0 0 0 0 0
May 30 0 0 420 130
June 0 10 0 0 0
July 0 0 0 0 0
August 0 0 0 0 0
September 0 0 0 0 0
October 0 0 0 0 0
November 0 0 0 0 0
December 0 0 0 0 0
if it is give accurate result don't forget to make a vote.

sql - max from group flag setter

I m trying to achieve flag setting for the condition in my table below
p_id mon_year e_id flag
---- --------- ----- -----
1 2011/11 20 0
1 2011/11 21 1
1 2012/01 22 1
1 2012/02 23 0
1 2012/02 24 0
1 2012/02 25 1
2 2011/11 28 0
2 2011/11 29 1
2 2012/01 30 1
grouping by p_id,e_id and mon_year, the flag is set for the last value in the month.
I m confused how can i achieve this
I tried to achieved this by using row_number and partition to seperate out the value. By still looking for to achieved
Output by using row_number query , i have got is as below:
Grouping by
p_id mon_year e_id row
---- --------- ----- -----
1 2011/11 20 1
1 2011/11 21 2
1 2012/01 22 1
1 2012/02 23 1
1 2012/02 24 2
1 2012/02 25 3
2 2011/11 28 1
2 2011/11 29 2
2 2012/01 30 1
Max of this value would set the flag column. But i m really bugged how to achieve it. Any help would be useful.
Thanks !!
I think this is what you're going for. . . The output exactly matches your example:
declare #t table (p_id int, [year] int, [month] int, [day] int)
insert #t select 1, 2011, 11, 20
union select 1, 2011, 11, 21
union select 1, 2012, 01, 22
union select 1, 2012, 02, 23
union select 1, 2012, 02, 24
union select 1, 2012, 02, 25
union select 2, 2011, 11, 28
union select 2, 2011, 11, 29
union select 2, 2012, 01, 30
select p_id, [year], [month], [day]
, case when r=1 then 1 else 0 end flag
from
(
select p_id, [year], [month], [day]
, row_number() over (partition by p_id, [year], [month] order by [day] desc) r
from #t
) x
order by p_id, [year], [month], [day]
Output:
p_id year month day flag
1 2011 11 20 0
1 2011 11 21 1
1 2012 1 22 1
1 2012 2 23 0
1 2012 2 24 0
1 2012 2 25 1
2 2011 11 28 0
2 2011 11 29 1
2 2012 1 30 1
Try ordering by descending. In that way, you don't have to look for maximum ROW_NUMBER but when ROW_NUMBER is 1 ;)
Something like this (I didn't completely understand what you want to achieve, so this is probably not 100% accurate):
WITH r_MyTable
AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY mon_year ORDER BY p_id, e_id DESC) AS GroupRank
FROM MyTable
)
UPDATE r_MyTable
SET flag = CASE WHEN GroupRank = 1 THEN 1 ELSE 0 END;
You can use max statement on e_id to get last value for the month, code is below:
IF OBJECT_ID('tempdb..#tmptest') IS NOT NULL
DROP TABLE #tmptest
SELECT
*
INTO
#tmptest
FROM
(
SELECT '1' p_id, '2011/11' mon_year, '20' e_id, '0' flag UNION ALL
SELECT '1', '2011/11', '21', '1' UNION ALL
SELECT '1', '2012/01', '22', '1' UNION ALL
SELECT '1', '2012/02', '23', '0' UNION ALL
SELECT '1', '2012/02', '24', '0' UNION ALL
SELECT '1', '2012/02', '25', '1' UNION ALL
SELECT '2', '2011/11', '28', '0' UNION ALL
SELECT '2', '2011/11', '29', '1' UNION ALL
SELECT '2', '2012/01', '30', '1'
) as tmp
SELECT
tmptest.*
FROM
(
SELECT
MAX(e_id) e_id
,p_id
,mon_year
FROM
#tmptest
GROUP BY
p_id,mon_year
) tblLastValueEID
INNER JOIN
#tmptest tmptest
ON
tmptest.p_id = tblLastValueEID.p_id
AND
tmptest.mon_year = tblLastValueEID.mon_year
AND
tmptest.e_id = tblLastValueEID.e_id