Reservation table contains reservations start dates, start hours and durations.
Start hour is by half hour increments in working hours 8:00 .. 18:00 in work days.
Duration is also by half hour increments in day.
CREATE TABLE reservation (
startdate date not null, -- start date
starthour numeric(4,1) not null , -- start hour 8 8.5 9 9.5 .. 16.5 17 17.5
duration Numeric(3,1) not null, -- duration by hours 0.5 1 1.5 .. 9 9.5 10
primary key (startdate, starthour)
);
table structure can changed if required.
How to find first free half hour in table which is not reserved ?
E.q if table contains
startdate starthour duration
14 9 1 -- ends at 9:59
14 10 1.5 -- ends at 11:29, e.q there is 30 minute gap before next
14 12 2
14 16 2.5
result should be:
starthour duration
11.5 0.5
Probably PostgreSql 9.2 window function should used to find
first row whose starthour is greater than previous row starthour + duration
How to write select statement which returns this information ?
Postgres 9.2 has range type and I would recommend to use them.
create table reservation (reservation tsrange);
insert into reservation values
('[2012-11-14 09:00:00,2012-11-14 10:00:00)'),
('[2012-11-14 10:00:00,2012-11-14 11:30:00)'),
('[2012-11-14 12:00:00,2012-11-14 14:00:00)'),
('[2012-11-14 16:00:00,2012-11-14 18:30:00)');
ALTER TABLE reservation ADD EXCLUDE USING gist (reservation WITH &&);
"EXCLUDE USING gist" creates index which disallows to inset overlapping entries. You can use the following query to find gaps (variant of vyegorov's query):
with gaps as (
select
upper(reservation) as start,
lead(lower(reservation),1,upper(reservation)) over (ORDER BY reservation) - upper(reservation) as gap
from (
select *
from reservation
union all values
('[2012-11-14 00:00:00, 2012-11-14 08:00:00)'::tsrange),
('[2012-11-14 18:00:00, 2012-11-15 00:00:00)'::tsrange)
) as x
)
select * from gaps where gap > '0'::interval;
'union all values' masks out non working times hence you can make reservation between 8am and 18pm only.
Here is the result:
start | gap
---------------------+----------
2012-11-14 08:00:00 | 01:00:00
2012-11-14 11:30:00 | 00:30:00
2012-11-14 14:00:00 | 02:00:00
Documentation links:
- http://www.postgresql.org/docs/9.2/static/rangetypes.html "Range Types"
- https://wiki.postgresql.org/images/7/73/Range-types-pgopen-2012.pdf
Maybe not the best query, but it does what you want:
WITH
times AS (
SELECT startdate sdate,
startdate + (floor(starthour)||'h '||
((starthour-floor(starthour))*60)||'min')::interval shour,
startdate + (floor(starthour)||'h '||
((starthour-floor(starthour))*60)||'min')::interval
+ (floor(duration)||'h '||
((duration-floor(duration))*60)||'min')::interval ehour
FROM reservation),
gaps AS (
SELECT sdate,shour,ehour,lead(shour,1,ehour)
OVER (PARTITION BY sdate ORDER BY shour) - ehour as gap
FROM times)
SELECT * FROM gaps WHERE gap > '0'::interval;
Some notes:
It will be better not to separate time and data of the event. If you have to, then use standard types;
If it is not possible to go with standard types, create function to convert numeric hours into the time format.
Related
I am using the DATEDIFF function to calculate the difference between my two timestamps.
payment_time = 2021-10-29 07:06:32.097332
trigger_time = 2021-10-10 14:11:13
What I have written is : date_diff('minute',payment_time,trigger_time) <= 15
I basically want the count of users who paid within 15 mins of the triggered time
thus I have also done count(s.user_id) as count
However it returns count as 1 even in the above case since the minutes are within 15 but the dates 10th October and 29th October are 19 days apart and hence it should return 0 or not count this row in my query.
How do I compare the dates in my both columns and then count users who have paid within 15 mins?
This also works to calculate minutes between to timestamps (it first finds the interval (subtraction), and then converts that to seconds (extracting EPOCH), and divides by 60:
extract(epoch from (payment_time-trigger_time))/60
In PostgreSQL, I prefer to subtract the two timestamps from each other, and extract the epoch from the resulting interval:
Like here:
WITH
indata(payment_time,trigger_time) AS (
SELECT TIMESTAMP '2021-10-29 07:06:32.097332',TIMESTAMP '2021-10-10 14:11:13'
UNION ALL SELECT TIMESTAMP '2021-10-29 00:00:14' ,TIMESTAMP '2021-10-29 00:00:00'
)
SELECT
EXTRACT(EPOCH FROM payment_time-trigger_time) AS epdiff
, (EXTRACT(EPOCH FROM payment_time-trigger_time) <= 15) AS filter_matches
FROM indata;
-- out epdiff | filter_matches
-- out ----------------+----------------
-- out 1616119.097332 | false
-- out 14.000000 | true
I've a PSQL table like this:
Order
Start_Hour
Start_Minute
Finish_Hour
Finish_Minute
10
10
15
12
15
10
12
15
14
15
10
16
00
17
00
And I need to calculate by a query the total time expressed in hours that I spent to finish the order. In this scenario I expect to have a total of 5 hours:
12:15 - 10:15 = 2 hours
14:15 - 12:15 = 2 hours
17:00 - 16:00 = 1 hours
The query result must be 5.
The idea was concatenate start hour/minute and finish hour/minute, convert them to hour, make the difference, calculating the total.
SELECT (Start_Hour & ":" & Start_Minute) as start, (Finish_Hour & ":" & Finish_Minute) as finish
FROM OrderDetails
But when I try to convert them to HH:MM using cast or convert but I got errors.
Any advice?
Thank you
This query uses make_time as Adrian Klaver suggests.
select
"Order",
sum(extract(hour from
make_time("Finish_Hour", "Finish_Minute", 0) -
make_time("Start_Hour", "Start_Minute", 0))
) as duration
from the_table
group by "Order";
However I have remarks about your data design. Hour and minute are not enough for storing time because (apart from missing precision and other reasons) the end time might be over midnight. You have a specific data type for this - timestamp. I would suggest something like
create table the_table
(
order_nr integer,
start_time timestamp,
finish_time timestamp
);
Also note that using mixed case names in Postgresql requires double-quoting.
Use make_time:
select make_time(12, 15, 0) - make_time(10, 15, 0);
?column?
----------
02:00:00
Where in your case you would substitute in Start_Hour, Start_Minute, Finish_Hour, Finish_Minute.
I need to compare date and time of 2 fields and see if its greater than 24 hours however there is another field whose value is less than the 24 hours of the compared date. For example ,
create date - 18/7/2019 11:15 AM
target date - 19/07/2019 11:16 AM
There is a gap of 24 hours here
Actual date - 19/07/2019 10:45 AM
Actual date is less than above said gap of 24 hours . Hence the query should return such records that has actual date less than the create date and target date gap of 24 hours. Here all the fields are of DATETIME data type in DB2 database.
It's not clear what you really try to do.
Actual date is less than above said gap of 24 hours
How can a date may be comparable to a gap (or time duration)?
Should we understand it as:
Gap between actual and created is less than gap between target and created?
If so, then try the following:
select *
from
(
select create, target, actual
, timestampdiff(8, char(target - create)) gap_target
, timestampdiff(8, char(actual - create)) gap_actual
from table(values
(timestamp('2019-07-18-11.15.00'), timestamp('2019-07-19-11.16.00'), timestamp('2019-07-19-10.45.00'))
) t (create, target, actual)
)
where
gap_target >= 24 and gap_actual < 24
-- or some another expression like:
-- gap_actual < gap_target
;
CREATE TARGET ACTUAL GAP_TARGET GAP_ACTUAL
------------------- ------------------- ------------------- ---------- ----------
2019-07-18 11:15:00 2019-07-19 11:16:00 2019-07-19 10:45:00 24 23
Is this what you want?
If not, then provide a few number of input records and the exact result desired.
I am after some guidance on the best way to get useful information out of our MIS database
Scenario:- I want to check staff utilisation by a variable period that I can drill down into. This needs to then be split into days so I can assess over a 24 hour period what was done
The table is huge and has loads of columns we need to calculate, so ideally I need to split the records that span 2 days into 2
The table has a datetimeformat field that has user [starttime], it then has a separate field that has [duration] which is in decimal hours.
So an example would be:
ID StartTime Duration Qty username
1 2016-11-24 23:00:00 2.00 1000 Joe Bloggs
In the example above Joe starts at 11pm and works till 1 am, so what I need is to somehow split this record in my query to put anything before midnight as 1 record and anything after into another This example is pretty simple as it is half/half but some might start at 10pm and finish at 6pm so I would need 2 hours and 6 hours.
Not sure on the best way to do this, my initial thoughts was to create a cte where a start time is in 1 day and if the starttime + duration was in the next day then split the record.
Not sure if there is an easier way or if anyone has had to do this before.
Any help appreciated
#Joe has the right idea, here is pseudo-SQL
SELECT ID,StartTime,Duration,Qty,username
WHERE TRUNCATE(StartTime,DAY) = TRUNCATE(StartTime + Duration hours ,DAY)
UNION
SELECT ID,StartTime, TRUNCATE(StartTime,DAY) + 1 days - StartTime hours ,Qty,username
WHERE TRUNCATE(StartTime,DAY) < TRUNCATE(StartTime+Duration hours,DAY)
UNION
SELECT ID,TRUNCATE(StartTime+Duration hour,DAY),StartTime + duration hours - DATE(StartTime+Duration),Qty,username
WHERE TRUNCATE(StartTime,DAY) < TRUNCATE(StartTime+Duration hours,DAY)
Where TRUNCATE(timestamp,DAY) truncates a timestamp to YYYY-MM-DD 00:00:00
You can multiply rows with join. Make Tally table, simple table with numbers 1, 2, 3... and do a join. I will use table starting at zero here:
CREATE TABLE Tally0 (Number INT IDENTITY(0,1) PRIMARY KEY NOT NULL);
GO
INSERT INTO Tally0 DEFAULT VALUES;
GO 10000
Now the harders part is conversion between dates and numerics:
;WITH
tmp1 AS (SELECT *,
DATEDIFF(SECOND, CONVERT(DATE, StartTime), StartTime)/3600.0
+ DATEPART(NANOSECOND, StartTime)/(3600*1000000000.0) AS startingHours
FROM Record),
tmp2 AS (SELECT *,
startingHours + Duration AS endingHours,
(startingHours + Duration)/24.0 AS endingDays
FROM tmp1)
SELECT *,
CASE WHEN Number = 0 THEN StartTime
ELSE DATEADD(DAY, Number, CONVERT(DATE, StartTime))
END AS StartTime2,
CASE WHEN Number = 0 AND 1 < endingDays THEN 24 - startingHours
WHEN Number = 0 THEN Duration
WHEN Number + 1 < endingDays THEN 24
ELSE endingHours - Number * 24
END AS Duration2
FROM tmp2
JOIN Tally0 ON Number < endingDays
I am trying to add a filter condition in the DB2 database. I am new to it and come from an Oracle background. I am trying to get records with dates in between today at 4 AM and today at 5 PM only. I currently have the below query that returns zero results:
db2 => select datetimeColumn from datetimeExample WHERE datetimeColumn BETWEEN timestamp(current date) - 1 day + 4 hour AND timestamp(current date) - 1 day + 13 hour
DATETIMECOLUMN
--------------------------
0 record(s) selected.
And here is the data in the table that I believe should show but there is something wrong with condition statement, any help is appreciated
db2 => select * from datetimeExample
DATETIMECOLUMN
--------------------------
2016-06-16-09.38.53.759000
1988-12-25-17.12.30.000000
2016-12-25-17.10.30.000000
2016-06-16-04.10.30.000000
2016-06-16-05.10.30.000000
1988-12-25-15.12.30.000000
1988-12-25-14.12.30.000000
2016-06-16-12.10.30.000000
2016-06-16-07.10.30.000000
2016-06-16-08.10.30.000000
10 record(s) selected.
The query should work when you leave out the - 1 day. The reason is that timestamp(current date) returns the timestamp for today at zero hours. Then you add 4 hours and are at the required start time. Similar maths for the end time (and 5 pm should be + 17 hours).
select datetimeColumn from datetimeExample
WHERE datetimeColumn
BETWEEN timestamp(current date) + 4 hours AND timestamp(current date) + 17 hours