Open Ticket Count Per Day - sql

I have a table that looks like this
id | Submit_Date | Close_Date
------------------------------
1 | 2015-02-01 | 2015-02-05
2 | 2015-02-02 | 2015-02-04
3 | 2015-02-03 | 2015-02-05
4 | 2015-02-04 | 2015-02-06
5 | 2015-02-05 | 2015-02-07
6 | 2015-02-06 | 2015-02-07
7 | 2015-02-07 | 2015-02-08
I can get a count of how many ticket were open on a particular day with this:
Select count(*) from tickets where '2015-02-05' BETWEEN Submit_Date and Close_Date
This gives me 4, but I need this count for each day of a month. I don't want to have to write 30 queries to handle this. Is there a way to capture broken down by multiple days?

I created a solution a way back using a mix of #Heinzi s solution with the trick from Generate a resultset of incrementing dates in TSQL
declare #dt datetime, #dtEnd datetime
set #dt = getdate()
set #dtEnd = dateadd(day, 100, #dt)
SELECT dates.myDate,
(SELECT COUNT(*)
FROM tickets
WHERE myDate BETWEEN Submit_Date and Close_Date
)
FROM
(select Dates_To_Checkselect 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
Code is combined from memory, I don't have it in front of me so there can be some typo's

First, you'll need a table that contains each date you want to check. You can use a temporary table for that. Let's assume that this table is called Dates_To_Check and has a field myDate:
SELECT myDate,
(SELECT COUNT(*)
FROM tickets
WHERE myDate BETWEEN Submit_Date and Close_Date)
FROM Dates_To_Check
Alternatively, you can create a huge table containing every possible date and use a WHERE clause to restrict the dates to those you are interested in.

If you're in SQL Server 2012 or newer you can do this using window functions with a small trick where you add 1 to the open days -1 to the closing days and then do a running total of this amount:
select distinct date, sum(opencnt) over (order by date) from (
select
Submit_Date as date,
1 as opencnt
from
ticket
union all
select
dateadd(day, 1, Close_Date),
-1
from
ticket
) TMP
There's a dateadd + 1 day to include the close date amount to that day

You could generate the list of dates and then retrieve the count for each date in your dateset.
The cte part generates the date list since the beginning of the year (an ssumption) and the next part calculates the count from your data set.
with cte as
(select cast('2015-01-01' as date) dt // you should change this part to the correct start date
union all
select dateadd(DD,1,dt) dt from cte
where dt<getdate()
)
select count(*)
from tickets
inner join cte
on cte.dt between Submit_Date and Close_Date
group by cte.dt

Related

SQL - Calculate relative amounts within a year from date segments

I am currently coding an existing Payroll system and I have the below problem. I need to count the Vacation days taken of one employee in one year in order to transfer them to the next. The days can be either complete, or hours in a day (e.g. 6 hour vacation from default 8 hour working day)
However the existing functionality only stores the aforementioned data in a table with columns like this.
EmployeeID | StartDate | EndDate | Hours
1 01-02-2018 04-02-2018 24
1 08-03-2018 08-03-2018 4
2 30-12-2017 04-01-2018 48
3 30-12-2018 04-01-2019 48
Now the issue is that I want to limit the dates to the previous year only. So since we have 2019, I need vacations only from 2018. Meaning records with different Start and End Year, need special handling
The result table should look like this
EmployeeID | HoursPreviousYear
1 28
2 32
3 16
I am already aware of some helpful SQL functions such as DATEDIFF() or YEAR(), but since each record is different, I would probably need to use a cursor and iterate the table. Then to pass the results to a different table, I would have create in the query and return it.
To be honest I am baffled...
I never had to use cursors before and as far as I can see, I am not sure even if I can return a table as a result (which I also need to use in a join later on). I am not sure if it is worth to continue struggling with it, but it seems that there should be an easier way.
My other option was to change the behavior of the Save button, to save 2 different records, with no overlapping years, but I cannot since we are having legacy data...
There are obviously some edge cases where this isn't thorough enough, but it should get you started.
This assumes 8 hours taken per day off, totally fails to account for date ranges that span a weekend or holiday, and wouldn't account for someone taking, say three full days off followed by a half day.
DECLARE #Year int = 2018;
SELECT
EmployeeID,
SUM(CASE WHEN StartDate < DATEFROMPARTS(#Year,1,1)
THEN DATEDIFF(DAY,DATEFROMPARTS(#Year-1,12,31),EndDate)*8
WHEN EndDate > DATEFROMPARTS(#Year,12,31)
THEN DATEDIFF(DAY,StartDate,DATEFROMPARTS(#Year+1,1,1))*8
ELSE [Hours]
END) AS HoursPreviousYear
FROM
#table
GROUP BY
EmployeeID;
+------------+-------------------+
| EmployeeID | HoursPreviousYear |
+------------+-------------------+
| 1 | 28 |
| 2 | 32 |
| 3 | 16 |
+------------+-------------------+
You can use DATEDIFF to calculate additional days for start and end date to deduct extra hours from total hours as shown in the following query-
SELECT EmployeeID,
SUM(Hours) - (SUM(StDiff)+SUM(EndDiff))*8 HoursPreviousYear
FROM
(
SELECT EmployeeID,
CONVERT(DATE, StartDate , 103) StartDate,
CONVERT(DATE, EndDate , 103) EndDate,
Hours,
CASE
WHEN YEAR(CONVERT(DATE, StartDate , 103)) = 2018 THEN 0
ELSE DATEDIFF(DD,CONVERT(DATE, StartDate , 103),CONVERT(DATE, '01-01-2018' , 103))
END StDiff,
CASE
WHEN YEAR(CONVERT(DATE, EndDate , 103)) = 2018 THEN 0
ELSE DATEDIFF(DD,CONVERT(DATE, '31-12-2018' , 103),CONVERT(DATE, EndDate , 103))
END EndDiff
FROM your_table
WHERE YEAR(CONVERT(DATE, StartDate , 103)) <= 2018
AND YEAR(CONVERT(DATE, EndDate , 103)) >= 2018
)A
GROUP BY EmployeeID

Get Room Price of Overlapping dates [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
I have table in this structure:
id accom_id room_type_id date_from date_to price_per_room
3 1 2 2017-09-01 2017-09-10 70.00
5 1 2 2017-09-11 2017-09-20 100.00
Lets say I want to stay from 2017-09-07 to 2017-09-15. So with DATEDIFF I need to count how many days the price is 70 and how many days the price is 100. At the end I want to show the total.
Can anyone help me build this query? I hope its clear what ask!
Assuming no overlapping ranges are defined, and assuming that all of the ranges given are meant to be inclusive, we can obtain the data using a CTE and then a simple matter of aggregation:
declare #t table (date_from date,date_to date, price_per_room int)
insert into #t (date_from,date_to,price_per_room) values
('20170901','20170910',70.00 ),
('20170911','20170920',100.00)
declare #Start date
declare #End date
select #Start = '20170907',#End = '20170915'
;With IncludedPeriods as (
select
CASE WHEN #Start > date_from THEN #Start ELSE date_from END as fromDT,
CASE WHEN #End < date_to THEN #End ELSE date_to END as ToDT,
price_per_room
from
#t
where
date_from <= #End and
#Start <= date_to
)
select
SUM(price_per_room * (1 + DATEDIFF(day,fromDT,ToDT)))
from
IncludedPeriods
Note that we're adding one to the DATEDIFF result since it counts transitions, but I'm assuming that a period from '20170911' to '20170911' should count as one day and longer periods similarly.
Unlike some of the other answers which attempt to enumerate various "cases" for overlaps, this uses the simple rule - two periods overlap if the first starts before the second ends and if the second starts before the first ends - that's the logic applied in the where clause inside the CTE. To determine the extent of the overlap, we take the later of the two start dates and the earlier of the two end dates - that's what the CASE expressions are doing. If we had scalar MIN and MAX functions that operated on dates I'd prefer to use those but no such functions built into SQL Server.
In SQL Server you can do this:
select dateadd(d, number, '20170801')
from master.dbo.spt_values
where type='P' and (number <= datediff(d, '20170801', '20170810'))
You calculate the total days (in the where clause) and you select the numbers from 0 to total days (master.dbo.spt_values is a helpful table in SQL Server). Otherwise you can create a table with ascending numbers from 0 to ?). And then you add to the startdate the number. So you get all days of the booking:
2017-08-01
2017-08-02
2017-08-03
2017-08-04
2017-08-05
2017-08-06
2017-08-07
2017-08-08
2017-08-09
2017-08-10
Then you can join your given price_table on date_from and date_to and you can calculate your sum.
EDIT: Just to complete it... :-)
SELECT SUM(price_per_room)
FROM master.dbo.spt_values
LEFT OUTER JOIN room_prices AS rp ON (date_from <= DATEADD(d, number, my_start_date) AND DATEADD(d, number, my_start_date) <= date_to)
WHERE type='P' AND (number <= DATEDIFF(d, my_start_date, my_end_date))
I would suggest using a Date table(table containing all dates) or a derived table to populate these dates(you can find it online easily) like this:
SELECT p.id,p.room_type_id,sum(p.price_per_room)
FROM(
SELECT t.date,s.price_per_room,s.id,s.room_type_id
FROM (DATES QUERY \ DATE_TABLE) t
LEFT JOIN s.yourTable
ON(t.date between s.date_from and s.date_to)) p
WHERE p.id = YourID and p.room_type_id = YourTypeId
AND p.date between yourStartDate and yourEndDate
GROUP BY p.id,p.room_type_id
That populate a row for each date inside the range of dates for the price, so it will know how much is the price per day, and then just sum the price between your desired dates.
You can use a calendar or dates table for this sort of thing.
Without taking the actual step of creating a table, you can use an adhoc tables of dates with a common table expression like so:
declare #fromdate date = '20170907';
declare #thrudate date = '20170915';
;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]
)
select total_price = sum(price_per_room)
from dates d
inner join t
on d.date >= t.date_from
and d.date <= t.date_to
rextester demo: http://rextester.com/MCF52261
returns:
+-------------+
| total_price |
+-------------+
| 780 |
+-------------+
For a more detailed breakdown of the price and dates, you can swap the above select for this one:
select
fromdate = min(date)
, thrudate = max(date)
, days = count(*)
, price_per_room = avg(price_per_room)
, total = sum(price_per_room)
from dates d
inner join t
on d.date >= t.date_from
and d.date <= t.date_to
group by grouping sets ((price_per_room),())
rextester demo: http://rextester.com/NKZD1468
returns:
+------------+------------+------+----------------+-------+
| fromdate | thrudate | days | price_per_room | total |
+------------+------------+------+----------------+-------+
| 2017-09-07 | 2017-09-10 | 4 | 70 | 280 |
| 2017-09-11 | 2017-09-15 | 5 | 100 | 500 |
| 2017-09-07 | 2017-09-15 | 9 | 86 | 780 |
+------------+------------+------+----------------+-------+
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
Using UNION ALL for all four possible cases may do the job as well
SELECT SUM(x.p)
FROM
(
SELECT (DATEDIFF(DAY, #from, date_to)+1)* price p
FROM tab
WHERE date_to BETWEEN #from AND #to AND NOT date_from BETWEEN #from AND #to
UNION ALL
SELECT ISNULL(SUM((DATEDIFF(DAY, date_from, date_to)+1)* price), 0) p
FROM tab
WHERE date_to BETWEEN #from AND #to AND date_from BETWEEN #from AND #to
UNION ALL
SELECT (DATEDIFF(DAY, date_from, #to)+1)* price p
FROM tab
WHERE date_from BETWEEN #from AND #to AND NOT date_to BETWEEN #from AND #to
UNION ALL
-- in the case that the (#from, #to) interval is completely in one row interval
SELECT (DATEDIFF(DAY, #from, #to)+1)* price p
FROM tab
WHERE date_from <= #from AND date_to >= #to
)x;
sqlfiddle demo

SQL Server: Finding date given EndDate and # Days, excluding days from specific date ranges

I have a TableA in a database similar to the following:
Id | Status | Start | End
1 | Illness | 2013-04-02 | 2013-04-23
2 | Illness | 2013-05-05 | 2014-01-01
3 | Vacation | 2014-02-01 | 2014-03-01
4 | Illness | 2014-03-08 | 2014-03-09
5 | Vacation | 2014-05-05 | NULL
Imagine it's keeping track of a specific user's "Away" days. Given the following Inputs:
SomeEndDate (Date),
NumDays (Integer)
I want to find the SomeStartDate (Date) that is Numdays non-illness days from EndDate. In other words, say I am given a SomeEndDate value '2014-03-10' and a NumDays value of 60; the matching SomeStartDate would be:
2014-03-10 to 2014-03-09 = 1
2014-03-08 to 2014-01-01 = 57
2013-05-05 to 2013-05-03 = 2
So, at 60 non-illness days, we get a SomeStartDate of '2013-05-03'. IS there any easy way to accomplish this in SQL? I imagine I could loop each day, check whether or not it falls into one of the illness ranges, and increment a counter if not (exiting the loop after counter = #numdays)... but that seems wildly inefficient. Appreciate any help.
Make a Calendar table that has a list of all the dates you will ever care about.
SELECT MIN([date])
FROM (
SELECT TOP(#NumDays) [date]
FROM Calendar c
WHERE c.Date < #SomeEndDate
AND NOT EXISTS (
SELECT 1
FROM TableA a
WHERE c.Date BETWEEN a.Start AND a.END
AND Status = 'Illness'
)
ORDER BY c.Date
) t
The Calendar table method lets you also easily exclude holidays, weekends, etc.
SQL Server 2012:
Try this solution:
DECLARE #NumDays INT = 70, #SomeEndDate DATE = '2014-03-10';
SELECT
[RangeStop],
CASE
WHEN RunningTotal_NumOfDays <= #NumDays THEN [RangeStart]
WHEN RunningTotal_NumOfDays - Current_NumOfDays <= #NumDays THEN DATEADD(DAY, -(#NumDays - (RunningTotal_NumOfDays - Current_NumOfDays))+1, [RangeStop])
END AS [RangeStart]
FROM (
SELECT
y.*,
DATEDIFF(DAY, y.RangeStart, y.RangeStop) AS Current_NumOfDays,
SUM( DATEDIFF(DAY, y.RangeStart, y.RangeStop) ) OVER(ORDER BY y.RangeStart DESC) AS RunningTotal_NumOfDays
FROM (
SELECT LEAD(x.[End]) OVER(ORDER BY x.[End] DESC) AS RangeStart, -- It's previous date because of "ORDER BY x.[End] DESC"
x.[Start] AS RangeStop
FROM (
SELECT #SomeEndDate AS [Start], '9999-12-31' AS [End]
UNION ALL
SELECT x.[Start], x.[End]
FROM #MyTable AS x
WHERE x.[Status] = 'Illness'
AND x.[End] <= #SomeEndDate
) x
) y
) z
WHERE RunningTotal_NumOfDays - Current_NumOfDays <= #NumDays;
/*
Output:
RangeStop RangeStart
---------- ----------
2014-03-10 2014-03-09
2014-03-08 2014-01-01
2013-05-05 2013-05-03
*/
Note #1: LEAD(End) will return the previous End date (previous because of ORDER BY End DESC)
Note #2: DATEDIFF(DAY, RangeStart, RangeStop) computes the num. of days between current start (alias x.RangeStop) and "previous" end (alias x.RangeStar) => Current_NumOfDays
Note #3: SUM( Current_NumOfDays ) computes a running total thus: 1 + 66 + (3)
Note #4: I've used #NumOfDays = 70 (not 60)

Query to find records that were active within a range of dates

I have a database table containing the start and expire date of employee certifications. I need to query the table to get employees who were certified during a date range.
Example table:
EmpID | FromDate | ExpireDate
---------+-------------+--------------
1 | 2/1/2011 | 3/1/2015
2 | 10/1/2010 | 2/1/2013
3 | 3/1/2013 | 5/30/2013
4 | 11/1/2000 | 3/1/2012
5 | 5/6/2013 | 5/30/2017
If a user wants to find employees that were certified between 5/1/2013 and 5/30/2013 they should get back employee ids 1, 3, and 5. Note that employee id 5 was certified during the date range though the FromDate is after the start date of the query (5/1/2013).
Hope that makes sense. I'm having a hard time wrapping my head around how to write the query.
Assuming that your dates are really stored as dates, the following should work:
select t.*
from t
where FromDate <= '5/30/2013' and
EndDate >= '5/1/2013';
The logic is that FromDate is less than the end of the period and EndDate is after the beginning. That will get any overlap at all with the period.
The query will be:
declare #FromDate datetime
declare #ToDate datetime
set #FromDate = '1-May-2013'
set #ToDate = '30-May-2013'
select * from emp
where (#FromDate <= FromDate And #ToDate > FromDate And #ToDate < ExpireDate)
Or (#FromDate >= FromDate And #ToDate between FromDate and ExpireDate)
Here both the conditions are separately mentioned and added using or operator. It will help you to write down your query in more logical manner.
you have to select
SELECT EmpId FROM tableName WHERE FromDate <= endDateRange AND
ExpireDate>=startDateRange
Looks like there are 3 scenarios of this query.
Considering 5/1/2013 as st_dt
and 5/30/2013 as end_dt
Select EmpID from tablename WHERE
(st_dt > FromDate AND end_dt < ExpireDate) OR
(st_dt > FromDate AND end_dt > ExpireDate) OR
(st_dt < FromDate AND end_dt < ExpireDate);
Make certain changes in this query like table name, etc. You will get your answer for sure.

SQL command: get min date and hour from table

SQL command: get min date and hour from table
TblAzmon:
Acode(pk) | Aname | Adate | Ahour | ADcode_fk
----------------------------------------------------------------------------
1 system 1358/05/05 08:00 2
2 graphic 1389/05/05 08:00 1
3 simulation 1392/05/06 07:30 1
4 math 1389/05/05 09:00 1
I want the output date and time for the manager (ADcode) to get the smallest.
Desired output: [Where ADcode_fk='1']
Acode | Adate | Ahour
----------------------------------
2 1389/05/05 08:00
SQL command:
select Acode,Adate,Ahour from TblAzmon<br>
where Adate in (select min(Adate) from TblAzmon where ADcode_fk='1')
And Ahour in (select min(Ahour) from TblAzmon where ADcode_fk='1')
Output:---------->0 rows - NULL
Tip: All columns are of type text. Apart from the column Acode.
Please write the SQL code.
You could made it like that, using order by and top:
select top 1 *
from tblAzmon a
order by Adate, Ahour
Assuming you mean the earliest combination of date and hour, You can do this with order by and top:
select top 1 *
from tblAzmon a
order by Adate, Ahour
Your SQL doesnt work, because u select the minHour and minDate at once, and since the min(Date) doesnt have the min(Hour) you get 0 rows back.
U need to brake them apart and select them one by one like this. With that u should be able to do it on your own =)
SELECT Acode,Adate,min(Ahour)
FROM (Select Acode,min(Adate),Ahour FROM TblAzmon WHERE ADcode_fk='1') t
WHERE ADcode_fk='1'