Find overlapping time between two timestamp in postgresql - sql

I have a table in postgers with schema similar to this.
id start_time end_time
1 2019-10-21 20:00:00 UTC 2019-10-21 23:00:00 UTC
2 2019-10-21 22:00:00 UTC 2019-10-22 02:00:00 UTC
I want to write a query which will give me overlapping duration in database itself.
for example: given two inputs
t1=2019-10-21 21:00:00 UTC
t2=2019-10-22 01:00:00 UTC
the query should calculate all the overlapping time between the input range
the overlapping time between input and row 1 is 2 hrs
the overlapping time between input and row 2 is 3 hrs
the result wold be 2 + 3 = 5

Sounds as if you are looking for the intersection of two tsrange values:
tsrange(start_time, end_time, '[]') * tsrange('2019-10-21 21:00:00', '2019-10-22 01:00:00', '[]')
The '[]' specifies that both edges should be included in the range. This might or might not be what you want. You will need to adjust that to your requirements.
This will return a range type. To calculate the length of the range, subtract the upper value from the lower value:
select sum(upper(diff) - lower(diff))
from (
select tsrange(start_time, end_time, '[]') * tsrange('2019-10-21 21:00:00', '2019-10-22 01:00:00', '[]') as diff
from the_table
) t
This returns an interval representing the sum of all overlap.
If your column is a timestamp with time zone you need to use tstzrange instead.
Online example

You can use date arithmetics and aggregation:
select
sum(
greatest(
least(d.end_time, t.end_time) - greatest(d.start_time, t.start_time),
'0 hour'::interval
)
) total_overlap
from mytable t
cross join (values
('2019-10-21 21:00:00'::timestamp, '2019-10-22 01:00:00'::timestamp)
) d(start_time, end_time)
For each row, the difference between the smaller end time and the greatest start time gives you the duration of the overlap - if it is positive.

Related

choose from a time period oracle sql

I have timestamp date format in column FIRST_DATE and i need to choose time period from a certain hour, for ex. all from 18:00 10.05.21 to 18:00 11.05.2021
the problem is that date column in timestamp format - FIRST_DATE:
10/05/2020 0:00:03,000000 TIMESTAMP(6)
so i tried to use it:
select
count(*)
from TABLE
where to_char(FIRST_DATE, 'HH24:MI')>='18:00'
so with this way i was able to limit the start period by time, but if i add date to this my conditions stop working
and to_char(FIRST_DATE, 'DD-MON-YY')>='10-MAY-21'
how can i correct my script to select all from 18:00 10.05.21 to 18:00 11.05.2021
Don't compare dates (or timestamps) with strings. '18:00' and '10-MAY-21' are strings. Use TO_TIMESTAMP with appropriate format mask, e.g. (lines #5 and 6):
SQL> with test (first_date) as
2 (select to_timestamp('10/05/2020 23:00:03,000000', 'dd/mm/yyyy hh24:mi:ss,ff3') from dual)
3 select *
4 from test
5 where first_date between to_timestamp('10/05/2020 18:00:00,000000', 'dd/mm/yyyy hh24:mi:ss,ff3')
6 and to_timestamp('11/05/2020 18:00:00,000000', 'dd/mm/yyyy hh24:mi:ss,ff3')
7 /
FIRST_DATE
---------------------------------------------------------------------------
10.05.20 23:00:03,000000000
SQL>

How to pass timestamp value in Oracle Stored Procedure?

I have a table column which has timestamp data type. How to pass timestamp value to the table column? I am passing to_timestamp('1240','HH:MI:SS') in oracle procedure. In DB table I have a value like 01-AUG-20 12.40.00.000000000 PM . I only want to store timestamp in the DB table. Is there a way? Please guide me on this. Thanks
You appear to be confused about what the values store. A TIMESTAMP data type stores a date and time with year, month, day, hour, minute, second and optional fractional seconds and time zone components; you cannot have a TIMESTAMP with just hour, minute and second components as it will always have year-day components.
If you want to store time then either:
Use an INTERVAL DAY TO SECOND data type (with zero days);
Use a DATE (or, if you want fractional seconds, TIMESTAMP) data type and set the year-day components to a fixed value (or ignore them);
Use a string in a fixed format; or
Store the number of seconds after midnight and use TO_DATE( value, 'SSSSS' ) to convert to a date and then TO_CHAR to format it as needed.
I would say that if you want to add times then use an INTERVAL data type as it will natively support that.
For example:
CREATE TABLE times1 ( value INTERVAL DAY TO SECOND );
INSERT INTO times1 ( value ) VALUES ( INTERVAL '12:40' HOUR TO MINUTE );
SELECT * FROM times1;
Which outputs:
| VALUE |
| :------------------ |
| +00 12:40:00.000000 |
If you want to display times then use a DATE and ignore the year-to-day components as you can easily format the time using TO_CHAR.
For example:
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
CREATE TABLE times2 ( value DATE );
INSERT INTO times2 ( value )
-- Fixed date
SELECT DATE '1970-01-01' + INTERVAL '12:40' HOUR TO MINUTE FROM DUAL UNION ALL
-- Today's date
SELECT TRUNC( SYSDATE ) + INTERVAL '12:40' HOUR TO MINUTE FROM DUAL UNION ALL
-- First of current month
SELECT TO_DATE( '12:40', 'HH24:MI' ) FROM DUAL;
SELECT value, TO_CHAR( value, 'HH24:MI' ) FROM times2;
Which outputs:
VALUE | TO_CHAR(VALUE,'HH24:MI')
:------------------ | :-----------------------
1970-01-01 12:40:00 | 12:40
2020-08-05 12:40:00 | 12:40
2020-08-01 12:40:00 | 12:40
db<>fiddle here

SQL- Math with Dates

I am working out of Oracle SQL. I have some dates that may have been poorly formatted when loading.
I'm doing a basic Max(date)-Min(Date) to get the difference in days. My results are:
+000000156 00:00:00.000000
+000000149 00:00:00.000000
+00 00:00:00.000000
I want to do some basic math with these date differences (average, etc) but I get an error message.
How do I convert these strings into numbers?
My guess is that the columns are timestamps, not dates, as the results are intervals not numbers. As you've found, Oracle have not got around to overloading the standard aggregate functions for intervals (vote for this feature on the Oracle Database Ideas forum) and currently you still have to either write your own or cast the timestamps to dates.
with demo (start_date, end_date) as
( select timestamp '2019-12-31 00:00:00', timestamp '2020-06-04 00:00:00' from dual union all
select timestamp '2020-01-31 00:00:00', timestamp '2020-06-28 00:00:00' from dual
)
select end_date - start_date as elapsed_interval
, cast(end_date as date) - cast(start_date as date) as elapsed_days
from demo;
ELAPSED_INTERVAL ELAPSED_DAYS
----------------------------- ------------
+000000156 00:00:00.000000000 156
+000000149 00:00:00.000000000 149
Basic math with dates:
date + number = date + number of days (also fractions)
SELECT SYSDATE + 1 FROM DUAL; -- tomorrow
date - number = date - number of days
SELECT SYSDATE - 1/24 FROM DUAL; -- one hour ago
date - date = numbers of days between dates (also fraction of days)
date + date = impossible
months_between(date1, date2) = returns months between two dates
add_months(date, number) = adds number (months) to date
if you have a string or number and it can be the n-th day of the year (for instance 156),
you can transform in date with TO_DATE('156', 'DDD')
if you have a string with a particular format, you can transform it in date with
TO_DATE(string, format of the date you imagine)
https://www.techonthenet.com/oracle/functions/to_date.php
if you need the opposite transform, that is transforming date to char (or number), use TO_CHAR(date, format of the date)

SQL Oracle Unix timestamp conversion

I have two queries which give me different results, can someone explain why this is happening?
The first query uses the unixtime 1533624035000 which represents "07.08.2018 08:40:35" (UTC+2)
select floor((Buchungsdatum - 1533624035000) / (1000*60*60*24)) as Tag,
s.Kurztext as Buchungsstatus,
count(*) as Anzahl
from PfdBuchung b, Schluesselbegriff s
where b.Buchungsdatum >= 1533624035000 and b.SHKennung = 'H' and
b.Buchungsstatus=s.Begriff and s.Oberbegriff='Buchungsstatus' and
b.rzMandant = s.rzMandant
group by floor((Buchungsdatum - 1533624035000) / (1000*60*60*24)), s.Kurztext
order by 1,2`
Result
0 verarbeitet 21800
1 verarbeitet 23380
i have just posted the first two results here
In the 2nd query I convert the unixtimestamp to a datetime with the function POSIX_TO_TIMESTAMP which simple converts my unixts to
l_ora_timestamp := to_timestamp( '1970-01-01 02:00:00', 'yyyy-mm-dd HH24:MI:SS' ) + numtodsinterval( (ptime/1000), 'SECOND' )
select TO_CHAR(POSIX_TO_TIMESTAMP(b.Buchungsdatum), 'YYYY-MM-DD') as Tag,
s.Kurztext as Buchungsstatus, count(*) as Anzahl
from PfdBuchung b, Schluesselbegriff s
where TO_CHAR(POSIX_TO_TIMESTAMP(b.Buchungsdatum), 'YYYY-MM-DD') >= '2018-08-07' and
b.SHKennung = 'H' and
b.Buchungsstatus = s.Begriff and
s.Oberbegriff = 'Buchungsstatus' and
b.rzMandant = s.rzMandant
group by TO_CHAR(POSIX_TO_TIMESTAMP(b.Buchungsdatum), 'YYYY-MM-DD'), s.Kurztext
order by 1,2
Result:
2018-08-07 verarbeitet 15553
2018-08-08 verarbeitet 23315
The reason is pretty obvious.
Both queries count rows where the UNIX timestamp is after 06:40:35 UTC on 7 August 2018.
The first query groups into windows of 24 hours each FROM THIS POINT IN TIME. That is, the first row in the output counts input rows from 06:40:35 on 7 August till same time on 8 August, etc.
The second query counts rows grouped by CALENDAR days (UTC), from midnight to midnight.
There is no reason for the counts to match.
In the second query, you count rows for 7 August, but only input rows with timestamp after 06:40:35 are selected - this is why you only get a count of about 15k, vs. the ~20k for all (full!) 24 hour windows.
And this has nothing to do with time zone. You may be in UTC+2 yourself, but I don't see where in your calculations there is ANY regard paid to timezone.
Your function POSIX_TO_TIMESTAMP will be wrong in winter season due to daylight saving time. Better use
l_ora_timestamp := (timestamp '1970-01-01 00:00:00 UTC' + numtodsinterval(ptime/1000, 'SECOND')) AT TIME ZONE 'Europe/Berlin';

SQL generating a set of dates

I am trying to find a way to have a SELECT statement return the set of dates between 2 input dates with a given interval. I would like to be able to easily change the time frame and interval that would be returned, so hard coding something with a series of SELECT ... UNIONs would not be ideal.
For example: I want all the dates at 5 second intervals for the last 60 seconds.
Expected:
times
---------------------
2009-02-05 08:00:00
2009-02-05 08:00:05
2009-02-05 08:00:10
2009-02-05 08:00:15
2009-02-05 08:00:20
...
2009-02-05 08:00:55
Edit:
generate_series(...) can be used in place of a table in the SELECT and simulates a table with a series of numbers in it with a given start value, end value and optionally a step. From there it can be CAST to the type I need for time functions and manipulated during the SELECT.
Thanks Quassnoi.
SELECT CAST (s || ' seconds' AS INTERVAL) + TIMESTAMP 'now'
FROM generate_series(0, -60, -5) s