SQL Find Tickets Open Between Start Date and End Date - sql-server

I currently have the following table:
+-----+-----------------------------+------------------------------+
| ID | StartDate | EndDate |
+-----+-----------------------------+------------------------------|
| 1 | 2017-07-24 08:00:00.000 | 2017-07-29 08:00:00.000 |
| 2 | 2017-07-25 08:00:00.000 | 2017-07-28 08:00:00.000 |
| 3 | 2017-07-25 08:00:00.000 | 2017-07-26 08:00:00.000 |
+-----+-----------------------------+------------------------------+
I would like to know the count of the ID's that were not Closed on each date.
So for example, I wan't to know the count of open ID's on 2017-07-26 00:00:00.000. This would be all 3 in this case.
Another example: I wan't to know the count of open ID's on 2017-07-29 00:00:00.000. Which would be result to 1. Only ID=1 is Not yet closed at that date.
I have tried using another solution here on StackOverflow, but I can't quite figure why it is giving me false results.
declare #dt date, #dtEnd date
set #dt = getdate()-7
set #dtEnd = dateadd(day, 100, #dt);
WITH CTEt1 (SupportCallID, StartDate, EndDate, Onhold)
as
(SELECT SupportCallID
,OpenDate
,MAX(CASE WHEN StatusID IN('19381771-8E81-40C5-8E36-62A7DB0A2A99', '95C7A5FB-2389-4D14-9DAE-A08BFCC3B09A', 'D5429790-3B43-4462-9E1E-2466EA29AC74') then CONVERT(DATE, LastChangeDate) end) EndDate
,OnHold
FROM [ClienteleITSM_Prod_Application].[dbo].[SupportCall]
group by SupportCallID, OpenDate, OnHold
)
SELECT dates.myDate,
(SELECT COUNT(*)
FROM CTEt1
WHERE myDate BETWEEN StartDate and EndDate
)
FROM
(select dateadd(day, number, #dt) mydate
from
(select distinct number from master.dbo.spt_values
where name is null
) n
where dateadd(day, number, #dt) < #dtEnd) dates

If you use a cte to create a table of dates that span the range of dates in your source table, you can easily left join from that to your source table and count up the rows returned:
declare #t table(ID int,StartDate datetime,EndDate datetime);
insert into #t values (1,'2017-07-24 08:00:00.000','2017-07-29 08:00:00.000'),(2,'2017-07-25 08:00:00.000','2017-07-28 08:00:00.000'),(3,'2017-07-25 08:00:00.000','2017-07-26 08:00:00.000');
declare #StartDate datetime = (select min(StartDate) from #t);
declare #EndDate datetime = (select max(EndDate) from #t);
-- Table with 10 rows in to be joined together to create a large tally table (10 * 10 * 10 * etc)
with t(t) as (select t from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(t))
-- Add the row_number of the tally table to your start date to generate all dates within your data range
,d(d) as (select top(datediff(d,#StartDate,#EndDate)+1) dateadd(d,row_number() over (order by (select null))-1,#StartDate) from t t1,t t2,t t3)
select d.d
,count(t.ID) as OpenIDs
from d
left join #t as t
on(d.d between cast(t.StartDate as date) and t.EndDate)
group by d.d
order by d.d;
Output:
+-------------------------+---------+
| d | OpenIDs |
+-------------------------+---------+
| 2017-07-24 08:00:00.000 | 1 |
| 2017-07-25 08:00:00.000 | 3 |
| 2017-07-26 08:00:00.000 | 3 |
| 2017-07-27 08:00:00.000 | 2 |
| 2017-07-28 08:00:00.000 | 2 |
| 2017-07-29 08:00:00.000 | 1 |
+-------------------------+---------+

Related

SQL- split single row into multiple based on date columns

Matthew earns $600 in three days. And each day how he should earn should be split into three different rows.
RDBMS is SQL Server.
id name start_date end_date Total_Dollars
---------------------------------------------------
1 Mathew 01/01/2021 03/01/2021 600
Output should be
id name start_date end_date Total_Dollars
--------------------------------------------------
1 Rahul 01/01/2021 01/01/2021 200
1 Rahul 02/01/2021 02/01/2021 200
1 Rahul 03/01/2021 03/01/2021 200
If you have a calendar table, use that:
WITH
-- need a calendar table with one row per calendar date
cal (dt) AS (
SELECT DATE '2021-01-01'
UNION ALL SELECT DATE '2021-01-02'
UNION ALL SELECT DATE '2021-01-03'
UNION ALL SELECT DATE '2021-01-04'
UNION ALL SELECT DATE '2021-01-05'
UNION ALL SELECT DATE '2021-01-06'
UNION ALL SELECT DATE '2021-01-07'
)
,
-- your input ...
indata(id,nam,start_date,end_date,total_dollars) AS (
SELECT 1,'Mathew',DATE '2021-01-01',DATE '2021-01-03',600
)
-- real query starts here, replace following comma with "WITH" ...
,
daycount(daycount) AS (
SELECT COUNT(*) FROM cal JOIN indata ON dt BETWEEN start_date AND end_date
)
SELECT
id
, nam
, dt AS start_date
, dt AS end_date
, total_dollars / daycount AS total_dollars
FROM cal
JOIN indata ON dt BETWEEN start_date AND end_date
CROSS JOIN daycount;
-- out id | nam | start_date | end_date | total_dollars
-- out ----+--------+------------+------------+---------------
-- out 1 | Mathew | 2021-01-01 | 2021-01-01 | 200
-- out 1 | Mathew | 2021-01-02 | 2021-01-02 | 200
-- out 1 | Mathew | 2021-01-03 | 2021-01-03 | 200
Or, also:
SELECT
id
, nam
, dt AS start_date
, dt AS end_date
, total_dollars // count(*) OVER(PARTITION BY id) AS total_dollars
FROM cal
JOIN indata ON dt BETWEEN start_date AND end_date
-- out id | nam | start_date | end_date | total_dollars
-- out ----+--------+------------+------------+---------------
-- out 1 | Mathew | 2021-01-01 | 2021-01-01 | 200
-- out 1 | Mathew | 2021-01-02 | 2021-01-02 | 200
-- out 1 | Mathew | 2021-01-03 | 2021-01-03 | 200
You may use a recursive query as the following:
WITH CTE AS
(
SELECT id, name, start_date SDT, end_date, Total_Dolllars
FROM T
UNION ALL
SELECT id, name, DATEADD(DAY, 1,SDT), end_date, Total_Dolllars
FROM CTE
WHERE DATEADD(DAY, 1,SDT) <= end_date
)
SELECT id, name, SDT start_date, SDT end_date,
Total_Dolllars *1.00 / COUNT(*) OVER (PARTITION BY id) Total_Dolllars
FROM CTE
ORDER BY ID, SDT;
See a demo.

SQL Loop form between two days with ID

I Want to create loop between two days and USERID
For Example
If I Choose between 01-01-2017 and 05-01-2017
procedure already insert into my table days between for each user
every single user has a same days
I've tried the following
DECLARE #USERID int,#S_Date date,#E_Date date
SELECT #S_Date = #S_Date, #USERID = #USERID
While #S_Date <= #E_Date
begin
select #S_Date, #USERID
while #USERID < 3
begin
INSERT INTO myTable values(#S_Date, #USERID)
select #S_Date as S, #USERID as U
set #USERID = #USERID + 1
end
set #S_Date = DateAdd(Day, 1, #S_Date)
end
declare #fromdate date = '20170101'
declare #thrudate date = '20170105'
declare #from_UserId int = 1;
declare #thru_UserId int = 3;
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, dates as (
select top (datediff(day, #fromdate, #thrudate)+1)
[Date]=convert(date,dateadd(day,row_number() over(order by (select 1))-1,#fromdate))
from n as deka cross join n as hecto cross join n as kilo
cross join n as tenK cross join n as hundredK
order by [Date]
)
insert into myTable ([Date],UserId)
select [Date], u.UserId
from dates
cross join (
select UserId
from Users u
where u.UserId >= #from_UserId
and u.UserId <= #thru_UserId
) u;
The above uses stacked ctes to generate a numbers table, and uses that numbers table to generate dates between the date range provided.
It might not look as simple as using a while loop, but it will perform much better.
rextester demo: http://rextester.com/HHY62656
returns:
+------------+--------+
| date | userid |
+------------+--------+
| 2017-01-01 | 1 |
| 2017-01-02 | 1 |
| 2017-01-03 | 1 |
| 2017-01-04 | 1 |
| 2017-01-05 | 1 |
| 2017-01-01 | 2 |
| 2017-01-02 | 2 |
| 2017-01-03 | 2 |
| 2017-01-04 | 2 |
| 2017-01-05 | 2 |
| 2017-01-01 | 3 |
| 2017-01-02 | 3 |
| 2017-01-03 | 3 |
| 2017-01-04 | 3 |
| 2017-01-05 | 3 |
+------------+--------+
Number and Calendar table reference:
Generate a set or sequence without loops - 2 - Aaron Bertrand
The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden
Creating a Date Table/Dimension in sql Server 2008 - David Stein
Calendar Tables - Why You Need One - David Stein
Creating a date dimension or calendar table in sql Server - Aaron Bertrand

How to create data between two different dates with limited data

I am trying to create the data between two different dates.
Data in table looks like as shown below:
StartDate | EndDate | StudId | Active
-----------+---------------+-------------+-----------
01-01-2009 | 02-15-2009 | 12345 | Y
02-16-2009 | 03-15-2009 | 12345 | Y
03-16-2009 | 04-10-2009 | 12345 | N
04-11-2009 | 05-31-2009 | 12345 | Y
01-01-2009 | 02-15-2009 | 23642 | Y
02-16-2009 | 03-15-2009 | 23642 | Y
03-16-2009 | 04-10-2009 | 23642 | N
04-11-2009 | 05-31-2009 | 23642 | Y
and the data in table goes on with different Startdate, EndDate and StudID.
I am trying to get the result as shown below:
Startdate | StudID | Active
------------+----------+--------
01-01-2009 | 12345 | Y
01-02-2009 | 12345 | Y
01-03-2009 | 12345 | Y
01-04-2009 | 12345 | Y
. . .
. . .
02-15-2009 | 12345 | Y
02-16-2009 | 12345 | Y
As shown above I am trying to load the active data for student based on dates between Startdate and enddate.
We don't have any daily data using startdate and enddate we need to create daily data. If there is a gap between EndDate and next Startdate then the Active field should be '0' for those dates
Can someone suggest how to do this?
This requires calendar table and join
WITH calendar
AS (SELECT Min(StartDate) AS dates,
Max(EndDate) ed_date
FROM Yourtable
UNION ALL
SELECT Dateadd(dd, 1, dates),
ed_date
FROM calendar
WHERE dates < ed_date)
SELECT a.dates as Startdate,b.StudID,b.Active
FROM calendar a
JOIN Yourtable b
ON a.dates BETWEEN b.StartDate AND b.EndDate
ORDER BY dates
OPTION (maxrecursion 0)
Note: I have used Recursive CTE to generate dates. It is better to create physical calendar table and use it in queries like this
Live Demo
This should work.
declare #tmp date;
select #tmp = max(EndDate) from tmpTable;
print #tmp
;with cte as
(
select min(StartDate) over() as dd from tmpTable
union all select dateadd(day,1,dd) from cte where dd < #tmp
)
select distinct dd as StartDate, isnull(Studid, 12345), isnull(Active,0) as Active from tmpTable as t
right join cte as c on c.dd between t.startDate and t.enddate
where t.Studid = 12345 or t.studid is null
option (maxrecursion 0)

Attendance summarized report

Due to company policies I cannot give the actual query I am working with but heres the breakdown and general idea. We have an attendance register that records for each day if an employee was at work or not and where the employee works at. I am trying to make a summary of this to say between this and that date the employee worked 5 shifts. The problem I am sitting with is that one particular employee worked in workplace A for 2 days and was then transferred to workplace B. After a few days at workplace B the employee was then transferred back to workplace A.
My results to my attempt has showed that the employee begun working at workplace A from 1-Jan and ended at 10-Jan with only 2 working shifts. I have a group by on the working place and the begin and end dates are a min and max selection.
SELECT att.Employee, att.Workplace, dte.BeginDate, dte.EndDate, shf.WorkShift FROM
(SELECT * FROM Attendance WHERE WorkDate BETWEEN '1-Jan' AND '30-Jan') att
CROSS APPLY (SELECT COUNT(Shift) WorkShift FROM Attendance WHERE WorkDate BETWEEN '1-Jan' AND '30-Jan' AND Employee = att.Employee AND WorkPlace = att.WorkPlace AND Shift = 'Worked') shf
CROSS APPLY (SELECT MAX(WorkDate) BeginDate, MIN(WorkDate) EndDate FROM Attendance WHERE WorkDate BETWEEN '1-Jan' AND '30-Jan' AND Employee = att.Employee AND WorkPlace = att.WorkPlace) dte
So this employees records should appear like this (I am sorry for the very bad grid, I don't know how to make it look pretty, you are more than welcome to edit it to look better)
| Name | Workplace | beginDate | endDate | WorkShift |
| Jane | WorkPlaceA | 1-Jan | 2-Jan | 2 |
| Jane | WorkPlaceB | 3-Jan | 8-Jan | 5 |
| Jane | WorkPlaceA | 9-Jan | 10-Jan | 2 |
The attendance table looks something like this
| Name | Workplace | Date | Shift |
| Jane | WorkplaceA | 1-Jan | Worked |
| Jane | WorkplaceA | 2-Jan | Worked |
| Jane | WorkplaceB | 3-Jan | Worked |
| Jane | WorkplaceB | 4-Jan | Worked |
| Jane | WorkplaceB | 5-Jan | Worked |
| Jane | WorkplaceA | 6-Jan | Absent |
| Jane | WorkplaceA | 7-Jan | Absent |
| Jane | WorkplaceA | 8-Jan | Worked |
| Jane | WorkplaceB | 9-Jan | Worked |
| Jane | WorkplaceB | 10-Jan | Worked |
I believe you can accomplish this using CTE's. Here is a sample working code that shows your expected values.
;WITH CTE1 AS (
SELECT Employee, WorkPlace, TransactionDate,
ROW_NUMBER() OVER(PARTITION BY WorkPlace ORDER BY TransactionDate) AS WP,
ROW_NUMBER() OVER(ORDER BY TransactionDate) AS RN FROM Attendance WHERE Shift = 'Worked'),
CTE2 AS (SELECT Employee, WorkPlace, TransactionDate, WP, RN, WP-RN AS GB FROM CTE1),
CTE3 AS (SELECT Employee, WorkPlace, MIN(TransactionDate) AS TransactionDate, COUNT(1) AS Shifts FROM CTE2 GROUP BY Employee, WorkPlace, GB)
SELECT Employee, WorkPlace, TransactionDate AS [Start Date], DATEADD(DAY,Shifts - 1,TransactionDate) AS [End Date], Shifts FROM CTE3 ORDER BY TransactionDate ASC
I think your given output is wrong.
I think the way you are populating table is wrong.
Check my query,it can be further optmize,it do not count absent days
declare #t table(Name varchar(100),Workplace varchar(100), AttnDate date ,Shifts varchar(100))
insert into #t values
('Jane','WorkplaceA',' 1-Jan-16','Worked')
,('Jane','WorkplaceA',' 2-Jan-16','Worked')
,('Jane','WorkplaceB',' 3-Jan-16','Worked')
,('Jane','WorkplaceB',' 4-Jan-16','Worked')
,('Jane','WorkplaceB',' 5-Jan-16','Worked')
,('Jane','WorkplaceA',' 6-Jan-16','Absent')
,('Jane','WorkplaceA',' 7-Jan-16','Absent')
,('Jane','WorkplaceA',' 8-Jan-16','Worked')
,('Jane','WorkplaceB',' 9-Jan-16','Worked')
,('Jane','WorkplaceB','10-Jan-16','Worked')
DECLARE #Name VARCHAR(100) = 'Jane'
DECLARE #FromDate DATE = '01-Jan-16'
DECLARE #ToDate DATE = '31-Jan-16';
WITH CTE
AS (
SELECT *
,row_number() OVER (
ORDER BY attndate
) rn
FROM #t
WHERE NAME = #Name
AND (
AttnDate BETWEEN #FromDate
AND #ToDate
)
)
,CTE1
AS (
SELECT A.NAME
,A.workplace
,A.AttnDate
,Shifts
,rn
,1 RN1
FROM cte A
WHERE rn = 1
UNION ALL
SELECT a.NAME
,a.workplace
,a.AttnDate
,a.Shifts
,CASE
WHEN a.workplace = b.workplace
THEN b.rn
ELSE b.rn + 1
END rn
,RN1 + 1
FROM CTE A
INNER JOIN CTE1 b ON a.attndate > b.attndate
WHERE a.rn = RN1 + 1
)
,CTE2
AS (
SELECT NAME
,Workplace
,AttnDate beginDate
,(
SELECT max(AttnDate)
FROM CTE1 b
WHERE b.rn = a.rn
) endDate
,(
SELECT count(*)
FROM CTE1 b
WHERE b.rn = a.rn
AND Shifts = 'Worked'
) WorkShift
,rn
,ROW_NUMBER() OVER (
PARTITION BY rn ORDER BY rn
) rn3
FROM cte1 a
)
SELECT NAME
,workplace
,beginDate
,endDate
,WorkShift
FROM cte2
WHERE rn3 = 1

SQL - how do I generate rows for each month based on date ranges in existing dataset?

assume I have a dataset:
rowID | dateStart | dateEnd | Year | Month
121 | 2013-10-03 | 2013-12-03 | NULL | NULL
143 | 2013-12-11 | 2014-03-11 | NULL | NULL
322 | 2014-01-02 | 2014-02-11 | NULL | NULL
And I want sql to generate the following datasource based on the dateStart and the dateEnd. Note the year and month grouping.
rowID | dateStart | dateEnd | Year | Month
121 | 2013-10-03 | 2013-12-03 | 2013 | 10
121 | 2013-10-03 | 2013-12-03 | 2013 | 11
121 | 2013-10-03 | 2013-12-03 | 2013 | 12
143 | 2013-12-11 | 2014-03-11 | 2013 | 12
143 | 2013-12-11 | 2014-03-11 | 2014 | 1
143 | 2013-12-11 | 2014-03-11 | 2014 | 2
143 | 2013-12-11 | 2014-03-11 | 2014 | 3
322 | 2014-01-02 | 2014-02-11 | 2014 | 1
322 | 2014-01-02 | 2014-02-11 | 2014 | 2
I'm having a hard time wrapping my head around this one. Any ideas?
I find it easiest to approach these problems by creating a list of integers and then using that to increment the dates. Here is an example:
with nums as (
select 0 as n
union all
select n + 1 as n
from nums
where n < 11
)
select rowid, datestart, dateend,
year(dateadd(month, n.n, datestart)) as yr,
month(dateadd(month, n.n, datestart)) as mon
from table t join
nums n
on dateadd(month, n.n - 1, datestart) <= dateend;
First, create a tabled-valued function that takes the 2 dates and returns the year and month as a table:
create function dbo.YearMonths(#StartDate DateTime, #EndDate DateTime)
returns #YearMonths table
([Year] int,
[Month] int)
as
begin
set #EndDate = DATEADD(month, 1, #EndDate)
while (#StartDate < #EndDate)
begin
insert into #YearMonths
select YEAR(#StartDate), MONTH(#StartDate)
set #StartDate = DATEADD(month, 1, #StartDate)
end
return
end
As an example the following:
select *
from dbo.YearMonths('1/1/2014', '5/1/2014')
returns:
Then you would join to it like this to get what you wanted:
select m.*, ym.Year, ym.Month
from myTable m
cross apply dbo.YearMonths(dateStart, dateEnd) ym
Try this:
declare #months table(mth int)
insert into #months values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
declare #calendar table(yr int,mth int)
insert into #calendar
select distinct year(datestart),mth
from tbl cross join #months
union
select distinct year(dateend),mth
from tbl cross join #months
select t.rowID, t.datestart, t.dateend, y.yr [Year], y.mth [Month]
from
yourtable t
inner join #calendar y on year(datestart) = yr or year(dateend) = yr
where
(mth >= month(datestart) and mth <= month(dateend) and year(datestart) = year(dateend))
or
(year(datestart) < year(dateend))
and
(year(datestart) = yr and mth >= month(datestart) --All months of start year
or
(year(dateend) = yr and mth <= month(dateend))) -- All months of end year
order by t.rowID, [Year],[Month]
We create a 'Calendar table' which lists all the month and year combinations present in the source table. Then, we join the source table to the calendar table based on the year, and filter as required.

Resources