sql condition to not include 4712 date - sql

I always have an issue with date transformation. Can someone guide and help me understanding the date transformation.
I am using the below code in Oracle Fusion HCM Extract tool and I am getting the correct output
APPROVAL_STATUS_CD='APPROVED'
AND ABSENCE_STATUS_CD in ('SUBMITTED','ORA_WITHDRAWN')
and typetl.name != 'Banked Time - Disbursement'
and (TO_DATE(trunc(start_date) ,'YYYY-MM-DD')
>= TO_DATE((select trunc((sysdate),'month') as FirstDay from dual),'YYYY-MM-DD'))
but it is giving me data that has start_date as '4712-12-31' as well. I do not want this in my output. as soon as i add the below condition -
and (TO_DATE(trunc(start_date) ,'YYYY') != TO_DATE('YYYY','4712'))
I am not getting any output. How do I restrict the 4712 date in the start_Date column i.e. whichever data has 31-12-4712 in start_date should not come in output.

Assuming that there will be no higher values then you want:
AND start_date < DATE '4712-12-31'
Note: NEVER use TO_DATE on a value that is already a DATE data type.
Which would make your query:
WHERE APPROVAL_STATUS_CD='APPROVED'
AND ABSENCE_STATUS_CD in ('SUBMITTED','ORA_WITHDRAWN')
AND typetl.name != 'Banked Time - Disbursement'
AND start_date >= TRUNC(SYSDATE,'MM')
AND start_date < DATE '4712-12-31'

If you don't supply all the date elements then Oracle defaults to the first day of the current month; so TO_DATE('YYYY','4712') evaluates to 4712-04-01, not 4712-12-31 or 4712-01-01.
If you want a fixed date then it's easier to use a literal: DATE '4712-12-31', or possibly - given the range of valid dates Oracle allows - you really want DATE '-4712-01-01' (or DATE '-4712-12-31'). I'd check the full actual value you have in your data with TO_CHAR(start_date, 'SYYYY-MM-DD'). That will show you if it's BC/BCE (with a negative value) or AD/CE (with a positive value).
Also, do not use TO_DATE() for a value that is already a date; it might work, or it might do odd things. You don't need to do that. When you do TO_DATE(trunc(start_date) ,'YYYY-MM-DD') you're implicitly doing TO_DATE(TO_CHAR(trunc(start_date), <NLS_DATE_FORMAT>) ,'YYYY-MM-DD') - which relies on the current session's NLS settings. Even if it works today, for you, it will break one day someone else.
Just trunc(start_date) and `trunc(sysdate, 'month') is enough. Though there's no point truncating the start_date really - if the truncated value is after the start of the month, so is the original non-truncated value.

Related

Query with CASE WHEN / LAST_DAY is not giving any output

Running below query and not getting the output. Can someone please tell whats wrong in it?
Select distinct (table.datex)
from table
where table.datex =
(
CASE when extract( day from sysdate) >=19
then last_day(add_months(sysdate, -1))
else last_day(add_months(sysdate, -2))
END
)
Sample data
Datex
ID
30-JUN-21
A
31-MAY-21
B
29-JUN-21
C
Expected result
Datex
30-JUN-21
When I am passing the value hard-coded(calculated by the case) to where clause it's working fine, but when I apply the case it's not working. No error. No output is coming.
Date or datetime?
Oracle's LAST_DAY doesn't do what the name suggests, and the docs (https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/LAST_DAY.html#GUID-296C7C02-7FB9-4AAC-8927-6A79320CE0C6) fail to explain that, too.
Unlike several other DBMS Oracle doesn't have a date type. It only has a datetime type and they even call that inappropriately DATE. This means that a "date" in Oracle always has a time part. A date with its time part set to 00:00:00 can be considered a day's midnight (i.e. the very beginning of the day) or the whole day.
The function SYSDATE gives us a date in the sense of the DATE datatype, not in the sense of a real day, i.e. it gives us the datetime of "now", e.g. 2021-07-20 14:38:00. ADD_MONTHS changes the month in that datetime (and sometimes the year and sometimes even the day), i.e. leaves the time part untouched. LAST_DAY, too, changes the date part to get to the last day of the month, but leaves the time part untouched.
Your CASE expression hence results in something like TIMESTAMP '2021-07-20 14:38:00' and not in DATE '2021-07-20' as one might expect.
You say that you tried your query with the date you computed wth your case expression, and it worked. Did you compute the resulting day in your head or with a query? If the latter: The tool you are using may be set to only display a datetime's date part and omit the time part. This would explain why you only saw 30-JUN-21 when checking the CASE expression.
Solution
Truncate the datetime down to a whole day
Select distinct datex
from mytable
where (extract(day from sysdate) >=19 and datex = trunc(last_day(add_months(sysdate, -1))))
or (extract(day from sysdate) < 19 and datex = trunc(last_day(add_months(sysdate, -2))))
It doesn't matter whether you apply TRUNC late as in my example or right away on SYSDATE (with TRUNC(SYSDATE)) by the way. The only aim is to get rid of the time part at some point in the expression.
Don't use case in where clauses. Boolean logic can handle that.
And take a look if it is really the condition you want
Select distinct datex
from your_table
where
(
extract(day from sysdate) >=19
and datex = last_day(add_months(sysdate,-1))
)
or
(
extract(day from sysdate) < 19
and datex = last_day(add_months(sysdate,-2))
)

How to transform Oracle DateTime to Date

For instance, I have a datetime like this '2016-04-02 00:00:00' and another like this '2016-04-02 15:10:00'. I don't care about the time-part, I want them to match just by the date-part.
I have tried with date(), to_date, datepart, nothing works.
Do it like this:
where yourField >= the start of your date range
and yourField < the day after the end of your date range
Edit starts here:
While you could use trunc, as suggested by others, bear in mind that filtering on function results tends to be slow.
Truncating the date to day should do the trick. Documentation here:
https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions201.htm
For example
SELECT TRUNC(SYSDATE, 'DAY') FROM DUAL;
As others have said - there is no separate "date" data type in Oracle. A pure "date" is stored as a date with the time portion set to 00:00:00 (midnight at the beginning of the day), and TRUNC(date) will take any date and truncate the time to 00:00:00, so if you need to compare two dates you can write
where trunc(date_1) = trunc(date_2)
If your tables are very large and you need to do these comparisons often, this is not ideal, because wrapping column values within function calls (like date_1 within a TRUNC) prevents the use of an index you may have on the date_1 column. If you need to compare dates in two columns you may not have much of a choice, but if you compare to a fixed date (or something like SYSDATE) you may be better off with something like
where date_1 >= trunc(sysdate) and date_1 < trunc(sysdate) + 1
Here you are not using trunc on the column value, so if there's an index on the column, Oracle is free to use it - and trunc(sysdate) is computed only once, not for every single row. "+1" by the way means "add one day".
TO_DATE converts a string to a date; if you apply TO_DATE to a value that is already a legitimate date, you will get unexpected results because Oracle will first convert your true date to a string and then back to date again, and since these conversions require a date FORMAT for strings, and the formats Oracle assumes for conversion from date to string and from string to date may not match, .... you get the idea. As far as I know, DATE() (a FUNCTION) and DATEPART do not exist in Oracle; when you use a new language, keep Google close by and use it often.
If you input a date with no time component, for example TO_DATE('04-apr-2016, 'dd-mon-yyyy'), then the implicit time is 00:00:00 so you don't need to apply TRUNC() to it.
Good luck!

Issue with date formatting SQL

SELECT LISTING_EOD.LOCATION, LISTING_EOD.APPTTIME, LISTING_EOD.PERSON_ID,
LISTING_EOD.FORENAME, LISTING_EOD.SURNAME, LISTING_EODS.STATUS,
LISTING_EOD.DBDATE
FROM DBNAME.LISTING_EOD LISTING_EOD;
This query returns a list of data processed today, I need to modify to check yesterday's data. I have tried add the below line of code, but it doesn't return anything. Does anyone know how I can achieve this?
where LISTING_EOD.DBDATE = '18-OCT-2012';
If you always want yesterday's data, rather than hard-coding the date you can use:
WHERE LISTING_EOD.DBDATE >= TRUNC(SYSDATE) - 1
AND LISTING_EOD.DBDATE < TRUNC(SYSDATE)
TRUNC(SYSDATE) gives you midnight this morning, so if run today it would give a range between 18-Oct-2010 00:00:00 and 18-Oct-2012 23:59:59.
It's generally not a good idea to use implicit date format masks; your original code assumes your NLS_DATE_FORMAT is set to DD-MON-YYYY, but that might not be correct now (if you're seeing the time in the existing select then it probably isn't), and may well not be in the future. Always use an explicit date format mask, like TO_DATE('18-OCT-2012', 'DD-MON-YYY'), to avoid ambiguity and unexpected behaviour.
If the field is actually VARCHAR2 rather than a DATE - which is bad - then you'll need to convert the date range to a string to get a match:
WHERE LISTING_EOD.DBDATE >= TO_CHAR(TRUNC(SYSDATE) - INTERVAL '1' DAY, 'DD-MON-YYYY HH24:MI:SS')
AND LISTING_EOD.DBDATE <= TO_CHAR(TRUNC(SYSDATE) - INTERVAL '1' SECOND, 'DD-MON-YYYY HH24:MI:SS')
That will work for a single day, just, but you'd have problems looking for a date range. It's much better and safer to store data in a column of the appropriate type.
Dates in Oracle by default contain time as well. If you just specify '18-OCT-2012', it will only match 18-OCT-2012 00:00:00'. One way to get around this is to format your database date to what you are comparing it to, e.g. to_char(LISTING_EOD.DBDATE, 'DD-MON-YYYY') and compare this to '18-OCT-2012'. This comparison will disregard time completely.
If you had a date variable to compare with instead of a string, format this using the same date mask used for the database date. This also gets around any assumptions abut default date format on the database in question.
I realised the 'table' I was querying was a view, examined it inside sqldeveloper, and added '-1' to the sysdate. This query then returned the previous days results.
I successfully retrieved the correct data, thanks for all help received.

Date not valid for month specified

I have a problem when running this Oracle SQL statement:
SELECT *
FROM tbl_content
WHERE last_updated >= (systimestamp - INTERVAL '1' month(1))
ORDER BY last_updated desc
And this error:
java.sql.SQLException: ORA-01839: date not valid for month specified
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:111)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:330)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:287)
at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:742)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:212)
at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:951)
at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1053)
at oracle.jdbc.driver.T4CPreparedStatement.executeMaybeDescribe(T4CPreparedStatement.java:835)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1123)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3284)
at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3328)
at com.vtdd.sms.content.xskt.XsktService.getKQXSFollowArea(XsktService.java:4044)
at com.vtdd.sms.content.xskt.XsktService.getMessages(XsktService.java:421)
at com.vht.sms.content.ContentAbstract.getSubmitMessages(ContentAbstract.java:47)
at com.vht.sms.content.ContentFactory.getSubmitMessages(ContentFactory.java:335)
at com.vht.sms.content.ContentFactory.run(ContentFactory.java:62)
at java.lang.Thread.run(Thread.java:662)
Could you tell me what is wrong?
Firstly, why are you using systimestamp? If you want this to the month then surely sysdate is exact enough? Secondly, I like - i.e. it's personal preference - to make it extremely clear what's happening. Oracle has an add_months function, which will do what you want. So your query could easily be:
SELECT *
FROM tbl_content
WHERE last_updated >= add_months(sysdate, -1)
ORDER BY last_updated desc
What is actually wrong is that interval arithmetic doesn't adjust days - see the 6th bullet in the link:
When interval calculations return a datetime value, the result must be an actual datetime value or the database returns an error.
ADD_MONTHS does; as that link says:
If date is the last day of the month or if the resulting month has fewer days than the day component of date, then the result is the last day of the resulting month.
So, ADD_MONTHS(DATE '2011-12-31', -1) gives you 2011-11-30, while DATE '2011-12-31' - INTERVAL '1' MONTH tries to give you 2011-11-31, which as the message says, isn't a valid date.
(It's debatable if this behaviour is actually wrong; it's unexpected, but I believe it's conforming to ANSI. There may be times you want it to work this way, though I can't think of any...)

Oracle to_date function with quarter-format

I need to find some records created in a range of quarters. For example, I'm looking for all records created between the 4th quarter of 2008 and the 1st quarter of 2010. I have this in my WHERE-clause:
...and r.record_create_date between to_date('2008 4','YYYY Q')
and to_date('2010 1','YYYY Q')
but Oracle says: ORA-01820: format code cannot appear in date input format. The Q is a valid date format symbol, so I'm not sure what's happened. Is this even a valid way to find values in between calender quarters, or is there a better way?
Also interesting, and possibly related, if I execute this:
select to_date('2009','YYYY') from dual;
The value displayed in my IDE is 2009-08-01. I would have expected 2009-08-04, since today is 2010-08-04.
This:
select to_date('2009 1','YYYY Q') from dual;
of course, fails.
(Oracle 10g)
Oracle says: ORA-01820: format code cannot appear in date input format. The Q is a valid date format symbol, so I'm not sure what's happened.
See the second column of table 2.15 at http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/sql_elements004.htm#i34948. Not all format elements are allowed when converting to dates, timestamps, etc.
I recommend against using between for date range checks. People often will miss values within the ending day that the expect to be included. So I would translate:
and r.record_create_date between to_date('2008 4','YYYY Q')
and to_date('2010 1','YYYY Q')
To
and to_date('2008-10-01', 'YYYY-MM-DD') <= r.record_create_date
and record_create_date < to_date('2010-04-01', 'YYYY-MM-DD') -- < beginning of 2Q2010.
Someone asked the same question on OTN: http://forums.oracle.com/forums/thread.jspa?threadID=1081398&tstart=255
The crux of the issue is that you can not specify "Q" in the TO_DATE function.
Given that you're already specifying a portion of the date, why not provide the entire date? Mind too that to_date('2010 1','YYYY Q') would give you Jan 1st, 2010 when you really want March 31st, 2010... at a second to midnight.
Since the relationship between quarters to months is one-to-many, it doesn't make sense to do TO_DATE('2008 1', 'yyyy q'); what date should be returned? The first of the quarter, the end of the quarter, ...? (On the other hand, converting a date to a quarter - like TO_CHAR(SYSDATE, 'yyyy q') makes sense because a specific date only exists in one quarter.)
So, if you do want a query that looks for a date that falls between two quarters, you will have to "rolll your own" (explicitly stating the dates of the start/end of a quarter.)
As a side note, in case anyone is considering not using TO_DATE please do not use things like: WHERE date_value BETWEEN 'date string1' and 'date string2' without the TO_DATE function. It assumes a default date format and under certain situations can avoid potentially useful indexes altogether.
Below is one example where the same query can have a different result.
select sysdate from dual where sysdate between '1-Jan-10' and '31-Dec-10';
SYSDATE
---------
04-AUG-10
SQL> alter session set nls_date_format = 'YYYY-MM-DD';
Session altered.
SQL> select * from dual where sysdate between '1-Jan-10' and '31-Dec-10';
no rows selected
(Notice that in the second instance no error is returned. It just assumes Jan 10, 0001 and Dec. 10th, 0031.)
I think the best way is to just input the quarter start date and quarter end dates without even bothering with to_date. I think if you use
between '1-Jan-10' and '31-Dec-10'
for example, then you don't (in Oracle I believe) need to_date and it isn't much more difficult than typing in the quarter number
To calculate in Oracle the first day of a quarter and the last day of a quarter from the year and quarter:
I Use the fact
start_month= -2 + 3 * quarter
last_month = 3 * quarter
variable v_year number
variable v_quarter number
exec :v_year :=2017
exec :v_quarter:=4
select :v_year as year,
:v_quarter as quarter,
to_date(:v_year||to_char(-2+3*:v_quarter,'fm00'),'yyyymm') as quarter_start,
last_day(to_date(:v_year||to_char(3*:v_quarter,'fm00')||'01 23:59:59','yyyymmdd hh24:mi:ss')) as quarter_end
from dual a;
YEAR|QUARTER|QUARTER_START |QUARTER_END
2017| 4|2017-10-01 00:00:00|2017-12-31 23:59:59