Group by date and and check for continuous dates - sql

I have a set of data like this
date1 Price Availability ServiceID
2012-10-01 50 1 5
2012-10-02 60 1 5
2012-10-03 60 1 5
2012-10-04 60 1 5
2012-10-06 60 1 5
2012-10-07 60 0 5
2012-10-08 60 1 5
Now I want to check what is the total price, min availability and serviceid for a particular period
For example
from 2012-10-01 to 2012-10-03 results would be 170, availability 1 and serviceID 5
from 2012-10-06 to 2012-10-08 results would be 180, availability 0! and serviceID 5
from 2012-10-04 to 2012-10-06 results would be 120, availability 1! and serviceID 5 BUT there is a date missing so availability is 0!
I tried something like
select date1, sum(price), min(availability), service from #t
group by date1, price, availability, service
having count(date1) = datediff(day, #startdate, #enddate)
That does not work because if I group by date1, then count in not valid. Dont know how to get sum, min availability and check continuous dates.
EDIT
In case I want a results set to contain detailed spec. and filter out results with min(availability) = 0 or noncontinuous date
For example
from 2012-10-01 to 2012-10-03 results would be 170, availability 1 and serviceID 5
date1 Price Availability ServiceID
2012-10-01 50 1 5
2012-10-02 60 1 5
2012-10-03 60 1 5
from 2012-10-06 to 2012-10-08 results would be 180, availability 0! and serviceID 5
date1 Price Availability ServiceID
from 2012-10-04 to 2012-10-06 results would be 120, availability 1! and serviceID 5 BUT
date1 Price Availability ServiceID

Try this:
select sum(price) As TotalPrice,
service,
Case When Count(*) = DateDiff(Day, #Startdate, #EndDate) + 1
Then Min(Availability)
Else 0 End As Availability
from #T
Where Date1 >= #StartDate
And Date1 <= #endDate
group by service
By filtering the date in a where clause based on dates, and not grouping by the date, this allows you to get a count or rows which you can then match to the date diff. Note that you need to add 1 to the date diff because DateDiff from yesterday to today is only 1 but would represent 2 rows in your source data.
I should also mention that this is not actually checking for continuous dates. It's simply looking for a corresponding number of rows to match the calculated number of days.
Based on your comment and your edit, you may want to try this derived table solution.
Select T.date1,
SummaryData.TotalPrice,
SummaryData.Availability,
T.Service
From #T T
Inner Join (
select sum(price) As TotalPrice,
service,
Case When Count(*) = DateDiff(Day, #Startdate, #EndDate) + 1
Then Min(Availability)
Else 0 End As Availability
from #T
Where Date1 >= #StartDate
And Date1 <= #endDate
group by service
Having Case When Count(*) = DateDiff(Day, #Startdate, #EndDate) + 1
Then Min(Availability)
Else 0 End = 1
) As SummaryData
On T.Service = SummaryData.Service
Where Date1 >= #StartDate
And Date1 <= #endDate

If there wasn't for curveball with missing dates, query would be quite simple:
SELECT SUM(price), MIN(availability) AS Availibility, MIN(serviceID) AS serviceID FROM #t
WHERE date1 BETWEEN #Startdate AND #enddate
Howere, finding missing dates requires one additional CTE, I'll use idea from t-sql get all dates between 2 dates and it can look somehting like this:
;WITH dates AS (
SELECT #startdate AS dt
UNION ALL
SELECT DATEADD(dd, 1, dt)
FROM dates s
WHERE DATEADD(dd, 1, dt) <= #enddate
)
SELECT SUM(price),
CASE
WHEN EXISTS (SELECT * FROM dates WHERE dt NOT IN (SELECT date1 FROM #t WHERE date1 BETWEEN #Startdate AND #enddate)) THEN 0
ELSE MIN(availability)
END AS Availibility,
MIN(serviceID) AS serviceID FROM #t
WHERE date1 BETWEEN #Startdate AND #enddate

Related

Select which has matching date or latest date record

Here are two tables.
ItemInfo
Id Description
1 First Item
2 Second Item
ItemInfoHistory
Id ItemInfoId Price StartDate EndDate
1 1 45 2020-09-01 2020-09-15
2 2 55 2020-09-26 null
3 1 50 2020-09-16 null
Here is SQL query.
SELECT i.Id, Price, StartDate, EndDate
FROM Itemsinfo i
LEFT JOIN ItemInfoHistory ih ON i.id= ih.ItemsMasterId AND CONVERT(DATE, GETDATE()) >= StartDate AND ( CONVERT(DATE, GETDATE()) <= EndDate OR EndDate IS NULL)
Which gives following results, when runs the query on 9/20
Id Price StartDate EndDate
1 50 2020-09-16 NULL
2 NULL NULL NULL
For the second item, I want to get latest record from history table, as shown below.
Id Price StartDate EndDate
1 50 2020-09-16 NULL
2 55 2020-09-26 NULL
Thanks in advance.
Probably the most efficient method is two joins. Assuming the "latest" record has a NULL values for EndDate, then:
SELECT i.Id,
COALESCE(ih.Price, ih_last.Price) as Price,
COALESCE(ih.StartDate, ih_last.StartDate) as StartDate,
COALESCE(ih.EndDate, ih_last.EndDate) as EndDate
FROM Itemsinfo i LEFT JOIN
ItemInfoHistory ih
ON i.id = ih.ItemsMasterId AND
CONVERT(DATE, GETDATE()) >= StartDate AND
(CONVERT(DATE, GETDATE()) <= EndDate OR EndDate IS NULL) LEFT JOIN
ItemInfoHistory ih_last
ON i.id = ih_last.ItemsMasterId AND
ih_last.EndDate IS NULL;
Actually, the middle join doesn't need to check for NULL, so that could be removed.

Calculate Experience without overlapping

I'm trying to come up with the correct query to calculate the employment experience time but, I can't get it right. Here's the data I have:
Case 1:
EmployeeID PoisitionID StartDate EndDate
1 15 5/22/2017 5/22/2018
1 17 7/14/2018 8/10/2019
Case 2:
EmployeeID PositonID StartDate EndDate
1 15 5/22/2017 8/10/2019
1 17 3/8/2019 8/10/2019
Case 3:
EmployeeID PositonID StartDate EndDate
1 15 5/22/2017 NULL
1 17 3/8/2019 NULL
In the first case, my expected result in months would be: 27 months for both positions.
In the second case, my expected result in months would be:27 months for positonid 15 and 0 months for positionid 17 because positionid 17 falls during the date range of the first position and therefore, the employee will not be awarded with any years of experience.
In the third case, my expected result in months would be:30 months using today's date as an enddate for positonid 15 and 0 months for positionid 17 because positionid 17 falls during the date range of the first position and therefore, the employee will not be awarded with any years of experience.
You don't have any gaps, so I think this does what you want:
select employeeid,
datediff(month, min(startdate), coalesce(max(enddate), getdate())) as months
from t
group by employeeid;
This is what I have:
Your table 1:
select 1 as EmployeeID , 15 as PositonID , cast('5/22/2017' as date) as StartDate, cast('5/22/2018' as date) as EndDate into t2
union select 1, 17, '7/14/2018', '8/10/2019'
And the query to get the result
with a as
(
select EmployeeID, isnull(StartDate, cast(getdate() as date)) as sedate from t2
union
select EmployeeID, isnull(EndDate, cast(getdate() as date)) from t2
)
select a1.*, a2.sedate, case when datediff(month,a1.sedate, a2.sedate)< 0 then 0 else isnull(datediff(month,a1.sedate, a2.sedate), 0) end as months from a a1 left join a a2 on a1.EmployeeID = a2.EmployeeID and a1.sedate < a2.sedate
and not exists(select 1 from a a3 where a3.EmployeeID = a2.EmployeeID and a3.sedate > a1.sedate and a3.sedate < a2.sedate )
I changed the table to the values of Case2 and Case 3 and it seemed to work.
Let us know if that helps

Multiple counts and merge columns

I current have a query that grabs the number of parts made per hour between two dates:
DECLARE #StartDate datetime
DECLARE #EndDate datetime
SET #StartDate = '10/10/2018'
SET #EndDate = '11/11/2018'
SELECT
CONVERT(VARCHAR(10), CAST(presstimes AS DATE), 111) AS ForDate,
DATEPART(HOUR, presstimes) AS OnHour,
COUNT(*) AS Totals
FROM
partmasterlist
WHERE
((presstimes >= #StartDate AND presstimes < dateAdd(d, 1, #EndDate))
AND (((presstimes IS NOT NULL))))
GROUP BY
CONVERT(VARCHAR(10), CAST(presstimes AS DATE), 111),
DATEPART(HOUR, presstimes)
ORDER BY
CONVERT(VARCHAR(10), CAST(presstimes AS DATE), 111) ASC;
Output:
Date Hour QTY
---------------------
2018/11/06 11 16
2018/11/06 12 20
2018/11/06 13 29
2018/11/06 14 26
Now I need to add another qty column to count where "trimmingtimes" is set.
I can't figure out how to full join the date and hour columns (e.g. presstimes might have 20qty for Hour 2, but trimmingtimes is NULL for Hour 2);
Input:
ID presstimes trimmingtimes
-----------------------------------------------------------------
1 2018-10-10 01:15:23.000 2018-10-10 01:15:23.000
2 2018-10-10 01:15:23.000 NULL
3 2018-10-10 02:15:23.000 NULL
4 NULL 2018-10-10 03:15:23.000
Output:
Date hour Press QTY T QTY
------------------------------------
10/10/18 1 2 1
10/10/18 2 1 0
10/10/18 3 0 1
I suspect you want something like this:
select convert(date, v.dt) as date,
datepart(hour, v.dt) as hour,
sum(ispress) as num_press,
sum(istrim) as num_trim
from partmasterlist pml cross apply
(values (pml.presstime, 1, 0), (pml.trimmingtime, 0, 1)
) v(dt, ispress, istrim)
group by convert(date, v.dt), datepart(hour, v.dt)
order by convert(date, v.dt), datepart(hour, v.dt);
You can add a where clause for a particular range.

SQL how to count census points occurring between date records

I’m using MS-SQL-2008 R2 trying to write a script that calculates the Number of Hospital Beds occupied on any given day, at 2 census points: midnight, and 09:00.
I’m working from a data set of patient Ward Stays. Basically, each row in the table is a record of an individual patient's stay on a single ward, and records the date/time the patient is admitted onto the ward, and the date/time the patient leaves the ward.
A sample of this table is below:
Ward_Stay_Primary_Key | Ward_Start_Date_Time | Ward_End_Date_Time
1 | 2017-09-03 15:04:00.000 | 2017-09-27 16:55:00.000
2 | 2017-09-04 18:08:00.000 | 2017-09-06 18:00:00.000
3 | 2017-09-04 13:00:00.000 | 2017-09-04 22:00:00.000
4 | 2017-09-04 20:54:00.000 | 2017-09-08 14:30:00.000
5 | 2017-09-04 20:52:00.000 | 2017-09-13 11:50:00.000
6 | 2017-09-05 13:32:00.000 | 2017-09-11 14:49:00.000
7 | 2017-09-05 13:17:00.000 | 2017-09-12 21:00:00.000
8 | 2017-09-05 23:11:00.000 | 2017-09-06 17:38:00.000
9 | 2017-09-05 11:35:00.000 | 2017-09-14 16:12:00.000
10 | 2017-09-05 14:05:00.000 | 2017-09-11 16:30:00.000
The key thing to note here is that a patient’s Ward Stay can span any length of time, from a few hours to many days.
The following code enables me to calculate the number of beds at both census points for any given day, by specifying the date in the case statement:
SELECT
'05/09/2017' [Date]
,SUM(case when Ward_Start_Date_Time <= '05/09/2017 00:00:00.000' AND (Ward_End_Date_Time >= '05/09/2017 00:00:00.000' OR Ward_End_Date_Time IS NULL)then 1 else 0 end)[No. Beds Occupied at 00:00]
,SUM(case when Ward_Start_Date_Time <= '05/09/2017 09:00:00.000' AND (Ward_End_Date_Time >= '05/09/2017 09:00:00.000' OR Ward_End_Date_Time IS NULL)then 1 else 0 end)[No. Beds Occupied at 09:00]
FROM
WardStaysTable
And, based on the sample 10 records above, generates this output:
Date | No. Beds Occupied at 00:00 | No. Beds Occupied at 09:00
05/09/2017 | 4 | 4
To perform this for any number of days is obviously onerous, so what I’m looking to create is a query where I can specify a start/end date parameter (e.g. 1st-5th Sept), and for the query to then evaluate the Ward_Start_Date_Time and Ward_End_Date_Time variables for each record, and – grouping by the dates defined in the date parameter – count each time the 00:00:00.000 and 09:00:00.000 census points fall between these 2 variables, to give an output something along these lines (based on the above 10 records):
Date | No. Beds Occupied at 00:00 | No. Beds Occupied at 09:00
01/09/2017 | 0 | 0
02/09/2017 | 0 | 0
03/09/2017 | 0 | 0
04/09/2017 | 1 | 1
05/09/2017 | 4 | 4
I’ve approached this (perhaps naively) thinking that if I use a cte to create a table of dates (defined by the input parameters), along with associated midnight and 9am census date/time points, then I could use these variables to group and evaluate the dataset.
So, this code generates the grouping dates and census date/time points:
DECLARE
#StartDate DATE = '01/09/2017'
,#EndDate DATE = '05/09/2017'
,#0900 INT = 540
SELECT
DATEADD(DAY, nbr - 1, #StartDate) [Date]
,CONVERT(DATETIME,(DATEADD(DAY, nbr - 1, #StartDate))) [MidnightDate]
,DATEADD(mi, #0900,(CONVERT(DATETIME,(DATEADD(DAY, nbr - 1, #StartDate))))) [0900Date]
FROM
(
SELECT
ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS nbr
FROM sys.columns c
) nbrs
WHERE nbr - 1 <= DATEDIFF(DAY, #StartDate, #EndDate)
The stumbling block I’ve hit is how to join the cte to the WardStays dataset, because there’s no appropriate key… I’ve tried a few iterations of using a subquery to make this work, but either I’m taking the wrong approach or I’m getting my syntax in a mess.
In simple terms, the logic I’m trying to create to get the output is something like:
SELECT
[Date]
,SUM (case when WST.Ward_Start_Date_Time <= [MidnightDate] AND (WST.Ward_End_Date_Time >= [MidnightDate] OR WST.Ward_End_Date_Time IS NULL then 1 else 0 end) [No. Beds Occupied at 00:00]
,SUM (case when WST.Ward_Start_Date_Time <= [0900Date] AND (WST.Ward_End_Date_Time >= [0900Date] OR WST.Ward_End_Date_Time IS NULL then 1 else 0 end) [No. Beds Occupied at 09:00]
FROM WardStaysTable WST
GROUP BY [Date]
Is the above somehow possible, or am I barking up the wrong tree and need to take a different approach altogether? Appreciate any advice.
I would expect something like this:
WITH dates as (
SELECT CAST(#StartDate as DATETIME) as dte
UNION ALL
SELECT DATEADD(DAY, 1, dte)
FROM dates
WHERE dte < #EndDate
)
SELECT dates.dte [Date],
SUM(CASE WHEN Ward_Start_Date_Time <= dte AND
Ward_END_Date_Time >= dte
THEN 1 ELSE 0
END) as num_beds_0000,
SUM(CASE WHEN Ward_Start_Date_Time <= dte + CAST('09:00' as DATETIME) AND
Ward_END_Date_Time >= dte + CAST('09:00' as DATETIME)
THEN 1 ELSE 0
END) as num_beds_0900
FROM dates LEFT JOIN
WardStaysTable wt
ON wt.Ward_Start_Date_Time <= DATEADD(day, 1, dates.dte) AND
wt.Ward_END_Date_Time >= dates.dte
GROUP BY dates.dte
ORDER BY dates.dte;
The cte is just creating the list of dates.
What a cool exercise. Here is what I came up with:
CREATE TABLE #tmp (ID int, StartDte datetime, EndDte datetime)
INSERT INTO #tmp values(1,'2017-09-03 15:04:00.000','2017-09-27 06:55:00.000')
INSERT INTO #tmp values(2,'2017-09-04 08:08:00.000','2017-09-06 18:00:00.000')
INSERT INTO #tmp values(3,'2017-09-04 13:00:00.000','2017-09-04 22:00:00.000')
INSERT INTO #tmp values(4,'2017-09-04 20:54:00.000','2017-09-08 14:30:00.000')
INSERT INTO #tmp values(5,'2017-09-04 20:52:00.000','2017-09-13 11:50:00.000')
INSERT INTO #tmp values(6,'2017-09-05 13:32:00.000','2017-09-11 14:49:00.000')
INSERT INTO #tmp values(7,'2017-09-05 13:17:00.000','2017-09-12 21:00:00.000')
INSERT INTO #tmp values(8,'2017-09-05 23:11:00.000','2017-09-06 07:38:00.000')
INSERT INTO #tmp values(9,'2017-09-05 11:35:00.000','2017-09-14 16:12:00.000')
INSERT INTO #tmp values(10,'2017-09-05 14:05:00.000','2017-09-11 16:30:00.000')
DECLARE
#StartDate DATE = '09/01/2017'
,#EndDate DATE = '10/01/2017'
, #nHours INT = 9
;WITH d(OrderDate) AS
(
SELECT DATEADD(DAY, n-1, #StartDate)
FROM (SELECT TOP (DATEDIFF(DAY, #StartDate, #EndDate) + 1)
ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_objects) AS x(n)
)
, CTE AS(
select OrderDate, t2.*
from #tmp t2
cross apply(select orderdate from d ) d
where StartDte >= #StartDate and EndDte <= #EndDate)
select OrderDate,
SUM(CASE WHEN OrderDate >= StartDte and OrderDate <= EndDte THEN 1 ELSE 0 END) [No. Beds Occupied at 00:00],
SUM(CASE WHEN StartDTE <= DateAdd(hour,#nHours,CAST(OrderDate as datetime)) and DateAdd(hour,#nHours,CAST(OrderDate as datetime)) <= EndDte THEN 1 ELSE 0 END) [No. Beds Occupied at 09:00]
from CTE
GROUP BY OrderDate
This should allow you to check for any hour of the day using the #nHours parameter if you so choose. If you only want to see records that actually fall within your date range then you can filter the cross apply on start and end dates.

SQL Date Range Queries

I am working with a TABLE with date ranges bound by two fields (Start and End):
ID | START | END
1 2010-01-01 2010-07-01
2 2011-01-01 2011-07-01
3 2012-01-01 2013-07-01
4 2013-01-01 2013-07-01
5 2009-01-01 2013-07-01
I could get a rough estimate of the number of 'months' represented by all ranges by a query like:
SELECT SUM(DATEDIFF(dy, Start, End) / 30) as Total_Months
FROM TABLE
What I would rather like to do is to query how many months (or days) are represented by all ranges within a given period of time.
So If I asked how many months are represented from the time period [2013-01-01 - 2013-07-01] in the example above, it would say 18 (6 from each of rows 3, 4 and 5).
What is the best way to accomplish this?
UPDATED: You can do it like this
SELECT SUM(DATEDIFF(dy,
CASE WHEN '2013-01-01' > Start THEN '2013-01-01' ELSE Start END,
CASE WHEN '2013-07-01' < [End] THEN '2013-07-01' ELSE [End] END) / 30) Total_Months
FROM Table1
WHERE '2013-01-01' BETWEEN Start AND [End]
AND '2013-07-01' BETWEEN Start AND [End]
Output:
| TOTAL_MONTHS |
----------------
| 18 |
Here is SQLFiddle demo
Something like this?
SELECT SUM(DATEDIFF(dy, Start, End) / 30) as Total_Months
WHERE Start >= '2013-01-01' AND End <= '2013-07-01'
FROM TABLE
Or to make it parameterized:
SELECT SUM(DATEDIFF(dy, Start, End) / 30) as Total_Months
WHERE Start >= #startDate AND End <= #endDate
FROM TABLE