Oracle - Inline View - External WHERE condition applied inline - oracle

We have a table where dates are stored as VARCHAR2(It's legacy data table which we have no control on!) in format of YYYYMMDD. We have only SELECT privilege on table (Not feasible to write procedure/functions).
Need to select all rows from table where date is > sysdate.
We apply regular expression check for valid format and additional checks to ensure that day is valid for given month. All this works great! Our inline view selection ensures that only records with valid date strings are picked.
But, when we apply condition to check > sysdate as an external clause - we get invalid date for given month error even though inline view selection ensures that no such records are picked.
It looks like query execution is applying the condition from outer clause before inline view conditions are applied. Appreciate any comments on the behavior; and, how can we ensure that conditions from outside are applied only once inline conditions are met?
Data and Used Queries:
CREATE TABLE TEST_DATA_TABLE
(
DATESTRING VARCHAR2(20 BYTE)
);
Insert 3 rows with values:
19960322 --Valid Date in past
19831131 --Invalid Date 11/31
20180224 --Valid Date > SYSDATE
Valid data selection:
(Where clause 1 ensures format and clause 2 ensures valid date for month):
SELECT datestring AS i_dob
FROM test_data_table
WHERE REGEXP_LIKE (TRIM (datestring),
'(19|20)\d\d(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])')
AND TRIM (datestring) <=
TO_CHAR (
LAST_DAY (
TO_DATE (SUBSTR (TRIM (datestring), 1, 6) || '01',
'YYYYMMDD')),
'YYYYMMDD')
Above query works fine and return valid rows with valid date string,
To select records having date string > SYSDATE, above data is used inline and we apply condition >SYSDATE as below.
SELECT i_dob
FROM (
SELECT datestring AS i_dob
FROM test_data_table
WHERE REGEXP_LIKE (TRIM (datestring),
'(19|20)\d\d(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])')
AND TRIM (datestring) <=
TO_CHAR (
LAST_DAY (
TO_DATE (SUBSTR (TRIM (datestring), 1, 6) || '01',
'YYYYMMDD')),
'YYYYMMDD')
) X
WHERE TO_DATE (X.I_DOB, 'YYYYMMDD') > SYSDATE
It starts throwing error: ORA-01839: date not valid for month specified
Looks like the conditions are applied before all inline view conditions are checked.

You can get around this by making the inline view be actioned before the outer query by adding an additional rownum column to it (the presence of the rownum column means that Oracle needs to calculate that column for the subquery it applies to before it can further filter on it.
So, your query would become:
SELECT i_dob
FROM (
SELECT datestring AS i_dob,
rownum rn
FROM test_data_table
WHERE REGEXP_LIKE (TRIM (datestring),
'(19|20)\d\d(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])')
AND TRIM (datestring) <=
TO_CHAR (
LAST_DAY (
TO_DATE (SUBSTR (TRIM (datestring), 1, 6) || '01',
'YYYYMMDD')),
'YYYYMMDD')
) X
WHERE TO_DATE (X.I_DOB, 'YYYYMMDD') > SYSDATE;
I would add a comment into your query explaining the presence of this column, otherwise some developer in the future may think "eh? This does nothing; I'll remove it!".
(Also of note is in 12.2, they have added/amended functions to allow for easier data validation, which would simplify your query when you get to 12.2!)

Related

How to use defined variable in Where clause

Defining and selecting variable works just fine in Oracle SQL Developer.
ALTER SESSION SET NLS_LANGUAGE=english; -- First day of week
--DEFINE SUMMER_START_DT = TO_CHAR(TO_DATE('03-24-2022', 'MM-DD-YYYY'),'yyyymmdd')
DEFINE SUMMER_START_DT = TO_CHAR(NEXT_DAY(LAST_DAY(TO_DATE(TO_CHAR('01/03/' || (EXTRACT(YEAR FROM SYSDATE)-1 + level) || '02:00:00'),'DD/MM/YYYY HH24:MI:SS')) - INTERVAL '7' DAY, 'SUNDAY'),'yyyymmdd') FROM DUAL CONNECT BY level <=1
SELECT &SUMMER_START_DT;
But I get an error when trying to use the variable in Select statement using it as filter in the Where clause.
SELECT a.* FROM TRADE a WHERE TO_CHAR(a.TRADE_DATE_TIME,'yyyymmdd') = &SUMMER_START_DT;
I get the error "SQL command not properly ended"
Hope someone can help me. Thanks
Kind regards
Soren Sig Mikkelsen
You substitution variable includes from dual, which is OK when you just prepend select in your first example; but in the second you end up with two from clauses:
SELECT a.*
FROM TRADE a
WHERE TO_CHAR(a.TRADE_DATE_TIME,'yyyymmdd') =
TO_CHAR(NEXT_DAY(LAST_DAY(TO_DATE(TO_CHAR('01/03/' || (EXTRACT(YEAR FROM SYSDATE)-1 + level) || '02:00:00'),'DD/MM/YYYY HH24:MI:SS')) - INTERVAL '7' DAY, 'SUNDAY'),'yyyymmdd')
FROM DUAL CONNECT BY level <=1
(You can see that in the generated column name/alias in the output grid; or set verify on and run as a script.)
If you really wanted to use that as the right-hand side of the filter then you could enclose it in parentheses:
SELECT a.* FROM TRADE a WHERE TO_CHAR(a.TRADE_DATE_TIME,'yyyymmdd') = (SELECT &SUMMER_START_DT);
which would become:
SELECT a.*
FROM TRADE a
WHERE TO_CHAR(a.TRADE_DATE_TIME,'yyyymmdd') =
(
SELECT TO_CHAR(NEXT_DAY(LAST_DAY(TO_DATE(TO_CHAR('01/03/' || (EXTRACT(YEAR FROM SYSDATE)-1 + level) || '02:00:00'),'DD/MM/YYYY HH24:MI:SS')) - INTERVAL '7' DAY, 'SUNDAY'),'yyyymmdd')
FROM DUAL CONNECT BY level <=1
)
But the connect by isn't doing anything here, so you can remove that; and if you remove from dual as well then you can run your first statement as:
SELECT &SUMMER_START_DT FROM DUAL;
and the second as it is.
You could simplify the calculation though. For a start you aren't using the time element, so you don't need to make it 2am; and you can truncate to the start of the year and add two months to get March 1st; as a string if that's really what you want:
to_char(next_day(last_day(add_months(trunc(sysdate, 'YYYY'), 2)) - 7, 'SUNDAY'), 'YYYYMMDD')
db<>fiddle
But you can keep it as a date; if you:
DEFINE SUMMER_START_DT = next_day(last_day(add_months(trunc(sysdate, 'YYYY'), 2)) - 7, 'SUNDAY')
then again you can do:
SELECT &SUMMER_START_DT FROM DUAL;
and your second query can be:
SELECT a.*
FROM TRADE a
WHERE a.TRADE_DATE_TIME >= &SUMMER_START_DT
AND a.TRADE_DATE_TIME < &SUMMER_START_DT + 1
which avoids converting every TRADE_DATE_TIME date value to a string to compare it, and allows an index on that date column to be used.

Compare 2 date fields - when one date field is null

I wanted to select the rows whose date is greater than last processed date.
The last processed date is in hist table.
select id
from table1
where to_date(last_updated_date,'MM/DD/YYYY HH24:MI:SS')
> select to_date(nvl(max(last_updated_date),sysdate),'MM/DD/YYYY HH24:MI:SS')
from table1_hist;
This return ORA-01843: not a valid month error.
I had manually inserted row in table1 as TO_DATE('09/26/2019 14:37:49', 'MM/DD/YYYY HH24:MI:SS') while in table1_hist there are no rows.
But when I query the table using sql developer for table1 I get the value appearing as '26-SEP-19'. last_updated_date is a date field.
I wanted to to get the id's from table1 after the last executed time.
Thanks
SYSDATE is a DATE value. Using TO_DATE() on a value which is already a DATE is useless.
Try
where last_updated_date >
(select MAX(NVL(last_updated_date, SYSDATE)) from table1_hist);
I will suggest using an analytical function as following:
SELECT
ID
FROM
(
SELECT
T.ID,
T.LAST_UPDATED_DATE,
MAX(TH.LAST_UPDATED_DATE) OVER() AS M_LAST_UPDATED_DATE
FROM
TABLE1 T
JOIN TABLE1_HIST TH ON ( T.ID = TH.ID )
-- i am guessing that this is the join condition or you can use your own conition here
)
WHERE
LAST_UPDATED_DATE > COALESCE(M_LAST_UPDATED_DATE, SYSDATE)
Cheers!!
First of all, you don't need any to_date conversion for comparison.
If table1_hist has at least one non-null value, then using
select t.id
from table1 t
where t.last_updated_date > ( select max(th.last_updated_date) from table1_hist th )
is enough, but all those values are null then use a query such as this :
select t.id
from table1 t
where t.last_updated_date >
(select nvl(max(th.last_updated_date),t.last_updated_date-1) from table1_hist th)
Demo

encountered the symbol FROM when expecting one of the following pl sql

I am trying to extract year from datefield but when I use extract (year from
datefield) I get this error
Encountered the symbol FROM when expecting one of the following pl sql
cursor o1 is
select substr(tarifa,1,2), count(*)
from pol p, uvod u, doppov d
where extract(year FROM datum_dop) = EXTRACT(YEAR FROM sysdate)
and izdavanje >='1-jul-13'
and p.orgjed = u.sorgz (+)
and DATUM_PREKIDA is not null
and p.polica=d.polica and d.pov_dopl='P'
and d.status='F'
and cisti_ao(p.polica)!=0
group by substr(tarifa,1,2);
Where did I made mistake ?
Ah, this is Forms, probably 6i.
Its engine doesn't know extract function. Change that line to
where to_char(datum_dop, 'yyyy') = to_char(sysdate, 'yyyy')
This would, though, make index on datum_dop column (if it exists) unusable and force Oracle to convert dates to strings, so you'd rather try with
where datum_dop >= trunc(sysdate, 'yyyy')
and datum_dop < add_months(trunc(sysdate, 'yyyy'), 12)
Other than that:
count(*) should have an alias (if you plan to use it), e.g. count(*) as broj_tarifa
if izdavanje is date, don't compare it to a string ('1-jul-13') but date, e.g. izdavanje >= to_date('01.07.2013', 'dd.mm.yyyy')
use table aliases for all columns

Oracle format conversions from to_char TO (to_timestamp or to_Date)

SELECT Owner
FROM
TABLEX
WHERE (
( To_Char(From_Date,'YYYYMM') <= '201707' )
And
( To_Char(To_Date,'YYYYMM') >= '201707' )
);
Oracle Query:
FROM_DATE, To_Date is in Timestamp format.This query is working fine, but my manager told me to_char Format comparison slow down performance of query and he asked me to use either date format OR timestamp format for conversions. I tried different combinations, but not able to identify. Please suggest.
Some tries I have done.
select to_date('201707','YYYYMM') from DUAL.
This gives me (01-JUL-2017) , but I have to check with all values greater than and less.
To make the query as efficient as possible, use constants of the same data type as the columns being compared. This eliminates the need for the database to perform a type conversion for each row:
Select owner From tablex
Where From_Date < To_Timestamp('2017-08-01 00:00:00','yyyy-mm-dd hh24:mi:ss')
And To_Date >= To_Timestamp('2017-07-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss');
Use Trunc function instead of To_Char:
SELECT Owner
FROM
TABLEX
WHERE (
( Trunc(From_Date,'YYYYMM') <= '201707' )
And
( Trunc(To_Date,'YYYYMM') >= '201707' )
);

Get date of the previous day in Oracle

I need to bring the day immediately preceding date in Oracle using a truncate but not how. He was using the following line but bring me some records for the current day of execution and should not be. Neceisto only the previous day; investigation found the truncate with dates in Oracle but not how to use it.
and fnxs.FECHA_INGRESO BETWEEN (TO_CHAR (SYSDATE-1, 'DD-MON-YY')) AND (TO_CHAR (SYSDATE, 'DD-MON-YY'));
I appreciate your help
Using BETWEEN with dates in Oracle is generally a bad idea. I see it all the time, and most of the time people get it wrong (like in the accepted answer above). Even when they fully understand that the two dates are included, they still make logical errors because they forget about timestamps.
The OP is asking for yesterday dates. The following sql shows that today falls within "BETWEEN TRUNC( SYSDATE ) - 1 AND TRUNC( SYSDATE )"
with adate as (
select trunc(sysdate) today from dual
) select today from adate where today between trunc(sysdate) -1
and trunc(sysdate);
16-Apr-15 00:00:00
[returns the record for today]
I find it easier to be correct with dates when you're more explicit about the end points:
SELECT * from your_table
WHERE fnxs.FECHA_INGRESO >= TRUMC(SYSDATE) - 1
AND fnxs.FECHA_INGRESO < TRUNC(SYSDATE);
Upon looking closer, the OP's date-like column might be a VARCHAR2 (could still be a date that was implicitly cast in the comparison he gave). If it is a VARCHAR, then it needs to be converted first (using an appropriate format string):
SELECT * FROM your_table
WHERE TO_DATE(fnxs.FECHA_INGRESO, 'DD-MON-YY') >= TRUMC(SYSDATE) - 1
AND TO_DATE(fnxs.FECHA_INGRESO, 'DD-MON-YY') < TRUNC(SYSDATE);
Assuming your column is of type DATE
SELECT *
FROM TABLE_NAME
WHERE FECHA_INGRESO BETWEEN TRUNC( SYSDATE ) - 1
AND TRUNC( SYSDATE );
If it is a character string then:
SELECT *
FROM TABLE_NAME
WHERE TO_DATE( FECHA_INGRESO, 'DD-MON-YY' )
BETWEEN TRUNC( SYSDATE ) - 1
AND TRUNC( SYSDATE );

Resources