I have a requirement to delete rows from a table with three consecutive days (exclude the days if weekends come in between)
CREATE TABLE [dbo].[Test]
(
[Scanid] [bigint] NULL,
[Employeeid] [int] NULL,
[Datescanned] [datetime] NULL
)
INSERT INTO [dbo].[Test]([Scanid], [Employeeid], [Datescanned])
VALUES (108639, 3820, '2016-04-28 17:12:33.000'),
(108639, 3820, '2016-04-28 18:05:46.000'),
(108639, 3820, '2016-04-28 20:58:36.000'),
(999999, 3820, '2016-04-29 10:08:00.000'),
(999999, 3820, '2016-04-29 10:12:10.000'),
(777777, 3820, '2016-05-02 10:12:00.000'),
(111111, 3820, '2016-04-04 10:12:00.000'),
(33333, 3820, '2016-04-11 17:23:00.000'),
(987623, 3820, '2016-04-18 11:12:00.000'),
(1234, 3820, '2016-05-26 10:00:00.000'),
(5678, 3820, '2016-05-27 10:00:00.000'),
(8920, 3820, '2016-05-31 10:00:00.000')
Output:
Scanid Employeeid Datescanned
----------------------------------------
108639 3820 2016-04-28 17:12:33.000
108639 3820 2016-04-28 18:05:46.000
108639 3820 2016-04-28 20:58:36.000
999999 3820 2016-04-29 10:08:00.000
999999 3820 2016-04-29 10:12:10.000
777777 3820 2016-05-02 10:12:00.000
111111 3820 2016-04-04 10:12:00.000
33333 3820 2016-04-11 17:23:00.000
987623 3820 2016-04-18 11:12:00.000
1234 3820 2016-05-26 10:00:00.000
5678 3820 2016-05-27 10:00:00.000
8920 3820 2016-05-31 10:00:00.000
We can take date only from datescanned field and then in the above example we should delete rows with 3 consecutive date from '2016-04-28' to '2016-05-02' (2016-04-30 and 31 are weekends so we can ignore) and also delete rows with 3 consecutive date from '2016-05-26' to '2016-05-31' (2016-05-29 and 30th are weekends so we can ignore). so only results should display row with days 2016-04-04,2016-04-11,2016-04-18 which don't have 3 consecutive days before or after them.
Here is the exact output that you want..
I could see one mistake in your question,[ie. delete rows with 3 consecutive date from '2016-05-26' to '2016-05-31' (2016-05-29 and 30th are weekends so we can ignore)'],those weekends days are not correct..and the correct dates are 2016-05-28 and 2016-05-29.
DROP TABLE [TestDates]
GO
CREATE TABLE [dbo].[TestDates](
[Scanid] [bigint] NULL,
[Employeeid] [int] NULL,
[Datescanned] [datetime] NULL
)
INSERT INTO [dbo].[TestDates] ([Scanid] ,[Employeeid],[Datescanned])
VALUES (108639,3820,'2016-04-28 17:12:33.000'),(108639,3820,'2016-04-28 18:05:46.000'),
(108639,3820,'2016-04-28 20:58:36.000'),(999999,3820,'2016-04-29 10:08:00.000'),
(999999,3820,'2016-04-29 10:12:10.000'),(777777,3820,'2016-05-02 10:12:00.000'),
(111111,3820,'2016-04-04 10:12:00.000'),(33333,3820,'2016-04-11 17:23:00.000'),
(987623,3820,'2016-04-18 11:12:00.000'),(1234,3820,'2016-05-26 10:00:00.000'),
(5678,3820,'2016-05-27 10:00:00.00'), (8920, 3820, '2016-05-30 10:00:00.000')
GO
DROP TABLE #t
GO
SELECT DISTINCT Employeeid,CONVERT(date,Datescanned) Datescanned INTO #T
FROM [TestDates]
GO
;WITH cte_cnt
AS
(
SELECT Employeeid, MIN(Datescanned) AS FROM_DATE
,MAX(Datescanned) AS TO_DATE
, COUNT('A') AS DayDiff
FROM (
SELECT Employeeid,Datescanned,
ROW_NUMBER() OVER(ORDER BY Datescanned) AS ROW_NUMBER,
DATEDIFF(D, ROW_NUMBER() OVER(ORDER BY Datescanned)
,CASE WHEN DATENAME(dw, cast (Datescanned as datetime)-1) = 'Sunday' THEN DATEADD(DAY, -2, Datescanned) ELSE Datescanned END) AS Diff
FROM #t) AS dt
GROUP BY Employeeid, Diff )
DELETE t
--SELECT *
FROM cte_cnt c
JOIN [TestDates] t
ON c.Employeeid=t.Employeeid
WHERE CAST(t.Datescanned as DATE) BETWEEN c.FROM_DATE AND c.TO_DATE and c.DayDiff=3
GO
SELECT *
FROM [TestDates]
GO
A solution which doesn't account for holidays would be
SELECT
t.*
FROM (SELECT DISTINCT
CAST(t1.datescanned AS date) first_date,
CAST(t2.datescanned AS date) second_date,
CAST(t3.datescanned AS date) third_date
FROM test t1
JOIN test t2 --add a join condition for employeeid as well
ON DATEDIFF(dd, CAST(t1.datescanned AS date), CAST(t2.datescanned AS date)) = 1
OR (DATEPART(WEEKDAY, CAST(t2.datescanned AS date)) = 2
AND DATEDIFF(dd, CAST(t1.datescanned AS date), CAST(t2.datescanned AS date)) = 3)
JOIN test t3 --add a join condition for employeeid as well
ON DATEDIFF(dd, CAST(t2.datescanned AS date), CAST(t3.datescanned AS date)) = 1
OR (DATEPART(WEEKDAY, CAST(t3.datescanned AS date)) = 2
AND DATEDIFF(dd, CAST(t2.datescanned AS date), CAST(t3.datescanned AS date)) = 3)
) x
JOIN test t
ON CAST(t.datescanned AS date) = x.first_date
OR CAST(t.datescanned AS date) = x.second_date
OR CAST(t.datescanned AS date) = x.third_date
Self join the table twice, each time on
a date difference of 1 or
3 when a weekend occurs and check if the weekday is Monday (weekday=2)
Sample demo
The result gives the rows which need to be deleted. But the caveat here is that, this would give you more than 3 consecutive days if there are no gaps. In that case you need to explain if you want to stop deleting at the 3rd day.
The previous scripts are good. I would make an improvement and for weekend and Holiday check, add a function and call it in the select statement.
Here is a simple function that you can use (I am assuming that you have a table called Holiday, which holds all holiday dates per State )
Create FUNCTION [dbo].[IsHolidayOrWeekend]
(
#date DateTime,
#stateId int
)
RETURNS Bit
AS
BEGIN
declare #dayOfWeek VARCHAR(9);
set #dayOfWeek = DATEName(DW, #date);
IF(#dayOfWeek = 'Saturday' OR #dayOfWeek = 'Sunday')
RETURN 1;
ELSE
begin
set #date = cast(#date as date) -- Remove the time portion
RETURN IsNull((SELECT 1 from Holiday where StateId = #provinceId and HolidayDate = #date ), 0)
end;
END
Perhaps this?:
delete from Test
where not exists (
select 1
from Test t2
where cast(t2.Datescanned as date)
between
dateadd(day,
case datepart(dayofweek, cast(Test.Datescanned as date))
when 1 then -4 when 2 then -4
else -2
end,
cast(Test.Datescanned as date)
)
and
dateadd(day,
case datepart(dayofweek, cast(Test.Datescanned as date))
when 4 then 4 when 5 then 4
else 2
end,
cast(Test.Datescanned as date)
)
)
I have an added requirement to igonre holidays also along with weekend. I cretaed Test_calendar table removing all holidays and weekends and assigned row number to active days. Then this is the code I used. It working but may not be the fast if we have millions of rows. For me data is small so it will get the work done. Please let me know if you can simplify deletion process to make it fast.
SELECT distinct scanid as [badgeid] , Employeeid,CONVERT(date, Datescanned) as Datescanned,RN
into #test1
FROM [dbo].[Test] a
inner join Test_Calendar b
on CONVERT(date, a.Datescanned)=b.Cal_date
order by Datescanned asc
declare #min int
declare #max int
declare #i int
select #min=MIN(rn) from #test1
select #max=Max(rn) from #test1
while(#min<#max)
begin
select #i=COUNT(*) from #test1 where rn in(#min ,#min+1,#min+2)
if(#i=3)
select * from #test1 where rn in(#min ,#min+1,#min+2)
set #min=#min+1
end
Related
So I've made the following code in order to remove weekends, however now I need to remove the holidays from the datediff part of the code:
Select
ii.initialDateFixed
,hh.holdDateFixed
,(datediff(dd,ii.initialDatefixed,hh.holdDatefixed) + 1)
-(DATEDIFF(wk,ii.initialDatefixed,hh.holdDatefixed)*2)
-(case when DATENAME(dw, ii.initialDatefixed) = 'Saturday' then 1 else 0 end)
-(case when DATENAME(dw, ii.initialDatefixed) = 'Sunday' then 1 else 0 end)
from tempTable
I have the following table on my db called dbo.holidays
holidayDate
description
2022-09-05 00:00:00.000
Labor Day
2022-11-24 00:00:00.000
Thanksgiving
Is there a way that I can use this table to remove the holidays in the datediff?
My main table called tempTable looks something like this:
ID
start date
end date
sadasfdas234134
2022-09-03 14:14:32.0000000
2022-09-16 14:14:32.0000000
dsf3245
2022-07-12 06:32:12.0000000
2022-07-19 12:12:24.0000000
Sure. You can use a correlated subquery for this. I'm not sure how the query you provided is running (i.e. what do the table aliases ii and hh point to?); I'm chalking that up to an incomplete redaction of your actual query. But here's a proof-of-concept that should get you what you need:
use tempdb;
go
drop table if exists #t;
create table #t (
ID varchar(40) not null,
start_date datetime2(0),
end_date datetime2(0)
);
insert into #t values
('sadasfdas234134', '2022-09-03 14:14:32', '2022-09-16 14:14:32'),
('dsf3245', '2022-07-12 06:32:12', '2022-07-19 12:12:24');
drop table if exists #holidays;
create table #holidays (
holidayDate date,
[description] varchar(100)
)
insert into #holidays values
('2022-09-05', 'Labor Day'),
('2022-11-24', 'Thanksgiving');
select ID,
[allDays] = datediff(day, start_date, end_date),
[nonHolidays] = datediff(day, start_date, end_date) -
(
select count(*)
from #holidays
where holidayDate between t.start_date and t.end_date
)
from #t as t;
I want to find the total number of days in a date range that overlap a table of date ranges.
For example, I have 7 days between 2 dates in the table below. I want to find the days between between them that also fall this date range: 2019-08-01 to 2019-08-30.
It should return 1 day.
This is the data source query:
SELECT LeaveId, UserId, StartDate, EndDate, Days
FROM TblLeaveRequest
WHERE UserId = 218
LeaveID UserID StartDate EndDate Days
----------- ----------- ----------------------- ----------------------- -----------
22484 218 2019-07-26 00:00:00.000 2019-08-01 00:00:00.000 7
I believe this might help you:
--create the table
SELECT
22484 LeaveID,
218 UserID,
CONVERT(DATETIME,'7/26/2019') StartDate,
CONVERT(DATETIME,'8/1/2019') EndDate,
7 Days
INTO #TblLeaveRequest
--Range Paramters
DECLARE #StartRange AS DATETIME = '8/1/2019'
DECLARE #EndRange AS DATETIME = '8/30/2019'
--Find sum of days between StartDate and EndDate that intersect the range paramters
--for UserId=218
SELECT
SUM(
DATEDIFF(
DAY
,CASE WHEN #StartRange < StartDate THEN StartDate ELSE #StartRange END
,DATEADD(DAY, 1, CASE WHEN #EndRange > EndDate THEN EndDate ELSE #EndRange END)
)
) TotalDays
from #TblLeaveRequest
where UserId=218
This assumes that no start dates are greater than end dates. It also assumes that the range parameters always intersect some portion of the range in the table.
If the parameter ranges might not intersect then you'll have to eliminate those cases by excluding negative days:
SELECT SUM( CASE WHEN Days < 0 THEN 0 ELSE Days END ) TotalDays
FROM
(
SELECT
DATEDIFF(
DAY
,CASE WHEN #StartRange < StartDate THEN StartDate ELSE #StartRange END
,DATEADD(DAY, 1, CASE WHEN #EndRange > EndDate THEN EndDate ELSE #EndRange END)
) Days
from #TblLeaveRequest
where UserId=218
) TotalDays
if I understand your problem correctly, you have two ranges of dates and you are looking for the number of days in the intersection.
Considering a Gant chart:
Start_1 .................... End_1
Start_2 .......................End_2
If you can create a table structure like
LeaveID UserID StartDate_1 EndDate_1 StartDate_2 EndDate_2
----------- ----------- ---------- --------- ---------- ---------
22484 218 2019-07-26 2019-08-01 2019-08-01 2019-08-30
You can determine the number of days by
select
leaveID
, UserID
,case
when StartDate_2 <= EndDate_1 then datediff(day,StartDate_2,EndDate_1) + 1
else 0
end as delta_days_intersection
from table
I hope that helps
You can use a numbers (Tally) table to count the number of days:
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE LeaveRequest
(
LeaveId Int,
UserId Int,
StartDate Date,
EndDate Date,
Days Int
)
Insert Into LeaveRequest
VALUES
(22484, 218, '2019-07-26','2019-08-01', 7)
Query 1:
DECLARE #StartDate Date = '2019-08-01'
DECLARE #EndDate Date = '2019-08-30'
;WITH Tally
AS
(
SELECT ROW_NUMBER() OVER (ORdER By Numbers.Num) AS Num
FROM
(
Values(1),(2),(3),(4),(5),(6),(7),(8),(9)
)Numbers(Num)
Cross APPLY
(
Values(1),(2),(3),(4),(5),(6),(7),(8),(9)
)Numbers2(Num2)
)
SELECT COUNT(DATEADD(d, Num -1, StartDate)) As NumberOfDays
FROM LeaveRequest
CROSS APPLY Tally
WHERE DATEADD(d, Num -1, StartDate) <= EndDate AND
DATEADD(d, Num -1, StartDate) >= #StartDate AND
DATEADD(d, Num -1, StartDate) <= #EndDate
Results:
| NumberOfDays |
|--------------|
| 1 |
CREATE TABLE #LeaveRequest
(
LeaveId Int,
UserId Int,
StartDate Date,
EndDate Date,
Days Int
)
Insert Into #LeaveRequest
VALUES
(22484, 218, '2019-07-26','2019-08-01', 7)
Declare #FromDate datetime='2019-08-01'
declare #ToDate datetime='2019-08-30'
;With CTE as
(
select top (DATEDIFF(day,#FromDate,#ToDate)+1)
DATEADD(day, ROW_NUMBER()over(order by (select null))-1,#FromDate) DT
from sys.objects
)
select * from #LeaveRequest LR
cross apply(select count(*)IntersectingDays
from CTE c where dt between lr.StartDate and lr.EndDate)ca
--or
--select lr.*,c.*
from CTE c
--cross apply
(select lr.* from #LeaveRequest LR
where c.DT between lr.StartDate and lr.EndDate)lr
drop table #LeaveRequest
Better create one Calendar table which will always help you in other queries also.
create table CalendarDate(Dates DateTime primary key)
insert into CalendarDate WITH (TABLOCK) (Dates)
select top (1000000)
DATEADD(day, ROW_NUMBER()over(order by (select null))-1,'1970-01-01') DT
from sys.objects
Then inside CTE write this,
select top (DATEDIFF(day,#FromDate,#ToDate)+1) Dates from CalendarDate
where Dates between #FromDate and #ToDate
DECLARE #FromDate datetime = '2019-08-01'
DECLARE #ToDate datetime = '2019-08-30'
SELECT
IIF(#FromDate <= EndDate AND #ToDate >= StartDate,
DATEDIFF(day,
IIF(StartDate > #FromDate, StartDate, #FromDate),
IIF(EndDate < #ToDate, EndDate, #ToDate)
),
0) AS overlapping_days
FROM TblLeaveRequest;
If i have Vacation table with the following structure :
emp_num start_date end_date
234 8-2-2015 8-5-2015
234 6-28-2015 7-1-2015
234 8-29-2015 9-2-2015
115 6-7-2015 6-7-2015
115 8-7-2015 8-10-2015
considering date format is: m/dd/yyyy
How could i get the summation of vacations for every employee during specific month .
Say i want to get the vacations in 8Aug-2015
I want the result like this
emp_num sum
234 7
115 4
7 = all days between 8-2-2015 and 8-5-2015 plus all days between 8-29-2015 AND 8-31-2015 the end of the month
i hope this will help you
declare #temp table
(emp_num int, startdate date, enddate date)
insert into #temp values (234,'8-2-2015','8-5-2015')
insert into #temp values (234,'6-28-2015','7-1-2015')
insert into #temp values (234,'8-29-2015','9-2-2015')
insert into #temp values (115,'6-7-2015','6-7-2015')
insert into #temp values (115,'8-7-2015','8-10-2015')
-- i am passing 8 as month number in your case is August
select emp_num,
SUM(
DATEDIFF (DAY , startdate,
case when MONTH(enddate) = 8
then enddate
else DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,startdate)+1,0))--end date of month
end
)+1) AS Vacation from #temp
where (month(startdate) = 8 OR month(enddate) = 8) AND (Year(enddate)=2015 AND Year(enddate)=2015)
group by emp_num
UPDATE after valid comment: This will fail with these dates: 2015-07-01, 2015-09-30 –#t-clausen.dk
i was assumed OP wants for month only which he will pass
declare #temp table
(emp_num int, startdate date, enddate date)
insert into #temp values (234,'8-2-2015','8-5-2015')
insert into #temp values (234,'6-28-2015','7-1-2015')
insert into #temp values (234,'8-29-2015','9-2-2015')
insert into #temp values (115,'6-7-2015','6-7-2015')
insert into #temp values (115,'8-7-2015','8-10-2015')
insert into #temp values (116,'07-01-2015','9-30-2015')
select emp_num,
SUM(
DATEDIFF (DAY , startdate,
case when MONTH(enddate) = 8
then enddate
else DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,startdate)+1,0))
end
)+1) AS Vacation from #temp
where (Year(enddate)=2015 AND Year(enddate)=2015)
AND 8 between MONTH(startdate) AND MONTH(enddate)
group by emp_num
This will work for sqlserver 2012+
DECLARE #t table
(emp_num int, start_date date, end_date date)
INSERT #t values
( 234, '8-2-2015' , '8-5-2015'),
( 234, '6-28-2015', '7-1-2015'),
( 234, '8-29-2015', '9-2-2015'),
( 115, '6-7-2015' , '6-7-2015'),
( 115, '8-7-2015' , '8-10-2015')
DECLARE #date date = '2015-08-01'
SELECT
emp_num,
SUM(DATEDIFF(day,
CASE WHEN #date > start_date THEN #date ELSE start_date END,
CASE WHEN EOMONTH(#date) < end_date
THEN EOMONTH(#date)
ELSE end_date END)+1) [sum]
FROM #t
WHERE
start_date <= EOMONTH(#date)
and end_date >= #date
GROUP BY emp_num
Using a Tally Table:
SQL Fiddle
DECLARE #month INT,
#year INT
SELECT #month = 8, #year = 2015
--SELECT
-- DATEADD(MONTH, #month - 1, DATEADD(YEAR, #year - 1900, 0)) AS start_day,
-- DATEADD(MONTH, #month, DATEADD(YEAR, #year - 1900, 0)) AS end_d
;WITH CteVacation AS(
SELECT
emp_num,
start_date = CONVERT(DATE, start_date, 101),
end_date = CONVERT(DATE, end_date, 101)
FROM vacation
)
,E1(N) AS(
SELECT * FROM(VALUES
(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
)t(N)
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b),
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b),
Tally(N) AS(
SELECT TOP(SELECT MAX(DATEDIFF(DAY, start_date, end_date)) FROM vacation)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM E4
)
SELECT
v.emp_num,
COUNT(*)
FROM CteVacation v
CROSS JOIN Tally t
WHERE
DATEADD(DAY, t.N - 1, v.start_date) <= v.end_date
AND DATEADD(DAY, t.N - 1, v.start_date) >= DATEADD(MONTH, #month - 1, DATEADD(YEAR, #year - 1900, 0))
AND DATEADD(DAY, t.N - 1, v.start_date) < DATEADD(MONTH, #month, DATEADD(YEAR, #year - 1900, 0))
GROUP BY v.emp_num
First, you want to use the correct data type to ease your calculation. In my solution, I used a CTE to format your data type. Then build a tally table from 1 up to the max duration of the all the vacations. Using that tally table, do a CROSS JOIN on the vacation table to generate all vacation dates from its start_date up to end_date.
After that, add a WHERE clause to filter dates that falls on the passed month-year parameter.
Here, #month and #year is declared as INT. What you want is to get all dates from the first day of the month-year up to its last day. The formula for first day of the month is:
DATEADD(MONTH, #month - 1, DATEADD(YEAR, #year - 1900, 0))
And for the last day of the month, add one month to the above and just use <:
DATEADD(MONTH, #month, DATEADD(YEAR, #year - 1900, 0))
Some common date routines.
More explanation on tally table.
Select(emp_name,start_date,end_date) AS sum_day from table_Name Group by emp_num,start_date,end_date
Try this
with cte(
Select emp_num,DATEDIFF(day,start_date,end_date) AS sum_day from table_Name
Group by emp_num,start_date,end_date
)
Select emp_num,sum(sum_day) as sum_day from cte group by emp_num
I have a table which contains following columns
userid,
game,
gameStarttime datetime,
gameEndtime datetime,
startdate datetime,
currentdate datetime
I can retrieve all the playing times but I want to count the total playing time per DAY and 0 or null if game not played on a specific day.
Take a look at DATEDIFF to do the time calculations. Your requirements are not very clear, but it should work for whatever you're looking to do.
Your end result would probably look something like this:
SELECT
userid,
game,
DATEDIFF(SS, gameStarttime, gameEndtime) AS [TotalSeconds]
FROM [source]
GROUP BY
userid,
game
In the example query above, the SS counts the seconds between the 2 dates (assuming both are not null). If you need just minutes, then MI will provide the total minutes. However, I imagine total seconds is best so that you can convert to whatever unit of measure you need accurate, such as hours that might be "1.23" or something like that.
Again, most of this is speculation based on assumptions and what you seem to be looking for. Hope that helps.
MSDN Docs for DATEDIFF: https://msdn.microsoft.com/en-us/library/ms189794.aspx
You may also look up DATEPART if you want the minutes and seconds separately.
UPDATED BASED ON FEEDBACK
The query below breaks out the hour breakdowns by day, splits time across multiple days, and shows "0" for days where no games are played. Also, for your output, I have to assume you have a separate table of users (so you can show users who have no time in your date range).
-- Define start date
DECLARE #BeginDate DATE = '4/21/2015'
-- Create sample data
DECLARE #Usage TABLE (
userid int,
game nvarchar(50),
gameStartTime datetime,
gameEndTime datetime
)
DECLARE #Users TABLE (
userid int
)
INSERT #Users VALUES (1)
INSERT #Usage VALUES
(1, 'sample', '4/25/2015 10pm', '4/26/2015 2:30am'),
(1, 'sample', '4/22/2015 4pm', '4/22/2015 4:30pm')
-- Generate list of days in range
DECLARE #DayCount INT = DATEDIFF(DD, #BeginDate, GETDATE()) + 1
;WITH CTE AS (
SELECT TOP (225) [object_id] FROM sys.all_objects
), [Days] AS (
SELECT TOP (#DayCount)
DATEADD(DD, ROW_NUMBER() OVER (ORDER BY x.[object_id]) - 1, #BeginDate) AS [Day]
FROM CTE x
CROSS JOIN CTE y
ORDER BY
[Day]
)
SELECT
[Days].[Day],
Users.userid,
SUM(COALESCE(CONVERT(MONEY, DATEDIFF(SS, CASE WHEN CONVERT(DATE, Usage.gameStartTime) < [Day] THEN [Day] ELSE Usage.gameStartTime END,
CASE WHEN CONVERT(DATE, Usage.gameEndTime) > [Day] THEN DATEADD(DD, 1, [Days].[Day]) ELSE Usage.gameEndTime END)) / 3600, 0)) AS [Hours]
FROM [Days]
CROSS JOIN #Users Users
LEFT OUTER JOIN #Usage Usage
ON Usage.userid = Users.userid
AND [Days].[Day] BETWEEN CONVERT(DATE, Usage.gameStartTime) AND CONVERT(DATE, Usage.gameEndTime)
GROUP BY
[Days].[Day],
Users.userid
The query above yields the output below for the sample data:
Day userid Hours
---------- ----------- ---------------------
2015-04-21 1 0.00
2015-04-22 1 0.50
2015-04-23 1 0.00
2015-04-24 1 0.00
2015-04-25 1 2.00
2015-04-26 1 2.50
2015-04-27 1 0.00
I've edited my sql on sql fiddle and I think this might get you what you asked for. to me it looks a little more simple then the answer you've accepted.
DECLARE #FromDate datetime, #ToDate datetime
SELECT #Fromdate = MIN(StartDate), #ToDate = MAX(currentDate)
FROM Games
-- This recursive CTE will get you all dates
-- between the first StartDate and the last CurrentDate on your table
;WITH AllDates AS(
SELECT #Fromdate As TheDate
UNION ALL
SELECT TheDate + 1
FROM AllDates
WHERE TheDate + 1 <= #ToDate
)
SELECT UserId,
TheDate,
COALESCE(
SUM(
-- When the game starts and ends in the same date
CASE WHEN DATEDIFF(DAY, GameStartTime, GameEndTime) = 0 THEN
DATEDIFF(HOUR, GameStartTime, GameEndTime)
ELSE
-- when the game starts in the current date
CASE WHEN DATEDIFF(DAY, GameStartTime, TheDate) = 0 THEN
DATEDIFF(HOUR, GameStartTime, DATEADD(Day, 1, TheDate))
ELSE -- meaning the game ends in the current date
DATEDIFF(HOUR, TheDate, GameEndTime)
END
END
),
0) As HoursPerDay
FROM (
SELECT DISTINCT UserId,
TheDate,
CASE
WHEN CAST(GameStartTime as Date) = TheDate
THEN GameStartTime
ELSE NULL
END As GameStartTime, -- return null if no game started that day
CASE
WHEN CAST(GameEndTime as Date) = TheDate
THEN GameEndTime
ELSE NULL
END As GameEndTime -- return null if no game ended that day
FROM Games CROSS APPLY AllDates -- This is where the magic happens :-)
) InnerSelect
GROUP BY UserId, TheDate
ORDER BY UserId, TheDate
OPTION (MAXRECURSION 0)
Play with it your self on sql fiddle.
I have created a query to return following output.
Date Day Sale Qty Purchase Qty Transfer Qty
------------------------------------------------------------------
05/04/2015 1 11 0 0
07/04/2015 3 0 16 0
08/04/2015 4 12 14 17
11/04/2015 7 1 2 0
My current query is as follows.
(select T1.Date,T1.Day,T1.SaleQty,0 as PurchaseQty,0 as TransferQty from SaleTable T1)
union all
(select T2.Date,T2.Day,0 as SaleQty,T2.PurchaseQty,0 as TransferQty from PurchaseTable T2)
union all
(select T3.Date,T3.Day,0 as SaleQty,0 as PurchaseQty,T3.TransferQty from TransferTable T3)
Required output is in the following format
Date Day Sale Qty Purchase Qty Transfer Qty
------------------------------------------------------------------
05/04/2015 1 11 0 0
06/04/2015 2 0 0 0
07/04/2015 3 0 16 0
08/04/2015 4 12 14 17
09/04/2015 5 0 0 0
10/04/2015 6 0 0 0
11/04/2015 7 1 2 0
How should I write query to return rows with date and day when no result set is returned for that date output.
You need a table to act as a lookup table for the dates and days that are missing to cover the range of dates in the query results. You can create one like so:
-- add a temp table for your sample data
CREATE TABLE #Results
([Date] datetime, [Day] int, [Sale Qty] int, [Purchase Qty] int, [Transfer Qty] int)
;
-- insert your sample data
INSERT INTO #Results
([Date], [Day], [Sale Qty], [Purchase Qty], [Transfer Qty])
VALUES
('2015-04-05 00:00:00', 1, 11, 0, 0),
('2015-04-07 00:00:00', 3, 0, 16, 0),
('2015-04-08 00:00:00', 4, 12, 14, 17),
('2015-04-11 00:00:00', 7, 1, 2, 0)
;
-- find the max date
DECLARE #MaxDate DATETIME = (SELECT TOP 1 [Date] FROM #Results ORDER BY [Date] DESC)
-- recursive cte to build the date & day lookup table
;WITH cte AS (
-- cte anchor is the min date and day = 1
SELECT MIN([Date]) AS DateValue, 1 AS [Day]
FROM #Results
UNION ALL
-- uses dateadd to increment days until #MaxDate reached
SELECT DATEADD(DAY, 1, cte.DateValue), [Day] +1
FROM cte
WHERE DATEADD(DAY, 1, cte.DateValue) <= #MaxDate
)
-- inserts values into temp lookup table
SELECT *
INTO #DateLookup
FROM cte
This will create a temp table with the range of values using the lowest and highest dates in your results that holds these values:
DateValue Day
----------------------------
2015-04-05 00:00:00.000 1
2015-04-06 00:00:00.000 2
2015-04-07 00:00:00.000 3
2015-04-08 00:00:00.000 4
2015-04-09 00:00:00.000 5
2015-04-10 00:00:00.000 6
2015-04-11 00:00:00.000 7
You will then need to link to this table and replace any NULL values with 0 like so:
SELECT #DateLookup.[DateValue] AS [Date],
#DateLookup.[Day] ,
COALESCE([Sale Qty],0) AS [Sale Qty],
COALESCE([Purchase Qty],0) AS [Purchase Qty],
COALESCE([Transfer Qty],0) AS [Transfer Qty]
FROM #DateLookup
LEFT JOIN #Results ON #DateLookup.DateValue = #Results.[Date]
-- some tidy up
DROP TABLE #Results
DROP TABLE #DateLookup
If you don't already have a dates table in your system, add one. you can use this t-sql script:
CREATE TABLE TblDates(TheDate date not null);
DECLARE #StartDate date,
#NumberOfDates int,
#Counter int
SELECT #StartDate = GETDATE(), -- or whatever date you want
#NumberOfDates = 100, -- or whatever number of dates you want after start date
#Counter = 0;
WHILE(#Counter < #NumberOfDates)
BEGIN
INSERT INTO TblDates(TheDate) VALUES (DATEADD(d, #Counter, #StartDate)
END
After running this script you should have a dates table ready to use.
Then all you have to do is this:
SELECT TheDate,
DATEPART(D, TheDate) As Day,
ISNULL(T1.SaleQty, 0) As SaleQty,
ISNULL(T2.PurchaseQty, 0) As PurchaseQty,
ISNULL(T3.TransferQty, 0) As TransferQty
FROM tblDates LEFT JOIN
SaleTable T1 ON(TheDate = T1.Date) LEFT JOIN
PurchaseTable T2 ON(TheDate = T2.Date) LEFT JOIN
TransferTable T3 ON(TheDate = T3.Date)
Update
Now that i think about it, you can use a numbers table to create a cte that will hold your dates (creating the numbers table is fairly simple and much like the script I wrote for the dates table)
With DatesCTE(TheDate) AS
SELECT DATEADD(D, TheNumber, #startDate)
FROM NumbersTable
WHERE DATEADD(D, TheNumber, #StartDate) < #EndDate
Update 2
As Selva TS mentioned in a comment to this answer, it's possible to generate a dates cte even without a numbers table, using recursive cte like this:
DECLARE #StartDate datetime, #EndDate datetime
SELECT #StartDate = DATEADD(YEAR, -1, GETDATE()), #EndDate = GETDATE()
;WITH Calendar AS(
SELECT #StartDate dateidx
UNION ALL
SELECT dateidx + 1
FROM Calendar
WHERE dateidx + 1 < #EndDate
)