Concatenating and comparing values of fields as date - sql

Can you help me out with this problem.
I have the following fields below with sample values:
STRM = 1171
TERM_BEGIN_DT = 01-SEPT-18
TERM_END_DT = 31-JUL-19
ACAD_YEAR = 2018
*Additional Info:
2018/19 Academic Year
1st August 2018 – 31st July 2019*
What I want to do is, I want to get the STRM within the current ACADEMIC YEAR
The SQL i want is:
SELECT STRM FROM PS_TERM_TBL
WHERE TERM_BEGIN_DT BETWEEN '01-AUG-18' AND '31-JUL-19';
The problem is, given only the values above, I have to hard code '01-AUG-' and '31-JUL-' and concatenate each with the '18' and '18' + 1 of ACAD_YEAR respectively.
Main question is, how do i do this?
How do i get the 18 of 2018 in ACAD_YEAR and then add 1 to it to get 19?
I think i will get a invalid type error with this one, so what do i have to convert to to_date in order for the comparison to be legit?

You can use ADD_MONTHS to translate the epoch date of the start of the current academic year back to the start of the calendar year, TRUNCate the value to the beginning of the year, and then use ADD_MONTHS again to reverse the initial translation:
SELECT STRM
FROM PS_TERM_TBL
WHERE TERM_BEGIN_DT >= ADD_MONTHS( TRUNC( ADD_MONTHS( SYSDATE, -7 ), 'YYYY' ), 7 )
AND TERM_BEGIN_DT < ADD_MONTHS( TRUNC( ADD_MONTHS( SYSDATE, -7 ), 'YYYY' ), 19 )
As an aside, '01-AUG-18' is not a date - it is a string literal that Oracle is implicitly converting to a date using the NLS_DATE_FORMAT session parameter (which is something that can change depending on the user's territory and users can also change their own session's settings so should not be relied upon to be give a consistent format model). If you want to specify dates then you should use either:
a date literal DATE '2018-08-01'; or
an explicit conversion from a string literal TO_DATE( '01-AUG-18', 'DD-MON-RR', 'NLS_DATE_LANGUAGE = American' )

Related

Oracle SQL: Syntax for WHERE clause with one date between two others

Apologies in advance, feel like I'm missing something fundamental here.
I'm limiting a query WHERE one field (already in date format) is between two others:
SELECT Stuff
FROM Table
WHERE datefield BETWEEN (currentdate - 28) AND (current_date - 1)
This returns nothing. Now if I format both as dates explicitly and look for a single date:
SELECT Stuff FROM Table
WHERE TO_DATE(datefield, 'YYYY-MM-DD') = TO_DATE((current_date - 1), 'YYYY-
MM-DD')
That returns the single day's results as intended. However, if I then try something along the lines of:
SELECT Stuff FROM Table
WHERE TO_DATE(datefield, 'YYYY-MM-DD') >= TO_DATE((current_date - 28),'YYYY-
MM-DD')
This returns the entire table, including dates from long before (current_date - 28)
And finally if I try the BETWEEN after explicitly formatting:
SELECT Stuff FROM Table
WHERE TO_DATE(datefield, 'YYYY-MM-DD') BETWEEN TO_DATE((current_date - 28)
,'YYYY-MM-DD') AND TO_DATE((current_date - 1) ,'YYYY-MM-DD')
This returns nothing again.
Any help would be much appreciated.
SELECT Stuff
FROM Table
WHERE datefield BETWEEN (currentdate - 28) AND (current_date - 1)
Will return values between the 28 days before the current date at the same time of day as now and 1 day before the current date at the same time of day as now. So, if you run this at 10:23 today then it will get results before 10:23 yesterday and if your results for yesterday were all entered in the afternoon then they will not be included in the results.
SELECT Stuff
FROM Table
WHERE TO_DATE(datefield, 'YYYY-MM-DD') = TO_DATE((current_date - 1), 'YYYY-MM-DD')
TO_DATE( date_string, format_model ) takes a string as the first argument so Oracle will implicitly convert your date to a string using the NLS_DATE_FORMAT session parameter. So your query is effectively:
SELECT Stuff
FROM Table
WHERE TO_DATE(
TO_CHAR(
datefield,
( SELECT VALUE FROM NLS_SESSION_PARAMETERS WHERE PARAMETER = 'NLS_DATE_FORMAT' )
),
'YYYY-MM-DD'
)
=
TO_DATE(
TO_CHAR(
(current_date - 1),
( SELECT VALUE FROM NLS_SESSION_PARAMETERS WHERE PARAMETER = 'NLS_DATE_FORMAT' )
),
'YYYY-MM-DD'
)
Firstly, don't ever rely on an implicit conversion. Secondly, depending on what your NLS_DATE_FORMAT session parameter is your query will either: raise an exception if the format model does not match YYYY-MM-DD; or give you gibberish dates (i.e. if your default format model is RR-MM-DD then you'll lose the centuries from your dates; if it is DD-MM-YY then you'll swap days and years - which might "work" [i.e. not raise an exception] for the next decade or so and then blow up horribly in February 2029); or it will work. However, this is a session parameter so users can change it and your query will randomly stop working without changing your SQL.
Assuming that your NLS_DATE_FORMAT is YYYY-MM-DD then this will effectively truncate your dates and you could do it much simpler using:
SELECT Stuff
FROM Table
WHERE TRUNC(datefield) = TRUNC(current_date - 1)
This will get all the results (regardless of time of day) for yesterday.
What you probably want is:
SELECT Stuff
FROM Table
WHERE datefield >= TRUNC( currentdate - 28 )
AND datefield < TRUNC( current_date )
As it will get all the results from Midnight 28 days ago to before midnight today.
If this doesn't work then you need to check your date values:
SELECT TO_CHAR( datevalue, 'YYYY-MM-DD HH24:MI:SS' )
FROM table;
You will possibly find that the dates are stored with the year in the 1st century AD (i.e. 0018-10-24 09:50:00 as the value) and this will (probably) be because dates were uploaded as a string with a 2-digit year (YY-MM-DD) when a 4-digit year (YYYY-MM-DD) was expected so Oracle will assume the century is 0.

uisng to_date function still get date format picture ends before converting entire input string error

I have the following code where I want to see if a date is less than a year ago:
select id
from mytable
where id= :p_id
and (to_date(trunc(sysdate), 'yyyy-mm-dd') - to_date(datewhen, 'yyyy-mm-dd')) < 365;
I keep getting the error:
ORA-01830: date format picture ends before converting entire input
string
Looking at other question with the same error on StackOverflow I see the solution usually is to use the to_date function which I am doing so I am unsure why this is occuring. The datewhen field is of type Date.
Do not use to_date() with the columnes of DATE data type. to_date() converts character string to a value of DATE data type. It makes no sense to convert the DATE to DATE. In a first step datewhen column of type DATE will be implicitly converted into a character data type by using the default date format (that's most probably not 'yyyy-mm-dd') and this is the culprit of the ORA-01830 error.
So your statement should look something like this:
select id from mytable where id = :p_id and (trunc(sysdate) - trunc(datewhen)) < 365;
I'd calculate the difference in the months or years instead of days:
... where months_between(sysdate, datewhen) < 12
If your datewhen column is char/varchar formatted as yyyy-mm-dd then you have to do the to_date conversion on datewhen, but not on SYSDATE: it's already a date and doesn't need to be converted.
To filter on a date within the past 365 days, compare it to SYSDATE - 365:
select id
from mytable
where id = :p_id
and to_date(datewhen, 'yyyy-mm-dd') > sysdate - 365;
But a year isn't always 365 days: on leap years it's 366 days. To get a one year ago value that's always correct, subtract an interval of one year from the current date:
select id
from mytable
where id = :p_id
and datewhen > sysdate - interval '1' year;
One more thing: the Oracle DATE type isn't just a date; it's a date and a time. SYSDATE returns the current date and time. Try this query:
select to_char(sysdate, 'yyyy-mm-dd hh24:mi:ss') from dual;
Unless you run this at exactly midnight you'll see a time component as well.
Say your query runs on 2 September 2017 at 10 AM and you're looking for a date within the past year. You'd expect to get the date 3 September 2016, but you wouldn't because at 10 AM SYSDATE is 3 September 2016 at 10:00:00. That's greater than the plain date 3 September 2016, which is 3 September 2016 at 0:00:00, so records with a datewhen of `2016-09-03' won't be included.
To ignore the time component of an Oracle DATE value, use TRUNC. Your final query should look something like this:
select id
from mytable
where id = :p_id
and datewhen > trunc(sysdate) - interval '1' year;
you use TO_DATE function when the value in character format
Syntax
The syntax for the TO_DATE function in Oracle/PLSQL is:
TO_DATE( string1 [, format_mask] [, nls_language] )

When and why does the TRUNC(DATE, [FORMAT]) used in the real world

I am getting into Oracle database. I came across the TRUNC(DATE, [FMT]) function. I am not really clear on it except it seems to return the beginning value of some sort?
Can somebody educate me on it? When or what would it be used for at work, or why somebody might want to use the function?
Try this query to know when it might be usefull:
ALTER SESSION SET NLS_DATE_FORMAT = 'yyyy-mm-dd hh24:mi:ss';
select sysdate,
trunc( sysdate, 'mi' ) As beginning_of_current_minute,
trunc( sysdate, 'mi' ) As beginning_of_current_hour,
trunc( sysdate, 'dd' ) As beginning_of_current_day,
trunc( sysdate, 'iw' ) As beginning_of_current_week,
trunc( sysdate, 'mm' ) As beginning_of_current_month,
trunc( sysdate, 'q' ) As beginning_of_current_Quarter,
trunc( sysdate, 'y' ) As beginning_of_current_Year
FROM dual;
An example - you want to get all orders starting from the beginning of the current week:
SELECT *
FROM ORDERS
WHERE order_date >= trunc( sysdate, 'iw' )
A real world example would be if you wanted to aggregate results from a table by year. You could use the TRUNC function like this:
SELECT TRUNC(my_date, 'YEAR') the_year, count(*)
FROM some_table
GROUP BY TRUNC(my_date, 'YEAR');
...which would return a set of results with the first column the date truncated to the beginning of the year and the second column a count of all the records with dates within that year.
the_year, count(*)
_________________
01-JAN-12, 543
01-JAN-13, 1268
01-JAN-14, 1134
01-JAN-15, 1765
There are obviously other ways to achieve the same thing, but this is a real world example of how you might use TRUNC.
Another might be if you are comparing dates and you only want to use a certain degree of precision. If you have a timestamp column and you want all the records for today, you could select based on a range where the timestamp is greater than midnight yesterday and less than midnight today, or you could select where the timestamp, truncated to the DATE, is equal to today.
https://docs.oracle.com/cd/E29805_01/server.230/es_eql/src/cdfp_analytics_lang_trunc.html
Another thing it is useful for is to get the time component of the current day. I use an expression like this all the time:
SELECT sysdate - trunc(sysdate) AS TodaysTime FROM DUAL
Because the system date is stored in a decimal format (e.g. sysdate = 42651.2426897456) and the integer value corresponds to midnight, I can use the above statement to get only the decimal portion (e.g. TodaysTime = 0.2426897456, or just before 6 AM).
There may be easier ways to do this, but in my applications this has been the easiest as I frequently need to work with only the day's time component.

Oracle SQL WHERE MONTH = N

In my select I am using this
(TRUNC(TO_DATE(TIMESTAMP, 'dd.mm.yyyyHH24:mi'))) TIMESTAMP,
to get the following output in a date format: e.g. 22/04/2016
Now I want to add a statement in my WHERE-clause to show only dates in special months, for example only dates which are in MARCH and APRIL
I tried using this:
WHERE (TRUNC(TO_DATE(TIMESTAMP, 'mm'))) in (3,4)
which gives me an error.
Thanks for helping.
Just use EXTRACT() on the timestamp:
WHERE EXTRACT(MONTH FROM timestamp) IN (3, 4)
This would match records from March and April.
In my select I am using this
(TRUNC(TO_DATE(TIMESTAMP, 'dd.mm.yyyyHH24:mi'))) TIMESTAMP,
to get the following output in a date format: e.g. 22/04/2016
TO_DATE takes a string value so your "TIMESTAMP" column will be implicitly converted to a string and then back to a date... which is unnecessary (and relies on the value of the NLS_TIMESTAMP_FORMAT session parameter to format the implicit conversion - which, if changed, will break the query); you can just do:
TRUNC( "TIMESTAMP" ) AS "TIMESTAMP"
If you want to then filter on different months then you can do (as suggested by Tim Biegeleisen):
WHERE EXTRACT( MONTH FROM "TIMESTAMP" ) IN ( 3, 4 )
or if you want a particular year then
WHERE TRUNC( "TIMESTAMP", 'MM' ) IN ( DATE '2016-03-01', DATE '2016-04-01' )
or, so you can use an index on the column:
WHERE "TIMESTAMP" >= DATE '2016-03-01'
AND "TIMESTAMP" < DATE '2016-05-01'

PLSQL - how to extract month and year only from full date

I have a date like 01/03/2016. i want to get back 03/2016 in date type.
I know extract function, but it give me char type.
I don't want to use to_char.
Is there any way to do that?
Date datatype always has a day, month, year, hour, minute and second part.
You can use use to_char() function to extract the required component from a date.
select to_char(sysdate,'MM/YYYY') from dual;
However you can use below query but it will return 01-FEB-16
select to_date('01-2016','DD\YYYY') from dual;
Date does not have a format - it is represented internally by a series of bytes which you can see using:
SELECT DUMP( SYSDATE ) FROM DUAL;
Which outputs something like ( for the date 2016-02-17T09:13:44Z):
Typ=13 Len=8: 224,7,2,17,9,13,44,0
If you want the year & month then you can do use TRUNC( date_value, 'MM' ) or TO_CHAR( date_value, 'MM/YYYY' ):
SELECT TRUNC( SYSDATE, 'MM' ) AS "Date",
TO_CHAR( SYSDATE, 'MM/YYYY' ) AS monthyear,
DUMP( TRUNC( SYSDATE, 'MM' ) ) AS "Bytes"
FROM DUAL
Which outputs (note, the last 4 bytes are all zero after truncation):
Date | MonthYear | Bytes
--------------------------------------------------------------------
2016-02-01T00:00:00Z | 02/2016 | Typ-13 Len=8: 224,7,2,1,0,0,0,0
However, the output of the date is based on the NLS_DATE_FORMAT session parameter and your client will do an implicit TO_CHAR when it outputs the date using this format mask.
You can find out your current NLS_DATE_FORMAT with the query (as you can see above, mine is set to an ISO8601 format YYYY-MM-DD"T"HH24:MI:SS"Z"):
SELECT VALUE
FROM NLS_SESSION_PARAMETERS
WHERE PARAMETER = 'NLS_DATE_FORMAT';
You can then alter it using:
ALTER SESSION SET NLS_DATE_FORMAT = 'MM/YYYY';
Then when you do:
SELECT SYSDATE, DUMP( SYSDATE ) FROM DUAL;
You will get the output:
Date | Bytes
-----------------------------------------------
02/2016 | Typ=13 Len=8: 224,7,2,17,9,13,44,0
Note, the last 4 bytes are not all non-zero (the date has not been truncated) but now the output is just the month/year.
However, it is just simpler to store the date as a date (with the day and time components unchanged) and then use TO_CHAR( date_value, 'MM/YYYY' ) whenever you want to output it as you will get the output format you want without changing the value across the entire session.