How can I truncate a timestamp to make readjustments to a date in Oracle? - sql

I have the following query in my SELECT from Oracle:
CASE
WHEN to_char(ola.promise_date, 'D') = 2 THEN (ola.promise_date - 4)
WHEN to_char(ola.promise_date, 'D') = 3 THEN (ola.promise_date - 4)
WHEN to_char(ola.promise_date, 'D') > 3 AND to_char(ola.promise_date, 'D') < 7 THEN (ola.promise_date - 2)
WHEN to_char(ola.promise_date, 'D') = 7 THEN (ola.promise_date - 2)
WHEN to_char(ola.promise_date, 'D') = 1 THEN (ola.promise_date - 2)
END mod_Promise_date
"ola.promise_date" is a column of type TIMESTAMP, and I use the CASE statement in order to adjust the date, considering the day of the week of the original promise date (because of internal considerations of shipment, among other things)
So, for example, something that has a promise date of 2021-11-07 00:00:00.0000000 will have a modified promise date of 2021-11-05 00:00:00.0000000 (considering Sunday as the seventh day)
Now, I run this query with a program made with C#, there I have a date picker in order to select specific promise dates, so the user can choose Sunday, November 7, 2021 and it will run the query as said before, this works good with the exception of promise dates that are something like 2021-11-07 23:59:00.0000000 because the query will consider the date as 8th of November instead of 7th, and this is not expected behavior, because the hour, minutes, seconds and fractional seconds are not really needed in this specific instance of the project.
Is there a way to ignore the hour, minutes, seconds and fractional seconds in the CASE-WHEN statement? Or floor down the date to consider everything in the same day as the day specified, without consideration of the time.
I'm pretty new using Oracle, so sorry if I'm not clear enough or if the query doesn't look viable.

You can use TRUNC(datetime_value) to truncate a DATE or a TIMESTAMP data type and set the time component to midnight:
CASE TRUNC(ola.promise_date) - TRUNC(ola.promise_date, 'IW')
WHEN 0 /* Monday */ THEN TRUNC(ola.promise_date) - 2
WHEN 1 /* Tuesday */ THEN TRUNC(ola.promise_date) - 4
WHEN 2 /* Wednesday */ THEN TRUNC(ola.promise_date) - 4
WHEN 6 /* Sunday */ THEN TRUNC(ola.promise_date) - 2
ELSE TRUNC(ola.promise_date) - 2
END mod_Promise_date
Note: TO_CHAR(datevalue, 'D') will give different values depending on where you are in the world as different territories consider the week to start on different days-of-the-week. If you want a territory-agnostic method of determining the day-of-the-week then you can find the difference from the start of the ISO week using TRUNC(datetime_value) - TRUNC(datetime_value, 'IW').

If I understood you correctly, you'll just have to truncate that value and "remove" time component (i.e. reset it to midnight).
For example:
(just setting format; you don't have to do that)
SQL> alter session set nls_timestamp_format = 'dd.mm.yyyy hh24:mi:ss.ff6';
Session altered.
Query that shows the result:
SQL> select systimestamp,
2 trunc(systimestamp) result
3 from dual;
SYSTIMESTAMP RESULT
----------------------------------- -------------------
05.11.21 19:29:41,207000 +01:00 05.11.2021 00:00:00
SQL>
You'd have
... THEN (trunc(ola.promise_date) - 4)

Related

Rolling back on query one day before SQL

I am trying to make a query in which for every weekday, it picks always the last business day, except for Mondays. On Mondays, it should always pick the last Friday.
To achieve this, it could be either done by selecting or creating an additional column that allows to identify these cases.
Examples:
If first_date = Tuesday then pick Monday
If first_date = Thursday then pick Wednesday
If first_date = Monday then pick Friday
Here my query:
SELECT name,
first_date,
last_day,
product,
TO_DATE(first_date) - TO_DATE(last_day) AS d,
CASE WHEN TO_CHAR(first_date, 'D') = '7' THEN -2
WHEN TO_CHAR(first_date, 'D') = '6' THEN -1
ELSE 0
END
FROM t1.mydata
WHERE d > 50
Any ideas on how to do this best?
You can use conditional with TO_CHAR conversion along with Dy (or Day) argument rather than using those integer representations, those might fail for some regions, for weekdays such that
SELECT CASE WHEN TO_CHAR(your_datecol,'Dy','NLS_DATE_LANGUAGE=English') ='Mon' THEN
'Fri'
WHEN TO_CHAR(your_datecol,'Dy','NLS_DATE_LANGUAGE=English') = 'Sun' THEN
Null
ELSE
TO_CHAR(your_datecol-1,'Dy','NLS_DATE_LANGUAGE=English')
END AS last_business_day
FROM your_table
ignoring the local offical vacancies.
You can use TRUNC(first_date) - TRUNC(fisrt_date, 'IW') to compare the date (truncated to midnight) to the start of the ISO week (which is always midnight on Monday). This will work regardless of the NLS_DATE_LANGUAGE or NLS_TERRITORY settings.
SELECT name,
first_date,
last_day,
product,
last_day - first_date AS d,
first_date
- CASE TRUNC(first_date) - TRUNC(fisrt_date, 'IW')
WHEN 0 THEN -3 -- Monday
WHEN 6 THEN -2 -- Sunday
ELSE -1 -- Other days
END AS prev_business_day
FROM t1.mydata
WHERE last_day - first_date > 50
Note: If you use TO_CHAR(first_date, 'D') then if the day is Monday then it will return: 1 in most of Europe; 2 in America; 3 in some Middle-Eastern countries; and 4 in Bangladesh as they all consider the first day of the week to be a different day db<>fiddle. This is based on the NLS_TERRITORY setting and is not something that can be overridden in the query.
Note: If you use TO_CHAR(first_date, 'Dy') then it will use the NLS_DATE_LANGUAGE session parameter to set the date language so your query would only work in one language. You would need to specify a third argument (i.e. TO_CHAR(first_date, 'Dy', 'NLS_DATE_LANGUAGE=English')) for it to work consistently in an International setting.

Right parenthesis error in oracle SQL Developer

SELECT titol, data_prestec
FROM if_llibres, if_llibre_prestec
WHERE
if_llibre_prestec.ubicacio = if_llibres.ubicacio
AND data_devolucio IS NULL
AND data_prestec <= date_sub(current_date(),interval 30 day);
There is no DATE_SUB() function in Oracle. You could phrase this as:
select
titol,
data_prestec
from if_llibres il
inner join if_llibre_prestec ilp
on ilp.ubicacio = il.ubicacio
where
data_devolucio is null
and data_prestec <= current_date - interval '30' day;
Note that I rewrote your query to use a standard, explicit join (with the on keyword) rather than an implicit join (with a comma in the from clause): this old syntax from decades ago should not be used in new code.
I would also recommend prefixing each column in the SELECT and WHERE clause with the (alias of the) table it belongs to: this makes the query unambiguous and easier to understand.
Subtracting 30 days smells like "previous month". If that was your intention, well, not all months have 30 days so you'll get wrong result. Note that Oracle offers the ADD_MONTHS function which allows you to subtract any number of months. In your case - 1 month.
Furthermore, current_date returns both date and time, so of you subtract 1 month (or 30 days, doesn't matter), you'll move back to that very second, not date itself.
Here's what I mean:
SQL> alter session set nls_Date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> select current_date as curdat,
2 current_date - interval '30' day as cur_30,
3 add_months(current_date, -1) as adm_1,
4 --
5 trunc(current_date) - interval '30' day as cur_trun_30,
6 add_months(trunc(current_date), -1) as adm_trun_1
7 from dual;
CURDAT CUR_30 ADM_1 CUR_TRUN_30 ADM_TRUN_1
------------------- ------------------- ------------------- ------------------- -------------------
14.06.2020 21:49:43 15.05.2020 21:49:43 14.05.2020 21:49:43 15.05.2020 00:00:00 14.05.2020 00:00:00
SQL>
CURDAT is current date (along with time)
CUR_30 subtracted 30 days; as you can see, you aren't on 14.05. but 15.05. at 21:49:43 so your query will return rows whose date value depends on that time as well
ADM_1 subtracted 1 month from current date, and it brought you back to 14.05. - again with the time component
CUR_TRUN_30 truncated current date (and "removed" the time component, setting time to midnight at beginning of that day), but you're again at 15.05. - not 14.05.
ADM_TRUN_1 truncated current date and set you to 14.05.2020 00:00 which is - I believe - what you wanted
Therefore, my suggestion would be
SELECT titol,
data_prestec
FROM if_llibres join if_llibre_prestec ON if_llibre_prestec.ubicacio = if_llibres.ubicacio
WHERE data_devolucio IS NULL
AND data_prestec <= ADD_MONTHS(TRUNC(current_date), -1);
Not related to your problems, but I'd suggest you to always use table aliases. The way you wrote the query, it is impossible to know which column belongs to which table and it makes confusion.

What is the difference between these 2 sql queries?

select * from accounts where sysdate - 100 < last_active_dt;
select * from accounts where sysdate - 100 <= last_active_dt;
Both of the queries returned the same result.
I was thinking that for the first query, it would not select
records on the day of last_active_dt. For the 2nd query, it
will select records on the day of last_active_dt.
But this does not seem to be the case as both returned the same results.
Is there any difference to it? And how can I see the difference?
last_active_dt column follows a date format DD-MON-YYYY
last_active_dt column follows a date format DD-MON-YYYY
No, it doesn't (but it may look like it does in the user interface you are using).
A DATE data type always has year, month, day, hour, minute and second components and is stored (without any formatting) in 7 bytes. It is the user interface that applies a (default) format to the date and this may hide the time component from you.
You can use:
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
To change the default date format used in SQL*Plus (and SQL Developer) to see the date with all the components.
Equally, SYSDATE is of a DATE data type and has year, month, day, hour, minute and second components so what you are probably intending is:
select * from accounts where TRUNC( sysdate ) - INTERVAL '100' DAY < last_active_dt;
This will get all the columns where the last_active_dt is after midnight of the day 100 days ago (even if it 1 second after). This will work if all your last_active_dt values have the times TRUNCated to exactly midnight 00:00:00 but not if the column as non-zero time components as you will still get values with times from 00:00:01 to 23:59:59 of the day 100 days ago.
If your last_active_dt has a non-zero time component then you want:
select * from accounts where TRUNC( sysdate ) - INTERVAL '99' DAY <= last_active_dt;

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

ORACLE Find Date ('DD-MON-YYYY') of a given weekday in the past 7 days

i am trying to create a view that compares a SCHEDULE table that has values such as ('Daily', 'Wednesday', 'Tuesday', etc..) and another table (REPORT CREATED) that is updated every day with dates (11-AUG-2017). Basically, if the Schedule table shows Daily, then the Report Created table record value should be whatever sysdate (current date) is equal to. That said, I'm not sure how to find out what the most recent 'Wednesday' or 'Tuesday' is equal to. I did find a function for SQL server (How to get Saturday's Date (Or any other weekday's Date)- SQL Server) ; however, I do not understand how it works and can't find an equivalent in Oracle. Any guidance would be greatly appreciated!
Edit: I have created two sample tables:
Schedules Table:
Report_Name | Frequency
ORDERS_BY_DEPT | Daily
LOW_STOCK | Wednesday
INVENTORY_DISC | Thursday
and the Reports Table:
Report_Name | Create_Dt
INVENTORY_DISC | 3-Aug-2017
LOW_STOCK | 9-Aug-2017
ORDERS_BY_DEPT | 10-Aug-2017
So essentially, the Inventory_Disc report is off, since it should have ran every Thursday but hasn't been updated since last Thursday and the Orders_By_Dept report is off since it is a daily report and didn't run today.
Use the NEXT_DAY( date_value, day_string ) function. Take the current day (SYSDATE) and subtract 7 days from it and then find the next day which matches your required day-of-the-week.
So, to get the most recent Wednesday:
SELECT NEXT_DAY( TRUNC( SYSDATE ) - 7, 'WEDNESDAY' )
FROM DUAL
You can find the day of the week with:
to_char(sysdate, 'D')
So for example, the last Thursday is:
select case
when to_char(sysdate, 'D') < 4 then sysdate - to_char(sysdate, 'D') - 7 + 4
else sysdate - to_char(sysdate, 'D') + 4
end
from dual