Adding an interval of 1 calendar month to a date - vertica

I would like to add 1 calendar month to a date, ignoring number of days in the month. i.e. add_month('2015-02-23') returns 2015-03-23 and add_month('2015-05-23') returns 2015-06-23
It seems like I could use INTERVAL '1 month' to do this, but I was surprised to find that whenever I do this, it adds 30 days to my input i.e. functionally the same as INTERVAL '30 days'. Does this happen for you too? What should I do instead to increment by 1 calendar month?
Examples:
SELECT DATE('2015-04-23') + INTERVAL '1 month'
returns 2015-05-23
while
SELECT DATE('2015-05-23') + INTERVAL '1 month'
returns 2015-06-22!

This behavior is due to Vertica being modeled on SQL 2008 in which 1 MONTH is a static 30 days, not a "smart month."
dbadmin=> SELECT INTERVAL '1 MONTH';
?column?
----------
30
(1 row)
In order to get the desired behavior, you should use INTERVALYM:
dbadmin=> SELECT INTERVALYM '1 MONTH';
?column?
----------
0-1
(1 row)
dbadmin=> SELECT DATE('2015-05-23') + INTERVALYM '1 MONTH';
?column?
---------------------
2015-06-23 00:00:00
(1 row)
Read more

Related

calculate number of saturdays+sunday total count in oracle

i have a query and I want to calculate the number of sat+sun total count in oracle, for example, I have a query pasted below there should be a total count of Saturday and Sunday, how can I achieve that please help, I really appreciate any help you can provide.
SELECT TO_DATE('01-12-2022','dd-mm-yyyy') start_date , TO_DATE(sysdate) end_date
FROM dual;
Don't use a row-generator to create a calendar (as it is very inefficient); just calculate the number by calculating the number of full weeks and then deal with the part weeks at the start and end of the range:
WITH range (start_date, end_date) AS (
SELECT DATE '2022-12-01', TRUNC(SYSDATE) FROM DUAL
)
SELECT -- Number of full weeks
(TRUNC(end_date, 'IW') - TRUNC(start_date, 'IW')) * 2/7
-- Number of weekend days in final week
+ GREATEST(end_date - TRUNC(end_date, 'IW') - 4, 0)
-- Number of weekend days in before first week
- GREATEST(start_date - TRUNC(start_date, 'IW') - 5, 0)
AS weekend_day_count
FROM range;
Which outputs:
WEEKEND_DAY_COUNT
8
fiddle
One option is to create a calendar between these two dates and then count number of Saturdays and Sundays:
SQL> with
2 test (start_date, end_date) as
3 -- period
4 (select date '2022-12-01', date '2022-12-29' from dual),
5 calendar as
6 -- calendar (all dates between START_DATE and END_DATE)
7 (select start_date + level - 1 as datum
8 from test
9 connect by level <= end_date - start_date + 1
10 )
11 -- number of Saturdays and Sundays
12 select count(*)
13 from calendar
14 where to_char(datum, 'dy', 'nls_date_language = english') in ('sat', 'sun');
COUNT(*)
----------
8
SQL>
You'd change dates at line #4.
P.S. If you look at code MT0 posted and their objection that row generator is inefficient, that's true. Although both queries return the same result, timing is different. For example:
Period 01.01.2022 - 31.12.2022 01.01.1900 - 31.12.2022 01.01.0001 - 31.12.2022
------ ----------------------- ----------------------- -----------------------
LF 00:00:00.00 00:00:00.17 00:00:03.72
MT0 00:00:00.02 00:00:00.02 00:00:00.05
It is obvious that my timing gets worse with period length. If you're looking at one year or a century, the difference is mostly irrelevant. For 2000 years, the difference is huge!
However, if you consider debugging, from my own point of view, my code is easier to read: "select number of rows from the calendar where date is either saturday or sunday" - plain English.
On the other hand, the other code isn't that straightforward; truncate date to week, subtract them, multiply by 2/7 (why "2/7" and not 4/9?), add result returned by the GREATEST function minus 4 (why 4? Why not 7?), subtract GREATEST of something minus 5 (why 5? Why not 2?) - as I said, that's NOT easy to read nor understand.
Therefore, it depends on what you actually need, timing vs. readability. Pick one :)

Where clause from a subquery

I have a table with business days BUSINESS_DAYS which has all the dates
I have another table with payment information and DUE_DATES
I want to return in my query the next business day IF the DUE_DATE is not a business day
SELECT SQ1.DUE_DATE, SQ2.DATE FROM
(SELECT * FROM
PAYMENTS
ORDER BY
DUE_DATE) SQ1,
(SELECT MIN(DATE) DATE FROM BUSINESS_DAYS WHERE SQ1.DUE_DATE <= DATE GROUP BY DATE) SQ2
Anyone can shed some light?
The way I see it, code you posted doesn't do what you wanted anyway (otherwise, you won't be asking a question at all). Therefore, I'd suggest another approach:
Altering the session (you don't have to do it; my database speaks Croatian so I'm switching to English; also, setting date format to display day name):
SQL> alter session set nls_date_language = 'english';
Session altered.
SQL> alter session set nls_date_format = 'dd.mm.yyyy, dy';
Session altered.
Two CTEs contain
business_days: as commented, only this year's July, weekends excluded, there are no holidays)
payments: two rows, one whose due date is a working day and another whose isn't
Sample data end at line #15, query you might be interested in begins at line #16. Its CASE expression check whether due_date is one of weekend days; if not, due date to be returned is exactly what it is. Otherwise, another SELECT statement returns the first (MIN) business day larger than due_date.
SQL> with
2 business_days (datum) as
3 -- for simplicity, only all dates in this year's July,
4 -- weekends excluded (as they aren't business days), no holidays
5 (select date '2021-07-01' + level - 1
6 from dual
7 where to_char(date '2021-07-01' + level - 1, 'dy')
8 not in ('sat', 'sun')
9 connect by level <= 31
10 ),
11 payments (id, due_date) as
12 (select 1, date '2021-07-14' from dual -- Wednesday, business day
13 union all
14 select 2, date '2021-07-25' from dual -- Sunday, non-business day
15 )
16 select p.id,
17 p.due_date current_due_date,
18 --
19 case when to_char(p.due_date, 'dy') not in ('sat', 'sun') then
20 p.due_date
21 else (select min(b.datum)
22 from business_days b
23 where b.datum > p.due_date
24 )
25 end new_due_date
26 from payments p
27 order by id;
ID CURRENT_DUE_DAT NEW_DUE_DATE
---------- --------------- ---------------
1 14.07.2021, wed 14.07.2021, wed --> Wednesday remains "as is"
2 25.07.2021, sun 26.07.2021, mon --> Sunday switched to Monday
SQL>

Oracle: splitting time range into days and calculation of duration

I'm developing code calculation service availability based on events, so I need to split events into daily "sub-events" and calculate duration of then.
So as input I have set of events like (EVENT_ID, START_TIME, END_TIME):
'event1';2021-05-01 12:30;2021-05-01 13:00
'event2';2021-05-03 10:55;2021-05-05 12:01
As output I'd like to get (EVENT_ID, DAY, DURATION_MINUTES):
'event1'; 2021-05-01; 30
'event2'; 2021-05-03; 785
'event2'; 2021-05-04; 1440
'event2'; 2021-05-05; 721
I can get it using procedures and cursor but this is not effective (the events database is quite big), so is there a way to do it using oracle sql query ? Any idea?
You appear to want a recursive query:
WITH days ( event_id, day, start_time, end_time ) AS (
SELECT event_id,
TRUNC( start_time ),
start_time,
end_time
FROM table_name
UNION ALL
SELECT event_id,
day + INTERVAL '1' DAY,
start_time,
end_time
FROM days
WHERE day + INTERVAL '1' DAY < end_time
)
SELECT event_id,
day,
ROUND(
(
LEAST(end_time, day + INTERVAL '1' DAY)
- GREATEST(start_time, day)
) * 24 * 60
) AS duration_minutes
FROM days
Which, for the sample data:
CREATE TABLE table_name ( event_id, start_time, end_time ) AS
SELECT 'event1', DATE '2021-05-01' + INTERVAL '12:30' HOUR TO MINUTE, DATE '2021-05-01' + INTERVAL '13:00' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 'event2', DATE '2021-05-03' + INTERVAL '10:55' HOUR TO MINUTE, DATE '2021-05-05' + INTERVAL '12:01' HOUR TO MINUTE FROM DUAL;
Outputs:
EVENT_ID
DAY
DURATION_MINUTES
event1
2021-05-01
30
event2
2021-05-03
785
event2
2021-05-04
1440
event2
2021-05-05
721
db<>fiddle here
If your Oracle version is 12 or higher, you can use a lateral join (in any of several equivalent formulations/syntaxes) to make the query faster. For example (using the table set up in MT0's answer):
select event_id, day, round(1440 * duration_days) as duration_minutes
from table_name cross join lateral
( select trunc(start_time) + level - 1 as day,
case when level = 1 and connect_by_isleaf = 1
then end_time - start_time
when level = 1 then 1 - (start_time - trunc(start_time))
when connect_by_isleaf = 1 then end_time - trunc(end_time)
else 1 end as duration_days
from dual
connect by level <= 1 + trunc(end_time) - trunc(start_time)
)
where duration_days != 0
order by event_id, day
;
The where clause is used when the end_time is midnight (at the beginning of an otherwise "new" day); in that case, presumably, you don't want to include that "new day" in the output, with a duration of 0 minutes.
In the lateral view, level = 1 corresponds to the first date in the interval, while connect_by_isleaf = 1 is for the last date in the interval. A special calculation is made when the end_time and start_time are on the same date. The query computes the difference in days first, then converts to minutes. Note that date calculations aren't 100% precise; I used round so I don't get results like 33.9999999999938020 minutes. If the inputs are in hh24:mi, we know beforehand that the answer (in minutes) should be an integer, so round seems fine there.

Use EPOCH time for timestamp to get records within 1 minute

I was curious to see how in Oracle 12c you can take a timestamp datatype and convert the records into EPOCH time to make them a number and then use that number to find any records within that date column that are within 1 minute of each other (assuming the same day if needed, or simply any calculations within 1 minute).
I tried the following but got an ORA-01873: the leading precision of the interval is too small error.
select (sold_date - to_date('1970-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS'))*86400 as epoch_sold_date from test1;
What is SOLD_DATE? For e.g. SYSDATE (function that returns DATE datatype), your code works OK.
SQL> select (sysdate
2 - to_date('1970-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS')
3 ) * 86400 as epoch_sold_date
4 from dual;
EPOCH_SOLD_DATE
---------------
1600807918
SQL>
As SOLD_DATE is a timestamp, but - it appears that fractions of a second aren't or special interest to you, cast it to DATE:
select (cast (systimestamp as date) --> this
- to_date('1970-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS')
) * 86400 as epoch_sold_date
from dual;
Saying that you get the same result for all rows: well, I don't, and you shouldn't either if SOLD_DATE differs.
SQL> with test (sold_date) as
2 (select timestamp '2020-09-22 00:00:00.000000' from dual union all
3 select timestamp '2015-03-18 00:00:00.000000' from dual
4 )
5 select sold_date,
6 (cast (sold_date as date)
7 - to_date('1970-01-01 00:00:00','YYYY-MM-DD HH24:MI:SS')
8 ) * 86400 as epoch_sold_date
9 from test;
SOLD_DATE EPOCH_SOLD_DATE
------------------------------ ---------------
22.09.20 00:00:00,000000000 1600732800
18.03.15 00:00:00,000000000 1426636800
SQL>
One more edit: when you subtract two timestamps, result is interval day to second. If you extract minutes from it, you get what you wanted:
SQL> with test (sold_date) as
2 (select timestamp '2020-09-22 10:15:00.000000' from dual union all
3 select timestamp '2015-03-18 08:05:00.000000' from dual
4 )
5 select sold_date,
6 lead(sold_date) over (order by sold_date) next_sold_date,
7 --
8 lead(sold_date) over (order by sold_date) - sold_date diff,
9 --
10 extract(day from lead(sold_date) over (order by sold_date) - sold_date) diff_mins
11 from test
12 order by sold_date;
SOLD_DATE NEXT_SOLD_DATE DIFF DIFF_MINS
------------------------------ ------------------------------ ------------------------------ ----------
18.03.15 08:05:00,000000000 22.09.20 10:15:00,000000000 +000002015 02:10:00.000000000 2015
22.09.20 10:15:00,000000000
SQL>
In your case, you'd check whether extracted minutes value is larger than 1 (minute).
If you just want to see how many minutes are there between two timestamps, then
cast them to dates
subtract those dates (and you'll get number of days)
multiply it by 24 (as there are 24 hours in a day) and by 60 (as there are 60 minutes in an hour)
Something like this:
SQL> with test (date_1, date_2) as
2 (select timestamp '2020-09-22 10:15:00.000000',
3 timestamp '2020-09-22 08:05:00.000000' from dual
4 )
5 select (cast(date_1 as date) - cast(date_2 as date)) * 24 * 60 diff_minutes
6 from test;
DIFF_MINUTES
------------
130
SQL>
If you are just looking to compare dates and find rows that are within one minute of each other, you do not need to use epoch time. There are several solutions to this problem on this thread.

Selecting every 3 seconds data rows between 2 dates

I have a requirement to take every 3 seconds data within the specific time interval in SQL. I am new to SQL so can anyone help me on the scenario
This is my select query which returns all the values but i need data for every 3 seconds only
SELECT ton_nbr
FROM
icr_file_interface
WHERE
(
reading_dttm BETWEEN
TO_DATE(concat('2016-10-19',to_char(0930)),'yyyy-mm-dd HH24MISS')
AND TO_DATE(concat('2016-10-19',to_char('0945')),'yyyy-mm-dd HH24MISS')
)
AND
(
ton_nbr BETWEEN
(SELECT value FROM text_para WHERE para_cd='ICR_ST_RNG')
AND (SELECT value FROM text_para WHERE para_cd='ICR_ED_RNG')
)
If you only need to subtract 3 seconds from a date, you can use the following:
SQL> select to_char(sysdate, 'yyyy-mm-dd hh24:mi:ss'),
2 to_char(sysdate - 3*1/24/60/60, 'yyyy-mm-dd hh24:mi:ss')
3 from dual;
TO_CHAR(SYSDATE,'YY TO_CHAR(SYSDATE-3*1
------------------- -------------------
2016-10-19 09:38:17 2016-10-19 09:38:14
Given that sysdate -1 means "subtract one day to sysdate", you can derive the number of seconds you need with a bit af arithmetic
This selects you data between last 3 seconds. Hope you got the idea.
select ton_nbr
from icr_file_interface
where reading_dttm between dateadd(ss, -3, getdate()) and getdate() ;

Resources