Oracle's ( or not) weekday convention - sql

I have written a query for my co-workers to use, that involves pulling data from Oracle Express g11 on a given date.
The date that query uses is last weekday(yesterday or Friday).
For some reason same query returns different number to represent the day of the week.
Example:
SELECT TRUNC('13June2016') - TRUNC('13June2016', 'D')
FROM DUAL
When run on my computer this query returns 0 but on my co-worker's computer it returns 1. This happens on every SQL client/interface available to us.
To the best of my knowledge our computers and users in DB are setup the same but clearly some setting is off.
Frankly I tried to google where does this discrepancy comes from but I received a lot of unrelated results. I'm lost because I'd don't even know where to look for the setting(or bug not sure) that accounts for this difference.

The D format modifier relies on the session's NLS settings:
alter session set nls_territory = 'America';
alter session set nls_date_format = 'YYYY-MM-DD';
select to_char(date '2016-06-13', 'D') char_d,
trunc(date '2016-06-13', 'D') trunc_d,
date '2016-06-13' - trunc(date '2016-06-13', 'D') diff1,
trunc(sysdate) - trunc(sysdate, 'D') diff2
from dual;
C TRUNC_D DIFF1 DIFF2
- ---------- ---------- ----------
2 2016-06-12 1 1
alter session set nls_territory = 'United Kingdom';
alter session set nls_date_format = 'YYYY-MM-DD';
select to_char(date '2016-06-13', 'D') char_d,
trunc(date '2016-06-13', 'D') trunc_d,
date '2016-06-13' - trunc(date '2016-06-13', 'D') diff1,
trunc(sysdate) - trunc(sysdate, 'D') diff2
from dual;
C TRUNC_D DIFF1 DIFF2
- ---------- ---------- ----------
1 2016-06-13 0 0
(Interestingly in producing that I stumbled over bug 14073795 when using trunc(date '2016-06-13'); the trunc is redundant and it seems quite inconsistent - changing a column alias can make it appear or disappear - so I don't think it's relevant to what you're seeing).
So it seems that you and your colleague have your PCs in different locales, and the clients you tested in either inherited the locale or explicitly set the territory differently.
To get consistent results regardless of the locale/NLS settings you can use the 'IW' format element instead of 'D', as that gives:
Same day of the week as the first day of the calendar week as defined by the ISO 8601 standard, which is Monday
alter session set nls_territory = 'America';
alter session set nls_date_format = 'YYYY-MM-DD';
select to_char(date '2016-06-13', 'IW') char_iw,
trunc(date '2016-06-13', 'IW') trunc_iw,
date '2016-06-13' - trunc(date '2016-06-13', 'IW') diff1,
trunc(sysdate) - trunc(sysdate, 'IW') diff2
from dual;
CH TRUNC_IW DIFF1 DIFF2
-- ---------- ---------- ----------
24 2016-06-13 0 0
alter session set nls_territory = 'United Kingdom';
alter session set nls_date_format = 'YYYY-MM-DD';
select to_char(date '2016-06-13', 'IW') char_iw,
trunc(date '2016-06-13', 'IW') trunc_iw,
date '2016-06-13' - trunc(date '2016-06-13', 'IW') diff1,
trunc(sysdate) - trunc(sysdate, 'IW') diff2
from dual;
CH TRUNC_IW DIFF1 DIFF2
-- ---------- ---------- ----------
24 2016-06-13 0 0
You can read more about how the format models are used in trunc() and round() functions, and more generally about date format models.
Relying on implicit conversion is also not a good idea; TRUNC('13June2016', 'D') is implicitly converting the string '13June2016' to a date using your session's NLS_DATE_FORMAT setting. Running that in a session with a different setting will error. And TRUNC('13June2016') is redundant as the implicitly-converted date will already have its time set to midnight, if it doesn't error.
You should always either explicitly convert the string and specify the format model, e.g. TRUNC(TO_DATE('13June2016', 'DDMonthYYYY', 'NLS_DATE_LANGUAGE=ENGLISH'), 'D') (where the third argument is needed in case the session date language is something else, which would stop June being recognised; or more simply use an ISO date literal like DATE '2016-06-13'. You may be using a datetime variable in your real code of course.

Related

Trying to convert datetime in date in oracle

Hi i'm trying to convert date 01-03-2020 10:48:27 which obtained from query
SELECT
LAST_DAY( ADD_MONTHS(SYSDATE,-3 ) )+1
FROM
dual;
into '01-Mar-2020' but not able to do trying many concept
eg.
trunc(SELECT LAST_DAY( ADD_MONTHS(SYSDATE , - 3 ) )+1 FROM dual),'YEAR')
and
SELECT TRUNC(TO_DATE('SELECT LAST_DAY( ADD_MONTHS(SYSDATE , - 3 ) )+1 FROM dual','DD-MON-YY'), 'YEAR') "New Year" FROM DUAL;
but getting error
Any idea would be appreciated
You're making things way too complicated. Oracle TRUNC takes an additional parameter to specify whatever time interval to truncate to:
SELECT TRUNC(some_date_here, 'MON') FROM dual
If you put some_date_here as sysdate, then currently it will return 01-May-2020 until next month when it starts returning 01-Jun-2020
You can truncate to any interval; TRUNC 01/01/2000 12:34:56 with 'MI' will return 01/01/2000 12:34:00. Truncating to DD is the default (cut the time off). Truncating to DAY sets the date back to the day that started the week in the country oracle thinks it is in (probably a Sunday or Monday)
More info: https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions230.htm#i1002084
As I understood your problem you want to go from the current date, to the first of the month that was between 2 and < 3 months ago (so if it's May now, you want to go back to first of March until it's June, when you want to go back to first of April)
If you hence, in the current date of 5th May, want to go back to a date of 1 March, take 2 months off the current date and then TRUNC to the start of the month:
SELECT TRUNC(ADD_MONTHS(sysdate, -2), 'MON') FROM dual
Don't forget you can TRUNC to the nearest quarter of a year, so if you're doing a report that is "the current quarter", then looking at a variation of TRUNC(sysdate, 'Q') would be the way to go
Lastly, I'd urge you NOT to use oracle to convert your dates to strings (in most cases) - if you keep it as a date all the way 'tIl it hits the user's computer it can be formatted for their regional preferences. If you make a decision as to the format as its coming out the dB it makes it much harder to deliver a good international experience for your app
"Convert" in your case means TO_CHAR; alter session is here to set default format for this session.
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> select
2 last_Day(add_months(sysdate, -3)) + 1 orig,
3 to_char(last_day(add_months(sysdate, -3)) + 1, 'dd-Mon-yyyy', 'nls_Date_language = english') result
4 from dual;
ORIG RESULT
------------------- --------------------
01.03.2020 07:25:44 01-Mar-2020
SQL>
Or, if you altered the session, you'd get it as
SQL> alter session set nls_date_language = 'english';
Session altered.
SQL> alter session set nls_date_format = 'dd-Mon-yyyy';
Session altered.
SQL> select
2 last_Day(add_months(sysdate, -3)) + 1 orig
3 from dual;
ORIG
-----------
01-Mar-2020
SQL>
But, yes - usually we TO_CHAR it.

SQL - Julien Date (CYYDDD) to date

Unfortunately, this is my first approach with SQL!
I am creating with the following code a query between an oracle DB and Excel (Power Query).
select "$Table"."Order" as "Order",
"$Table"."NR" as "Nr",
"$Table"."JDDATE" as "JDDATE"
from "POOLDB2"."3112" "$Table"
WHERE "Key" >118001
AND "CodeAA" = 1
This code works!
Now I want to format the Julian Date (CYYDDD) - for example 118001 for the 01.01.2019 - to a normal date format.
Does anyone know, how to implement this into the code above?
Maybe something like :
select "$Table"."Order" as "Order",
"$Table"."NR" as "Nr",
DATEADD(DAY, JDDATE % 1000 - 1, DATEADD(year, JDDATE/1000, 0))
"$Table"."JDDATE" as "JDDATE"
from "POOLDB2"."3112" "$Table"
WHERE "Key" >118001
AND "CodeAA" = 1
Best regards
There are many different formats for Julian Date... In your use case, this should do it :
with t as (select 118001 jd from dual)
select to_char( to_date(to_char(1901 + floor(jd / 1000)),'YYYY') + mod(jd,1000) - 1, 'dd.mm.yyyy' ) from t
Yields : 01.01.2019
For Oracle,
select to_char(sysdate,'J') from dual; --To Julian Date
select to_date(2456143,'J') from dual; --To Normal Date
must work.
Edit: Sorry I didn't see oracle tag.
Edit: For the requested behavior by OP
select to_date(to_char(1901 + floor(118001 / 1000)),'YYYY') from dual;
You can use the 118001 value you have, split into separate year and day sections, by adding to the nominal starting date 1900-01-01 (based on your comment that 118001 is actually 2018-01-01, not 2019-01-01):
select date '1900-01-01'
+ floor(118001 / 1000) * interval '1' year
+ (mod(118001, 1000) - 1) * interval '1' day
from dual;
DATE'1900-
----------
2018-01-01
or by startng the fixed date a day earlier you can remove the explicit -1:
select date '1899-12-31'
+ floor(118019 / 1000) * interval '1' year
+ mod(118019, 1000) * interval '1' day
from dual;
DATE'1899-
----------
2018-01-19
This avoids having to build up a longer string to convert to a date, though you could do that (modifying #GMB's approach) as:
select to_date(to_char(1900 + floor(118001 / 1000)) || '-01-01', 'YYYY-MM-DD')
+ (mod(118001, 1000) - 1)
from dual;
You need to specify the month, at least, in the to_date() call as Oracle defaults to the current month if that is not supplied. That behaviour is tucked away in the documentation:
If you specify a date value without a time component, then the default time is midnight. If you specify a date value without a date, then the default date is the first day of the current month.
The first part of that is fairly well known and makes sense ; the second part is a bit less obvious, and doesn't make it clear that it applies to partial dates too - so ifyou don't supply a year then the current year is used; if you don't supply a month then the current month is used; but if you don't supply a day then the 1st is used.
You can see what it's doing with some test conversions:
select to_date('2018-12-25', 'YYYY-MM-DD') as demo_a,
to_date('12:34:56', 'HH24:MI:SS') as demo_b,
to_date('2019', 'YYYY') as demo_c,
to_date('07-04', 'MM-DD') as demo_d,
to_date('2019-01', 'YYYY-MM') as demo_e
from dual;
DEMO_A DEMO_B DEMO_C DEMO_D DEMO_E
------------------- ------------------- ------------------- ------------------- -------------------
2018-12-25 00:00:00 2018-12-01 12:34:56 2019-12-01 00:00:00 2018-07-04 00:00:00 2019-01-01 00:00:00

How to change the week start day as sunday for database in oracle for reporting purpose

Have tried changing NLS_TERRITORY and NLS_LANGUAGE still database is showing Monday as starting week.
Alter session set nls_territory = 'America'
select TRUNC(sysdate, 'iw') AS iso_week_start_date,
TRUNC(sysdate, 'iw') + 7 - 1/86400 AS iso_week_end_date
from dual;
2 3
ISO_WEEK ISO_WEEK
--------- ---------
20-AUG-18 26-AUG-18
Here the ISO_WEEK_START_DATE should start from 19-AUG-2018 which is sunday
TRUNC(sysdate, 'iw') returns first day of week according to ISO-8601 which is always Monday. For local weeks use TRUNC(sysdate, 'WW') which depends on current user session NLS_TERRITORY

Sql queries regarding dates

select sysdate
from dual;
SYSDATE
-------
03-May-17
select sysdate -1
from dual;
SYSDATE
-------
02-May-17
==============
If the run the below sql query
select TO_DATE('01-DEC-15','DD-MON-YYYY') - 1
FROM DUAL;
I am getting the answer
30-Nov-15
=====
Can any body help me understand this behaviour?
The documentation has a section on datetime/interval arithmetic. A number added to or subtracted from a date is treated as a fraction of a day:
For example, SYSDATE + 1 is tomorrow. SYSDATE - 7 is one week ago. SYSDATE + (10/1440) is ten minutes from now
As #mathguy pointed out, all dates in Oracle have a time component, and sysdate has the current server time. You can see what's happening if you display the full value of the date/time. You can do that explicitly with to_char() and a suitable format, but for brevity here I'll alter my session NLS settings:
alter session set nls_date_format = 'SYYYY-MM-DD HH24:MI:SS';
select sysdate, sysdate - 1
from dual;
SYSDATE SYSDATE-1
-------------------- --------------------
2017-05-03 16:17:47 2017-05-02 16:17:47
Both show the same time, but on different days.
You can truncate a date to reduce it's precision; by default trunc(sysdate) zeros the time elements so it becomes midnight today rather than the current time.
Your third example is slightly more interesting because you are using mismatching values and format masks; with a 2-digit year and a 4-digit YYYY mask the year doesn't end up as you might have expected:
select to_date('01-DEC-15','DD-MON-YYYY'), to_date('01-DEC-15','DD-MON-YYYY') - 1
from dual;
TO_DATE('01-DEC-15', TO_DATE('01-DEC-15',
-------------------- --------------------
0015-12-01 00:00:00 0015-11-30 00:00:00
That probably isn't what you wanted. The RRRR mask is more forgiving, as described in the manual:
select to_date('01-DEC-15','DD-MON-RRRR'), to_date('01-DEC-15','DD-MON-RRRR') - 1
from dual;
TO_DATE('01-DEC-15', TO_DATE('01-DEC-15',
-------------------- --------------------
2015-12-01 00:00:00 2015-11-30 00:00:00
but it would be better to provide a full 4-digit year anyway:
select to_date('01-DEC-2015','DD-MON-YYYY'), to_date('01-DEC-2015','DD-MON-YYYY') - 1
from dual;
TO_DATE('01-DEC-2015 TO_DATE('01-DEC-2015
-------------------- --------------------
2015-12-01 00:00:00 2015-11-30 00:00:00
or even more simply, use a date literal:
select date '2015-12-01', date '2015-12-01' - 1
from dual;
DATE'2015-12-01' DATE'2015-12-01'-1
-------------------- --------------------
2015-12-01 00:00:00 2015-11-30 00:00:00
This also avoids issues with the date language; 'APR' won't be recognised in sessions where the language - specifically NLS_DATE_LANGAUGE - isn't English (unless it's coincidentally a recognised abbreviation ion another language, but some months will still fail). It's better to use month numbers. If you must use names or abbreviations, you can specify the language they are in with the optional third argument to to_date(). But literals are still easier if you aren't converting from a string variable.

SYSDATE but specify the time

I want to say the follow but substitute the date with SYSDATE but the time between is what I want to adjust. What would the syntax be?
where mydatefield between SYSDATE+'0001' and SYSDATE+'2359'
...
WHERE TO_CHAR( MOPACTIVITY.MOPNOTIFICATIONSENDAT , 'yyyy-mm-dd hh24:mi' )
BETWEEN '2013-07-26 00:00:01' AND '2013-07-26 23:59:59'
;
SYSDATE (or any other date column) in Oracle has the time component. So you need to strip that off and then add the hours/minutes/time condition.
Eg. to say current day 10:00 AM to 3:00 PM, you can say
date_column between (trunc(sysdate) + 10/24) and (trunc(sysdate) + 15/24)
Oracle date arithmetic works on the day level. so, +1 will give you the next day, 1/24 will give you an hour and 10/24 will give you 10:00 AM in the current day.
SQL> alter session set nls_date_format = 'DD-Mon-YYYY HH:MI:SS AM';
Session altered.
1 select sysdate,
2 trunc(sysdate),
3 trunc(sysdate) + 10/24,
4 trunc(sysdate) + 15/24
5* from dual
SQL> /
SYSDATE 26-Jul-2013 06:26:07 PM
TRUNC(SYSDATE) 26-Jul-2013 12:00:00 AM
TRUNC(SYSDATE)+10/24 26-Jul-2013 10:00:00 AM
TRUNC(SYSDATE)+15/24 26-Jul-2013 03:00:00 PM
For your question, you seem to be interested between current day and next day, so you can try adding + 1 to the date directly, once you strip the time component.
date_column >= trunc(sysdate) and
date_column < trunc(sysdate)+1
The best way to do this is to leave your MOPACTIVITY.MOPNOTIFICATIONSENDAT as a DATE type. That allows Oracle to optimize the query if there happens to be an index on the column. I'd recommend something like this:
WHERE MOPACTIVITY.MOPNOTIFICATIONSENDAT >= TRUNC(SYSDATE)
AND MOPACTIVITY.MOPNOTIFICATIONSENDAT < TRUNC(SYSDATE) + 1
That boils down to "greater than or equal to today at midnight" and "less than tomorrow at midnight".
We can also trunc both the dates and then compare the result
where TRUNC(MOPACTIVITY.MOPNOTIFICATIONSENDAT) = TRUNC(SYSDATE)
TRUNC Removes the timestamp from the dates