How to consolidate row with gaps using Sql Server? - sql

I have a table with data having a start date (and time), a end date (and time) a value and an identifier.
for instance I could have such rows (I left out the date value for breivety) :
id: 5, start: 00:00, end: 12:00, value: 50
id: 6, start: 00:00, end: 08:00, value: 60
id: 7, start: 08:00, end: 20:00, value: 70
id: 8, start: 22:00, end: 23:00, value: 80
id: 9, start: 00:00, end: 24:00, value: 90
id: 10, start: 05:00, end: 07:00, value: 100
Then I'd like to query the best (smaller id is better) matches which have value between 00:00 and 24:00 and not overlapping each other.
So it would result in:
start: 00:00, end: 12:00, value: 50 (from id 5)
start: 12:00, end: 20:00, value: 70 (from id 7, but cropped not to overlap previous match)
start: 22:00, end: 23:00, value: 80 (from id 8)
start: 20:00, end: 22:00, value: 90 (from id 9, but cropped not to overlap previous matches)
start: 23:00, end: 24:00, value: 90 (from id 9 again, but cropped not to overlap previous matches)
Nothing more, there are already values for the full datetime range.
There is no smallest unit of time (like one hour) so I can't generate a "tally table" with values from 00:00 to 24:00 and retrieves the best match for each unit of time (like 00:00 - 01:00).
There is no guarantee there will be enough values to fill the datetime range.
How could I achieve this?

Related

SQL Server - getting the correct price for the date period of an activity

I have some activity occurrences with the date range they occur across:
ActivityOccurrence:
ID: 1, ActivityID: 1, StartDate: 2018-05-01, EndDate: 2018-06-30
ID: 2, ActivityID: 2, StartDate: 2018-06-01, EndDate: 2018-07-31
ID: 3, ActivityID: 3, StartDate: 2018-07-01, EndDate: 2018-08-31
Each activity has a price which apply within an effective period:
EffectivePeriod
ID: 1, ActivityID: 1, ValidFrom: 2018-01-01, ValidTo: 2018-06-30, Price: 50
ID: 2, ActivityID: 2, ValidFrom: 2018-01-01, ValidTo: 2018-06-30, Price: 100
ID: 3, ActivityID: 3, ValidFrom: 2018-01-01, ValidTo: 2018-06-30, Price: 70
ID: 4, ActivityID: 1, ValidFrom: 2018-07-01, ValidTo: 2018-12-31, Price: 55
ID: 5, ActivityID: 2, ValidFrom: 2018-07-01, ValidTo: 2018-12-31, Price: 120
ID: 6, ActivityID: 3, ValidFrom: 2018-07-01, ValidTo: 2018-12-31, Price: 80
I'd like to link the Activity Occurrences with their correct rates. So:
ActivityOccurrence ID of 1 would link with EffectivePeriod ID of 1, spanning only the first effective period.
ActivityOccurrence ID of 2 would link with both EffectivePeriod ID of 2 and 5 as it spans across 2 effective periods.
ActivityOccurrence ID of 3 would link with EffectivePeriod ID of 6, spanning only the second effective period.
Doing a standard JOIN gets both effective periods for all 3 activity occurrences which I don't want. Using StartDate >= ValidFrom is correct for the first activity occurrence, but not the second and third. Using StartDate <= ValidTo means the first one is wrong, but the second and third are correct. Switching StartDate to EndDate also has some issues.
SQLFiddle: http://sqlfiddle.com/#!18/576c6/6
I feel like I'm missing something and the answer is very simple but I can't figure out what it is.
Are you trying to make sure that each ActivityOccurrence is joined with EACH EffectivePeriod that is temporally included in its date range?
What I use in such cases is make sure either start or end date of one table is between the start-end of the other:
SELECT ao.ActivityID, ao.StartDate, ao.EndDate, ep.Price
FROM ActivityOccurrence ao
JOIN EffectivePeriod ep ON ao.ActivityID = ep.ActivityID
AND
(
(ao.StartDate between ep.ValidFrom and ep.ValidTo)
OR
(ao.EndDate between ep.ValidFrom and ep.ValidTo)
)

Time elapsed between two dates (In a specific time range) ORACLE

I am creating a query that shows me the time elapsed between two dates, only taking into account only the one that is Monday through Friday from 08:00 to 17:00, for example:
For example, if a petition opens on day 1 at 6:30 p.m. and closes on day 2 at 8:45 p.m., the TMO is 45 minutes.
If it closes on day 3 at 8:45, the TMO is 9 hours and 45 minutes.
Example 2:
If a petition opens on Friday at 16:45 and closes on Tuesday at 8:30, the MTO would be: 15 minutes on Friday, nine hours on Monday and 30 minutes on Tuesday for an MTO = 9 hours 45 minutes
The query is performed on a single column of type date as I show below
I currently use a LAG function to make the query, but I can not create something functional, not even optimal to incorporate, I would greatly appreciate your help.
In the solution below I will ignore the "lag" part of your problem, which you said you know how to use. I am only showing how to count "working hours" between any two date_times (they may be during or before or after work hours, and/or they can be on weekend days; the computation is the same in all cases).
Explaining the answer in words: For two given date-times, "start" and "end", calculate how many "work" hours elapsed from the beginning of the week (from Monday 00:00:00) till each of them. This is in fact a calculation for ONE date, not for TWO dates. Then: given "start" and "end", calculate this number of hours for each of them; subtract the "end" number of hours from the "start" number of hours. To the result, add x times 5 times 9, where x is the difference in weeks between Monday 00:00:00 of the two dates. (If they are in the same week, the difference will be 0.)
To truncate a date to the beginning of the day, we use TRUNC(dt). To truncate to the beginning of Monday, TRUNC(dt, 'iw').
To compute how many "work" hours are from the beginning of the date dt until the actual time-of-day we can use the calculation
greatest(0, least(17/24, dt - trunc(dt)) - 8/24)
(the results will be in days; we calculate everything in days and then we can convert to hours). However, in the final formula we must check to see if the date is a Saturday or Sunday, in which case this should just be zero. Or, better, we can adjust the calculation a bit later, when we count from the beginning of Monday (we can use least( 5*9/24, ...)).
Putting everything together:
with
inputs ( dt1, dt2 ) as (
select to_date('2017-09-25 11:30:00', 'yyyy-mm-dd hh24:mi:ss'),
to_date('2017-10-01 22:45:00', 'yyyy-mm-dd hh24:mi:ss')
from dual
)
-- End of SIMULATED input dates (for testing only).
select 24 *
( least(5 * (17 - 8) / 24, greatest(0, least(17/24, dt2 - trunc(dt2)) - 8/24)
+ (17 - 8) / 24 * (trunc(dt2) - trunc(dt2, 'iw')))
-
least(5 * (17 - 8) / 24, greatest(0, least(17/24, dt1 - trunc(dt1)) - 8/24)
+ (17 - 8) / 24 * (trunc(dt1) - trunc(dt1, 'iw')))
+ 5 * (17 - 8) / 24 * (trunc(dt2, 'iw') - trunc(dt1, 'iw')) / 7
)
as duration_in_hours
from inputs
;
DURATION_IN_HOURS
-----------------
41.500

Optimization: How to get TimeId from time for each minute in a week?

I am creating a table which will have 2 columns:
Day_time (time from 1978-01-01 00:00:00 Sunday, till 1978-01-07 23:59:00.0 Saturday, Granularity: Minute)
Time_id (a unique id for each minute), to be populated
I have column one populated. I want to populate column two.
How I am doing it right now:
EXTRACT(dayofweek FROM day_time) * 10000 + DATEDIFF('minutes', TRUNC(day_time), day_time)
I basically want a function where I pass any date and it tells me where I am in a week. So, I need a function, just like the function above. Just more optimized, where I give a date and get a unique ID. The unique ID should repeat weekly.
Example: ID for Jan 1, 2015 00:00:00 will be same as Jan 8, 2015 00:00:00.
Why 1978-01-01? cuz it starts from a Sunday.
Why 10,000? cuz the number of minutes in a day are in four digits.
You can do it all in one fell swoop, without needing to extract the date separately:
SELECT DATEDIFF('minutes', date_trunc('week',day_time), day_time) which I'd expect to be marginally faster.
Another approach that I'd expect to be significantly faster would be converting the timestamp to epoch, dividing by 60 to get minutes from epoch and then taking the value modulus of 10,080 (for 60 * 24 * 7 minutes in a week).
SELECT (extract(epoch from day_time) / 60) % 10080
If you don't care about the size of the weekly index, you could also do:
SELECT (extract(epoch from day_time)) % 604800 and skip the division step altogether, which should make it faster still.

Check and calulate time period between datetime in sql 2005

I need to check if any work time period is between datetime and calculate hours between for work hours sum grater than 8 hours.
Sample data:
1. Work time: 07:00 - 17:00 and datetime for checking 06:00-22:00
Answer: 2 hour
2. Work time: 13:00 - 23:00 and datetime for checking 06:00-22:00
Answer: 1 hour (only 1 hour is grater 8 hours and between 06:00-22:00 )
3. Work time: 19:00 - 05:00 and datetime for checking 22:00-06:00
Answer: 2 hour (only 2 hour is grater 8 hours and between 22:00-06:00 )
Any ideas?
Try this: T-SQL DateDiff - partition by "full hours ago", rather than "times minutes turned 00 since"
Basically, DATEDIFF(HOUR, endTime, startTime). Then, subtract your result from 8 to get the difference (if they were "under" time, the difference will be negative).

Adding relative week number column to MySQl results

I have a table with 3 columns: user, value, and date. The main query returns the values for a specific user based on a date range:
SELECT date, value FROM values
WHERE user = '$user' AND
date BETWEEN $start AND $end
What I would like is for the results to also have a column indicating the week number relative to the date range. So if the date range is 1/1/2010 - 1/20/2010, then any results from the first Sun - Sat of that range are week 1, the next Sun - Sat are week 2, etc. If the date range starts on a Saturday, then only results from that one day would be week 1. If the date range starts on Thursday but the first result is on the following Monday, it would be week 2, and there are no week 1 results.
Is this something fairly simple to add to the query? The only ideas I can come up with would be based on the week number for the year or the week number based on the results themselves (where in that second example above, the first result always gets week 1).
Example:
$start = "05/27/2010";
$end = "06/13/2010";
//Query
Result:
week date user value
1 05/28/2010 joe 123
3 06/07/2010 joe 123
3 06/08/2010 joe 123
4 06/13/2010 joe 123
This can easily be done with this simple and obvious expression :)
SELECT ...,
FLOOR(
(
DATEDIFF(date, $start) +
WEEKDAY($start + INTERVAL 1 DAY)
) / 7
) + 1 AS week_number;
Some explanation: this expression just calculates difference between the given date and the start date in days, then converts this number to weeks via FLOOR("difference in days" / 7) + 1. That's simple, but since this works only when $start is Sunday, we should add an offset for other week days: WEEKDAY($start + INTERVAL 1 DAY) which equals to 0 for Sun, 1 for Mon, ..., 6 for Sat.