I have a problem ,i solve it but i have written a long procedure and i can\'t be sure that it covers all the possible cases .
The problem:
If i have a
Here is SQLFiddle with complete query.
I will show how I built a query that returns number of minutes for each emp_num, day_date. If it turns out that no minutes are left for a particular emp_num, day_date, then result would not have a row with 0, there will be no such row at all.
General idea
I will use a table of numbers. We'll need only 24*60=1440 numbers, but it is a good idea to have such table in your database for other reports. I personally have it with 100,000 rows. Here is a very good article comparing different methods to generate such table.
For each interval I'm going to generate a set of rows using the table of numbers - one row for each minute in the interval. I assume that intervals are [start; end), i.e. start minute is inclusive, end minute is exclusive. For example, interval from 07:00 to 08:00 is 60 minutes, not 61.
Generate a table of numbers
DECLARE @Numbers TABLE (N int);
INSERT INTO @Numbers(N)
SELECT TOP(24*60)
ROW_NUMBER() OVER(ORDER BY S.object_id) - 1 AS N
FROM
sys.all_objects AS S
ORDER BY N
;
For this task it is better to have numbers that start from 0. Normally you would have it as a permanent table with primary key on N.
Sample data
DECLARE @Missions TABLE (emp_num int, day_date datetime, mission_in datetime, mission_out datetime);
DECLARE @Periods TABLE (emp_num int, day_date datetime, work_st datetime, work_end datetime, check_in datetime, check_out datetime, day_state char(1));
INSERT INTO @Missions (emp_num, day_date, mission_in, mission_out) VALUES
(547, '2015-04-01', '2015-04-01 15:00:00', '2015-04-01 21:30:00'),
(547, '2015-04-02', '2015-04-02 08:00:00', '2015-04-02 14:00:00');
INSERT INTO @Periods (emp_num, day_date, work_st, work_end, check_in, check_out, day_state) VALUES
(547, '2015-04-01', '2015-04-01 08:00:00', '2015-04-01 16:00:00', '2015-04-01 07:45:00', '2015-04-01 12:10:00', 'W'),
(547, '2015-04-01', '2015-04-01 08:00:00', '2015-04-01 16:00:00', '2015-04-01 12:45:00', '2015-04-01 17:24:00', 'W'),
(547, '2015-04-02', '2015-04-02 00:00:00', '2015-04-02 00:00:00', '2015-04-02 07:11:00', '2015-04-02 13:11:00', 'E');
My solution will not use the day_state column. I expect that you would have 00:00:00 for both work_st and work_end. Solution expects that the date component within the same row is the same and that day_date doesn't have time component.
If I designed the schema for this task I would have three tables instead of two: Missions, WorkPeriods and CheckPeriods. I would split your table Periods into two to avoid repeating work_st and work_end in several rows. But this solution would deal with your current schema and it will essentially generate this third table on the fly. In practice it means that performance may be improved.
Mission minutes
WITH
CTE_MissionMinutes
AS
(
SELECT emp_num, day_date, N.N
FROM
@Missions AS M
CROSS JOIN @Numbers AS N
WHERE
N.N >= DATEDIFF(minute, M.day_date, M.mission_in) AND
N.N < DATEDIFF(minute, M.day_date, M.mission_out)
)
Each original row from @Missions turns into a set of rows, one for each minute of the interval (mission_in, mission_out).
Work periods
,CTE_WorkPeriods
AS
(
SELECT P.emp_num, P.day_date, P.work_st, P.work_end
FROM @Periods AS P
GROUP BY P.emp_num, P.day_date, P.work_st, P.work_end
)
Generate a third helper table - one row for each emp_num, day_date, work_st, work_end - all intervals for (work_st, work_end).
Work and Check minutes
,CTE_WorkMinutes
AS
(
SELECT emp_num, day_date, N.N
FROM
CTE_WorkPeriods
CROSS JOIN @Numbers AS N
WHERE
N.N >= DATEDIFF(minute, CTE_WorkPeriods.day_date, CTE_WorkPeriods.work_st) AND
N.N < DATEDIFF(minute, CTE_WorkPeriods.day_date, CTE_WorkPeriods.work_end)
)
,CTE_CheckMinutes
AS
(
SELECT emp_num, day_date, N.N
FROM
@Periods AS P
CROSS JOIN @Numbers AS N
WHERE
N.N >= DATEDIFF(minute, P.day_date, P.check_in) AND
N.N < DATEDIFF(minute, P.day_date, P.check_out)
)
Exactly the same as for Missions.
Union "secondary intervals"
,CTE_UnionPeriodMinutes
AS
(
SELECT emp_num, day_date, N
FROM CTE_WorkMinutes
UNION ALL -- can be not ALL here, but ALL is usually faster
SELECT emp_num, day_date, N
FROM CTE_CheckMinutes
)
Subtract secondary intervals from primary
,CTE_FinalMinutes
AS
(
SELECT emp_num, day_date, N
FROM CTE_MissionMinutes
EXCEPT
SELECT emp_num, day_date, N
FROM CTE_UnionPeriodMinutes
)
Sum up number of minutes
SELECT
emp_num
,day_date
,COUNT(*) AS FinalMinutes
FROM CTE_FinalMinutes
GROUP BY emp_num, day_date
ORDER BY emp_num, day_date;
To make the final query just put all CTEs together.
Result set
emp_num day_date FinalMinutes
547 2015-04-01 00:00:00.000 246
547 2015-04-02 00:00:00.000 49
There are 246 minutes between 17:24 and 21:30.
There are 49 minutes between 13:11 and 14:00.
Here is SQLFiddle with complete query.
It is fairly easy to show actual intervals that lead to this SUM of minutes, but you said you need just the SUM.