Showing posts with label details. Show all posts
Showing posts with label details. Show all posts

Saturday, February 25, 2012

Recording Gaps

hi,

I have a view that contains details of when people are allocated to certain task, relevant fields as follows:

RES_ID int(4) (The Id no for that member of staff)
PK TASK_ID int(4) (the code for the task)
PK START_DATE datetime(8) (The date and time that they started on the task
END_DATE datetime(8) (when the task finishes)

I have been asked to create a report that shows when people are NOT working on a particular date (so they can reassign people if a someone calls in sick). Sounds easy until you think about it.

e.g. say Brian works the following hours on 02.01.2007
3.00am - 4.00am
4.30am - 6.00am
7.00am - 11.00am

I need to show the following in report:
Brian free 12.00am - 2.59am
Brian free 4.01am - 4.29am
Brian free 6.01am - 6.59am
Brian free 11.01am - 11.59pm

(I need this info for each member of staff)

I cannot work out how to do this. I am relatively new to SQL server but have a good understanding of relational databases.

Any help would be much appreciated as I am totally stuck.

Thanks,
Chris

The best way to accomplish this type of goal is to have a 'Calendar' table. Then you simply JOIN against the Calendar table to interact with your data in a temporal fashion.

Read about using a Calendar table here:

Datetime -Calendar Table
http://www.aspfaq.com/show.asp?id=2519

|||

Try:

Code Snippet

createtable #t (

RES_ID int,

TASK_ID int,

[START_DATE] datetime,

END_DATE datetime

)

go

insertinto #t values(1, 1,'2007-06-18T03:00:00','2007-06-18T04:00:00')

insertinto #t values(1, 1,'2007-06-18T04:30:00','2007-06-18T06:00:00')

insertinto #t values(1, 1,'2007-06-18T07:00:00','2007-06-18T11:00:00')

insertinto #t values(2, 1,'2007-06-18T03:20:00','2007-06-18T04:30:00')

insertinto #t values(2, 1,'2007-06-18T04:31:00','2007-06-18T06:15:00')

insertinto #t values(2, 1,'2007-06-18T07:00:00','2007-06-18T12:00:00')

go

declare @.d datetime

set @.d ='20070618'

;with cte

as

(

select

a.RES_ID,

dateadd(minute, 1, a.END_DATE)as [START_DATE_1],

dateadd(minute,-1,

(

selectmin(b.[START_DATE])

from #t as b

where b.RES_ID = a.RES_ID and b.[START_DATE] > a.END_DATE

)

)as [END_DATE_1]

from

#t as a

where

a.[START_DATE] >= @.d and a.[END_DATE] <dateadd(day, 1, @.d)

andnotexists(

select*

from #t as b

where

b.RES_ID = a.RES_ID

and b.[START_DATE] >= @.d and b.[END_DATE] <dateadd(day, 1, @.d)

and b.[START_DATE] =dateadd(minute, 1, a.END_DATE)

)

and a.END_DATE <(

selectmax(c.END_DATE)

from #t as c

where

c.RES_ID = a.RES_ID

and c.[START_DATE] >= @.d and c.[END_DATE] < dateadd(day, 1, @.d)

)

)

select

*

from

cte

union

select

RES_ID,

@.d,

dateadd(minute, -1, min([START_DATE]))

from

#t

where

[START_DATE] >= @.d and [END_DATE] < dateadd(day, 1, @.d)

group by

RES_ID

having

dateadd(minute, -1, min([START_DATE])) > @.d

union

select

RES_ID,

dateadd(minute, 1, max([START_DATE])),

dateadd(minute, 1439, @.d)

from

#t

where

[START_DATE] >= @.d and [END_DATE] < dateadd(day, 1, @.d)

group by

RES_ID

having

dateadd(minute, 1, max([START_DATE])) < dateadd(minute, 1439, @.d)

order by

RES_ID,

[START_DATE_1]

go

drop table #t

go

The idea behind this possible solution, is to find the gaps for each RES_ID for a specific date.

AMB

|||

Hi,

thanks for the responses. Hunchback, how would I turn this into a stored procedure with the date as a parameter? I have tried but get lots of syntax errors. I'm sorry but I have only created basic SPs in the past.

Thanks,

Chris

|||

Code Snippet

create procedure dbo.usp_p1

@.d datetime

as

set nocount on

;with cte

as

(

select

a.RES_ID,

dateadd(minute, 1, a.END_DATE)as [START_DATE_1],

dateadd(minute,-1,

(

selectmin(b.[START_DATE])

from #t as b

where b.RES_ID = a.RES_ID and b.[START_DATE] > a.END_DATE

)

)as [END_DATE_1]

from

#t as a

where

a.[START_DATE] >= @.d and a.[END_DATE] <dateadd(day, 1, @.d)

andnotexists(

select*

from #t as b

where

b.RES_ID = a.RES_ID

and b.[START_DATE] >= @.d and b.[END_DATE] <dateadd(day, 1, @.d)

and b.[START_DATE] =dateadd(minute, 1, a.END_DATE)

)

and a.END_DATE <(

selectmax(c.END_DATE)

from #t as c

where

c.RES_ID = a.RES_ID

and c.[START_DATE] >= @.d and c.[END_DATE] < dateadd(day, 1, @.d)

)

)

select

*

from

cte

union

select

RES_ID,

@.d,

dateadd(minute, -1, min([START_DATE]))

from

#t

where

[START_DATE] >= @.d and [END_DATE] < dateadd(day, 1, @.d)

group by

RES_ID

having

dateadd(minute, -1, min([START_DATE])) > @.d

union

select

RES_ID,

dateadd(minute, 1, max([START_DATE])),

dateadd(minute, 1439, @.d)

from

#t

where

[START_DATE] >= @.d and [END_DATE] < dateadd(day, 1, @.d)

group by

RES_ID

having

dateadd(minute, 1, max([START_DATE])) < dateadd(minute, 1439, @.d)

order by

RES_ID,

[START_DATE_1]

go

exec dbo.usp_p1 '19960704'

go

AMB

|||

hi,

I keep getting the following message when I try to save the SP

ADO error:Incorrect syntax near the word 'with'

the server is SQL SERVER 2000 and I am trying to save the SP in an Access .adp file. Do I need a slightly different syntax?

Thanks,

Chris

|||

Commom table expressions were introduced with SS 2005, so we have to change the statement.

create procedure dbo.usp_p1

@.d datetime

as

set nocount on

select

a.RES_ID,

dateadd(minute, 1, a.END_DATE)as [START_DATE_1],

dateadd(minute,-1,

(

selectmin(b.[START_DATE])

from #t as b

where b.RES_ID = a.RES_ID and b.[START_DATE] > a.END_DATE

)

)as [END_DATE_1]

from

#t as a

where

a.[START_DATE] >= @.d and a.[END_DATE] <dateadd(day, 1, @.d)

andnotexists(

select*

from #t as b

where

b.RES_ID = a.RES_ID

and b.[START_DATE] >= @.d and b.[END_DATE] <dateadd(day, 1, @.d)

and b.[START_DATE] =dateadd(minute, 1, a.END_DATE)

)

and a.END_DATE <(

selectmax(c.END_DATE)

from #t as c

where

c.RES_ID = a.RES_ID

and c.[START_DATE] >= @.d and c.[END_DATE] < dateadd(day, 1, @.d)

)

union

select

RES_ID,

@.d,

dateadd(minute, -1, min([START_DATE]))

from

#t

where

[START_DATE] >= @.d and [END_DATE] < dateadd(day, 1, @.d)

group by

RES_ID

having

dateadd(minute, -1, min([START_DATE])) > @.d

union

select

RES_ID,

dateadd(minute, 1, max([START_DATE])),

dateadd(minute, 1439, @.d)

from

#t

where

[START_DATE] >= @.d and [END_DATE] < dateadd(day, 1, @.d)

group by

RES_ID

having

dateadd(minute, 1, max([START_DATE])) < dateadd(minute, 1439, @.d)

order by

RES_ID,

[START_DATE_1]

go

exec dbo.usp_p1 '19960704'

go

|||here's, another approach, this may or may not perform better than hunchbacks post, although they both have the same logic, btw, thanks hunchback for the test data that i piggy back with

[edited]
create table #t (
RES_ID int,
TASK_ID int,
[START_DATE] datetime,
END_DATE datetime
)

insert into #t values(1, 1, '2007-06-18T03:00:00', '2007-06-18T04:00:00')
insert into #t values(1, 1, '2007-06-18T04:30:00', '2007-06-18T06:00:00')
insert into #t values(1, 1, '2007-06-18T07:00:00', '2007-06-18T11:00:00')
insert into #t values(2, 1, '2007-06-18T03:20:00', '2007-06-18T04:30:00')
insert into #t values(2, 1, '2007-06-18T04:31:00', '2007-06-18T06:15:00')
insert into #t values(2, 1, '2007-06-18T07:00:00', '2007-06-18T12:00:00')

declare @.d datetime

set @.d = '20070618'

select *
from #t

select identity(int,1,1) as row
, *
, dateadd(mi,-1,start_date) as free_end
, dateadd(mi,1,end_date) as free_start
into #r
from #t
order by
res_id
, start_date

select res_id
, task_id
, min(dateadd(mi,-1,start_date)) as min_start
, max(dateadd(mi,1,end_date)) as max_end
into #minmax
from #t
group by
res_id
, task_id

select distinct
t.res_id
, t.task_id
, dateadd(mi,1,dateadd(mi,-1,@.d)) as free_start
, r.min_start as free_end
from #t t inner join
#minmax r on t.res_id = r.res_id
and t.task_id = r.task_id
union
select r1.res_id
, r1.task_id
, r1.free_start as free_start
, r2.free_end as free_end
from #r r1 inner join
#r r2 on r1.row = r2.row - 1
and r1.res_id = r2.res_id
and r1.task_id = r2.task_id
union
select distinct
t.res_id
, t.task_id
, r.max_end as free_start
, dateadd(mi,-1,dateadd(day,1,@.d)) as free_end
from #t t inner join
#minmax r on t.res_id = r.res_id
and t.task_id = r.task_id

drop table #t
drop table #r
drop table #minmax|||

hi Hunchback,

thanks very much this is great, but having had a close look at the data that the report has to deal with, it is not quite there - not your fault, mine for not being specific enough.

These are the extra conditions it has to deal with:

a task can start on the day before but end on the day of the report,e.g.

Chris task1 01/01/2007 23:50 - 02/01/2007 01.00

for the above, the report would have to say the freetime started at 01.01am.

Also, tasks can overlap ,e.g.

Chris task1 02/01/2007 06.50 - 02/01/2007 07.30

Chris task2 02/01/2007 07.15 - 02/01/2007 07.40

Chris task4 02/01/2007 07.20 - 02/01/2007 07.35

Chris task3 02/01/2007 07.50 - 02/01/2007 23.59

in the above the freetime is

00.00 - 06.49,

07.41 - 07.49

And finally,

if someone does not have any tasks that day then their freetime should show 00.00 - 23.59.

I realise that these extra conditions make it that much harder, but they were the reason that I originally thought I had no chance of working it out. It is my fault that I overlooked mentioning them.

Thanks,

Chris

|||You will probably find that cursors run much quicker here when you have a realistic amount of data.

How does this code grab you? I'd use a proper resources table in the first statement though. This should handle overlaps and people who are missing.

--First populate everyone
select
r.res_id, @.thedate as start_date, dateadd(day,1,dateadd(minute,-1,@.thedate)) as end_date
into #tmp
from (select distinct res_id from #t) r

--Now run through the time worked
declare csrCsr cursor for
select res_id, start_date, end_date
from #t
where start_date >= @.thedate
and start_date < dateadd(day,1,@.thedate)
order by res_id, start_date
for read only

declare @.res int, @.start datetime, @.end datetime

open csrCsr
fetch csrCsr into @.res, @.start, @.end
while (@.@.fetch_status = 0)
begin
-- For each overlap, put records in either side, and then get rid of the original
insert into #tmp
select @.res, start_date, dateadd(minute,-1,@.start)
from #tmp
where res_id = @.res
and start_date < @.end
and @.start < end_date
and start_date < @.start

insert into #tmp
select @.res, dateadd(minute,1,@.end), end_date
from #tmp
where res_id = @.res
and start_date < @.end
and @.start < end_date
and @.end < end_date

delete
from #tmp
where res_id = @.res
and start_date < @.end
and @.start < end_date

fetch csrCsr into @.res, @.start, @.end
end
close csrCsr
deallocate csrCsr

--Now query the table, and drop the temporary resource
select * from #tmp
drop table #tmp

--Rob|||Oh, throw that into a stored procedure, like this:

create procedure dbo.fetch_times (@.thedate datetime)
as
...

--and of course, use the proper table name instead of #t.|||

hi,

I'd like to thank everyone for their help. After a day or too of navel-gazing, I admitted to myself that my SQL is not that great and resorted to.......CURSORS!

Sorry, but once I did that, it was pretty easy (see below). It is all well and good saying everything should be done with set-base solutions, but I don't have the time or skill for the more extreme examples of these.

anyway, here's my solution:

ALTER PROCEDURE GAP_Report
(
@.d DATETIME
)

AS
SET NOCOUNT ON


DECLARE @.resname VARCHAR(50), @.startdate DATETIME, @.enddate DATETIME, @.rname VARCHAR(50), @.edate DATETIME


SET @.rname = ''

DECLARE @.FreeTime TABLE
(
Res_name VARCHAR(50),
free_start DATETIME,
free_end DATETIME
)

DECLARE cCursor CURSOR FAST_FORWARD
FOR

SELECT
RES_NAME,TASK_START_DATE, TASK_FINISH_DATE
FROM
view_TasksPerUser
WHERE
((DATEDIFF(d,TASK_START_DATE,@.d) = 0) OR (DATEDIFF(d,TASK_FINISH_DATE,@.d) = 0))
ORDER BY
RES_NAME,TASK_START_DATE

OPEN cCursor
FETCH cCursor INTO @.resname, @.startdate, @.enddate

WHILE @.@.Fetch_Status = 0
BEGIN
IF @.rname <> @.resname
BEGIN
IF DATEDIFF(d,@.d,@.startdate) = 0
BEGIN
INSERT INTO @.FreeTime VALUES (@.resname,@.d,DATEADD(s,-1,@.startdate))
END

IF @.rname <> ''
BEGIN
IF DATEDIFF(d,@.d,@.enddate) = 0
BEGIN
INSERT INTO @.FreeTime VALUES (@.rname,DATEADD(s,1,@.edate),DATEADD(s,-1,DATEADD(d,1,@.d)))
END
END
SET @.edate = @.enddate
END
ELSE
BEGIN
IF DATEDIFF(s,@.edate,@.Startdate) > 0
BEGIN

INSERT INTO @.FreeTime VALUES (@.resname,DATEADD(s,1,@.edate), DATEADD(s,-1,@.startdate))
END
END

SET @.rname = @.resname

IF @.enddate > @.edate

SET @.edate = @.enddate

FETCH cCursor INTO @.resname, @.startdate, @.enddate

END


CLOSE cCursor
DEALLOCATE cCURSOR

INSERT INTO
@.FreeTime
SELECT
RES_NAME as res_name,
@.d as Free_start,
DATEADD(s,-1,DATEADD(d,1,@.d)) as Free_end
FROM
(SELECT
DISTINCT RES_NAME
FROM
MSP_RESOURCES
WHERE
RES_NAME <> ''
UNION ALL
SELECT
DISTINCT res_name
FROM
@.Freetime) U
GROUP BY res_name
HAVING COUNT(*) = 1

SELECT
*
FROM
@.Freetime
ORDER BY res_name, free_start, free_end

RETURN 0

thanks again,

Chris

Monday, February 20, 2012

Record selection for latest dates (two fields)

Hi there.

I was wondering if anyone could help me here.

I need to select record details for the latest dates from two fields. The fields are:

Date1; and
Date2.

I tried to use the Maximum formula, but can not get it to work (i.e it wont select the latest date from each of the fields, and supress all other records):

{RCF03FormalAndUpcomingFormalReviews_RCF03.SttDat}= Maximum ({RCF03FormalAndUpcomingFormalReviews_RCF03.SttDat}) and
{RCF03FormalAndUpcomingFormalReviews_RCF03.QCF0612} = Maximum({RCF03FormalAndUpcomingFormalReviews_RCF03.QCF0612})

Any help would be great.

Cheers.

Mat.Not quite sure what you are asking, but if you want to select records for only the two most recent dates in the database, use a subroutine to select those dates, then "share" them with the main report.|||Try creating two formulas
@.maxrcf03 would be

Maximum ({RCF03FormalAndUpcomingFormalReviews_RCF03.SttDat})

@.MaxQCF would be

Maximum({RCF03FormalAndUpcomingFormalReviews_RCF03.QCF0612})

then use the formulas to return your maximum date for each of these fields.|||I tried that, but could not get it working.

Here is a sample of my dataset, where I need to return only information in row 1 (record 1):

Row 1. SPRING Cecil fred 31/05/2007 6/06/2007
Row 2. SPRING Cecil fred 1/06/2007 6/06/2007
Row 3. SPRING Cecil fred 18/06/2007 6/06/2007
Row 4. SPRING Cecil fred 15/06/2007 6/06/2007
Row 5. SPRING Cecil fred 19/06/2007 6/06/2007
Row 6. SPRING Cecil fred 25/06/2007 6/06/2007

The formulae overwrites the whole data range for all clients eg:

SPRING Cecil fred 31/05/2007 6/06/2007
SPRING Cecil fred 31/05/2007 6/06/2007
SPRING Cecil fred 31/05/2007 6/06/2007
SPRING Cecil fred 31/05/2007 6/06/2007
SPRING Cecil fred 31/05/2007 6/06/2007
SPRING Cecil fred 31/05/2007 6/06/2007
SPRING Cecil fred 31/05/2007 1/06/2007
WINTER Wayne 31/05/2007 4/06/2007
WINTER Wayne 31/05/2007 4/06/2007
WINTER Wayne 31/05/2007 4/06/2007

I'm not sure how to make the formulae record-specific i.e. so it only applies to Mr Spring, anmd not to Mr Winter.

Cheers and thanks for your help.

Mat.|||Group on the field that is SPRING, WINTER etc. I'll call it {table.id}

Add a GROUP selection formula of
{table.date1} = Maximum({table.date1}, {table.id})
and {table.date2} = Maximum({table.date2}, {table.id})

Note that this means that if a particular group does not have a record where *both* of the dates are the latest respective dates within the group then it will not show at all.

An alternative would be to group by {table.id}, sort descending on both date1 and date2 and then print in the group header (or sort ascending and print in the group footer).|||Thanks Jagan.

I'll give that a go tomorrow at work.

cheers.

Mat.