Checking a specific date format in Oracle SQL using RegexLike - sql

I am using oracle data integrator as an ETL tool to load data, there is one column which as a source comes in format YYYYMMDD for ex: 20190418. I want to query that table to identify all those records which do not fit 20190418 or YYYYMMDD format.
P.S. the column is in varchar2 datatype.
I already tried using something like this:
SELECT CASE WHEN NOT REGEXP_LIKE('20190418', '^\d{4}(0[1-9]|1[12])(0[1-9]|[12]\d|3[01])$') then '00000000' else '20190418' END FROM DUAL;
This seems to identify those illegal dates, but for example it didn't work for this one : '20181023'.
Can someone figure it out, I think I am missing some kind of pattern

I would propose a function rather than a regex, will be easier.
CREATE OR REPLACE FUNCTION Verify_date_number(inNumber IN NUMBER) RETURN VARCHAR2 AS
res DATE;
BEGIN
res := TO_DATE(inNumber, 'fxYYYYMMDD');
RETURN TO_CHAR(res, 'YYYYMMDD');
EXCEPTION
WHEN OTHERS THEN
RETURN '00000000';
END;
If you are running Oracle 12.2 then you can also use VALIDATE_CONVERSION
SELECT
CASE VALIDATE_CONVERSION('20190418' AS DATE, 'fxYYYYMMDD')
WHEN 1 THEN '20190418'
ELSE '00000000'
END
FROM dual;

Using PL/SQL maybe a better alternative
declare
v_date date;
begin
for c in
(
select '20190418' as date_str from dual union all
select '20191804' from dual union all
select '201904187' from dual
)
loop
begin
v_date := to_date(c.date_str,'yyyymmdd');
dbms_output.put_line(c.date_str);
exception when others then null;
end;
end loop;
end;
gives only the decently formatted data as output. In the above case, it's
20190418 only.
For 20191804, we would get ORA-01843: not a valid month
For 201904187, we would get ORA-01830: date format picture ends before converting entire input string
errors

you can try this one:
with tab as(
select '20190418' as dat from dual union all
select '20181023' as dat from dual union all
select '20181123' as dat from dual union all
select '20181223' as dat from dual union all
select '20181201' as dat from dual union all
select '20181209' as dat from dual union all
select '20181210' as dat from dual union all
select '20181229' as dat from dual union all
select '20181231' as dat from dual union all
select '20181232' as dat from dual union all
select '20181200' as dat from dual union all
select '20191418' as dat from dual
)
SELECT CASE WHEN NOT REGEXP_LIKE(dat, '^\d{4}(0[1-9]|(1[0-2]))(0[1-9]|[1-2][0-9]|3[0-1])$') then '00000000' else dat END as dat
FROM tab;
Result:
20190418
20181023
20181123
20181223
20181201
20181209
20181210
20181229
20181231
00000000
00000000
00000000

Related

i get ORA-01722: invalid number error in plsql

i have problem about ora 01722 i have string prols it is like "1501,1701,1901,2001,2501,2601" or "2321,1331,111231,35501" and i want control table x' column x.rol_id include prols or not but i get error in my code. here is part of my code. thanks.
function get_menu_detay_01(pmenu_kod varchar2,
proller varchar2) return sys_refcursor is
v_cr_1 sys_refcursor;
begin
open v_cr_1 for
select distinct mtd.sira_no,
mtd.seviye_1,
mtd.fa_icon_1,
mtd.seviye_2,
mtd.fa_icon_2,
mtd.seviye_3,
mtd.fa_icon_3,
ut.uygulama_tanim_id,
ut.aciklama,
ut.path,
ut.fa_icon,
ut.href,
ut.onclick
from uat.menu_tanim_d mtd
left join uat.uygulama_tanim ut
on ut.uygulama_tanim_id = mtd.uygulama_tanim_id
left join uat.uygulama_yetki uy
on mtd.uygulama_tanim_id = uy.uygulama_tanim_id
where mtd.menu_kod = pmenu_kod
and uy.rol_id in (select regexp_substr(tt.rol, '[^,]+', 1, level)
from (select proller rol
from dual t) tt
connect by regexp_substr(tt.rol, '[^,]+', 1, level) is not null)
order by mtd.sira_no;
return v_cr_1;
end;
If you say the strings you are splitting has separator , then you need to change the regular expression [^,]+ to to include , instead ; or the other way around
I just tried and its working,
WITH tst
AS
(SELECT 1501 rol_id FROM dual
UNION ALL
SELECT 1701 FROM dual
UNION ALL
SELECT 1901 FROM dual
UNION ALL
SELECT 2001 FROM dual
UNION ALL
SELECT 2501 FROM dual
UNION ALL
SELECT 2601 FROM dual
)
SELECT *
FROM tst x
WHERE x.rol_id in (select regexp_substr(tt.rol, '[^,]+', 1, LEVEL)
from (select '1701,1901,2001,2501,2601' rol
from dual t) tt
connect by regexp_substr(tt.rol, '[^,]+', 1, level) is not null);
EDIT: Test with function Please check how you call the function and pass the string in proper format or not
CREATE OR REPLACE FUNCTION get_menu_detay_01(proller VARCHAR2)
RETURN SYS_REFCURSOR
IS
v_cr_1 SYS_REFCURSOR;
BEGIN
OPEN v_cr_1 FOR
SELECT *
FROM ( SELECT 1501 rol_id FROM dual
UNION ALL
SELECT 1701 FROM dual
UNION ALL
SELECT 1901 FROM dual
UNION ALL
SELECT 2001 FROM dual
UNION ALL
SELECT 2501 FROM dual
UNION ALL
SELECT 2601 FROM dual
) x
WHERE x.rol_id IN (SELECT regexp_substr(tt.rol,'[^,)]+',1,LEVEL)
FROM (SELECT proller rol FROM dual t) tt
CONNECT BY regexp_substr(tt.rol,'[^,]+',1,LEVEL) IS NOT NULL);
RETURN v_cr_1;
END get_menu_detay_01;
Test:
set serveroutput on;
DECLARE
lo_ref_cur SYS_REFCURSOR;
lo_number NUMBER;
BEGIN
lo_ref_cur := get_menu_detay_01('1701,1901,2001,2501,2601');
LOOP
FETCH lo_ref_cur INTO lo_number;
EXIT WHEN lo_ref_cur%NOTFOUND;
dbms_output.put_line(lo_number);
END LOOP;
END;
/
Output:
------
1701
1901
2001
2501
2601
PL/SQL procedure successfully completed.

DBMS_Output: Some extra "enable" might be missing

Sorry the silly question. I am stuck with Dbms_Output:
First I tried it within a batch/sqlplus call. But the spool file would only contain the message "pl/sql sucessfully executed". (See first code part)
I am running the code on Oracle Database 12c Enterprise Edition Release 12.1.0.2.0
Spool D:\log\spool.txt
Declare
Some_Num Number := 5;
Begin
Dbms_Output.Enable(1000000);
Dbms_Output.Put_Line('Id, Timestamp');
For Rec In
(
Select 1 As Id, Sysdate As Timestamp From Dual
Union
Select 2 As Id, Sysdate As Timestamp From Dual
Union
Select 3 As Id, Sysdate As Timestamp From Dual
Union
Select 4 As Id, Sysdate As Timestamp From Dual
)
Loop
Dbms_Output.Put_Line( Rec.Id || ', ' || Rec.Timestamp );
-- some code here was actually executed;
End Loop;
--Dbms_Output.Disable;
Exception
When Others Then
Null;
Dbms_Output.Put_Line('Error');
--Dbms_Output.Disable;
End;
/
Spool Off
exit;
Found it myself. I needed to add
SET SERVEROUTPUT ON SIZE 1000000
at the beginning of the script.

Using Oracle SQL, how can I get weekdays (Monday-Sunday) using their rank (1-7)?

Using Oracle, here is my problem :
What I have:
A number between 1 and 7.
What I want:
1 = Monday, 2 = Tuesday...
What I did so far:
DECODE(myVar,1,'Monday',2,'Tuesday',...)
I have of course thought of writing a function but I would like to know if there is no native way to do that.
EDIT : Since it seems unclear, I want a native way to do this without using a DECODE or a CASE.
Thanks a lot.
You may try to use to_char function with the Day and D arguments for a date variable as in the following :
SQL> var day_nr number;
SQL> exec :day_nr := 1;
PL/SQL procedure successfully completed
day_nr
---------
1
SQL> alter session set NLS_TERRITORY="UNITED KINGDOM";
SQL> with t(day,day_nr) as
2 (
3 select to_char(level + sysdate,'Day','NLS_DATE_LANGUAGE=ENGLISH'),
4 to_char(level + sysdate,'D')
5 from dual
6 connect by level <= 7
7 )
8 select day
9 from t
10 where day_nr = :day_nr;
DAY
---------
Monday
As an example, if you substitute 1 for :day_nr you get Monday etc.
Rextester Demo
P.S. thanks to #Matthew McPeak I realize that the returning value for to_char(<date>,'D') may differ from TERRITORY, for example, it differs whether NLS_TERRITORY parameter is set "UNITED KINGDOM" or "AMERICAN_AMERICA".
The following formula will produce what you want:
SELECT TO_CHAR(TRUNC(SYSDATE, 'IW') +
(CAST(TO_CHAR(TRUNC(SYSDATE, 'IW'), 'D') AS NUMBER) +
:DAY_OF_WEEK - 3), 'Day')
FROM DUAL
To use, replace the :DAY_OF_WEEK parameter with whatever value you want and it will return the day of the week.
TO_DATE has a format_mask that allows you to create a date from the numeric day of the week. Then you could get the weekday name from that. However it requires a string input.
this will surely work as you have asked a function without decode or case:
declare
a varchar(20);
b varchar(20);
c varchar(20);
d varchar(20);
e varchar(20);
f varchar(20);
g varchar(20);
h varchar(20);
i number;
begin
i:=&i;
SELECT COALESCE((select 'null' from dual where i<>1),'MONDAY')into a from dual;
SELECT COALESCE((select 'null' from dual where i<>2),'TUEDAY')into b from dual;
SELECT COALESCE((select 'null' from dual where i<>3),'WEDNESDAY') into c from dual;
SELECT COALESCE((select 'null' from dual where i<>4),'THURDAY') into d from dual;
SELECT COALESCE((select 'null' from dual where i<>5),'FRIDAY')into e from dual;
SELECT COALESCE((select 'null' from dual where i<>6),'SATURDAY')into f from dual;
SELECT COALESCE((select 'null' from dual where i<>7),'SUNDAY')into g from dual;
DBMS_OUTPUT.PUT_LINE(a||b||c||d||e||f||g);
end;
/
for input of 4 output is:
null null null THURDAY null null null

How to capture 20 digit numeric or floating number to number field in oracle

I got a requirement to validate the number field which can occupy 20 numeric digits or 16 numeric digits and 4 decimal point values maximum. And also + and - signs shouldn't be calculated in this 20 digits.
Examples:
12345678901234567890 - Pass
+12345678901234567890 - Pass
-12345678901234567890 - Pass
1234567890123456.7890 - Pass
+1234567890123456.7890 - Pass
-1234567890123456.7890 - Pass
-12345678901234567.890 - Pass
123456789012345.67890 - Fail
123456789012345678901 - Fail
1234567890 - Pass
1.1 - Pass
Any thoughts how could we validate these kind of numbers?
Function
Added the explanation as comments. You can the use the format model SFM99999999999999999990.9999 to validate your number. Exception raised would means, the input doesn't match the format.
create or replace function validateMe(str IN VARCHAR2)
return
varchar2
as
STR_COPY VARCHAR2(4000);
RESULT NUMBER;
BEGIN
/* Prefix the Sign by default */
if(SUBSTR(str,1,1) NOT IN ('+','-')) THEN
STR_COPY := '+' || STR;
else
STR_COPY := STR;
end if;
/* Check if the length is > 21 -- Including the sign and excluding decimal */
if(length(REPLACE(STR_COPY,'.')) > 21 ) then
return 'Fail';
end if;
/* Use the TO_NUMBER() function to validate */
RESULT := to_number(STR_COPY,'SFM99999999999999999990.9999');
RETURN 'Pass';
EXCEPTION WHEN OTHERS THEN
/* Exception means Failure */
RETURN 'Fail';
END validateMe;
/
Query:
with mydata(str) as
(
select '12345678901234567890.56' from dual
union all
select '123456789012345678.56' from dual
union all
select '+12345678901234567890' from dual
union all
select '-12345678901234567890' from dual
union all
select '1234567890123456.7890' from dual
union all
select '+1234567890123456.7890' from dual
union all
select '-1234567890123456.7890' from dual
union all
select '-12345678901234567.890' from dual
union all
select '123456789012345.67890' from dual
union all
select '123456789012345678901' from dual
union all
select '1234567890' from dual
union all
select '1.1' from dual
)
select str,validateMe(str) from mydata;

Oracle sql terminator screening (+strange behavior)

I have a query, say this one
SELECT to_char(regexp_substr(q'{select * from dual minus select * from dual; select * from dual minus select * from dual;}'
, '[^;]+', 1, LEVEL)) FROM dual
CONNECT BY to_char(regexp_substr(q'{select * from dual minus select * from dual; select * from dual minus select * from dual;}', '[^;]+', 1, LEVEL)) IS NOT NULL;
and it works fine - splits my row
select * from dual minus select * from dual; select * from dual
minus select * from dual;
into two
select * from dual minus select * from dual
select * from dual
minus select * from dual
Everything is fine until I add some line brakes, like this
SELECT to_char(regexp_substr(q'{select * from dual minus select * from dual;
select * from dual minus select * from dual;}'
, '[^;]+', 1, LEVEL)) FROM dual
CONNECT BY to_char(regexp_substr(q'{select * from dual minus select * from dual;
select * from dual minus select * from dual;}', '[^;]+', 1, LEVEL)) IS NOT NULL;
and here it turns to hell: sql treats ; inside a string like an actual end of the query, ORA-01756 and stuff...
And everything is fine again if I add a random symbol after the ;, this way
SELECT to_char(regexp_substr(q'{select * from dual minus select * from dual;%
select * from dual minus select * from dual;}'
, '[^;]+', 1, LEVEL)) FROM dual
CONNECT BY to_char(regexp_substr(q'{select * from dual minus select * from dual;%
select * from dual minus select * from dual;}', '[^;]+', 1, LEVEL)) IS NOT NULL;
Please explain this behavior and suggest a workaround.
UPD: tried this in different IDE (SQL developer instead of PL/SQL developer). No errors. Maybe it's all about encoding...
UPD2: SQLPlus works the same way PL/SQL developer does in this case. SQL developer seems to be a bit 'smarter'. Still, no idea why.
Try:
SELECT to_char(regexp_substr(q'{select * from dual minus select * from dual;
select * from dual minus select * from dual;}'
, '[^;[:cntrl:]]+', 1, LEVEL)) FROM dual
CONNECT BY to_char(regexp_substr(q'{select * from dual minus select * from dual;
select * from dual minus select * from dual;}', '[^;[:cntrl:]]+', 1, LEVEL)) IS NOT NULL;
Output:
select * from dual minus select * from dual
select * from dual minus select * from dual
The reason is the regexp_substr looks for the next pattern you specify, which in your case was originally [^;]+ and this pattern would find the ; and the next char would be the linefeed. The simple solution if you wanted to break up the lines like this is to also exclude control chars in the regex search via [:cntrl:]
I created the open source project plsql_lexer to solve these kinds of problems.
Splitting can get tricky for complex SQL statements. And after the statements are split you will probably want to know what to do with them, and how to report on them. The procedures STATEMENT_CLASSIFIER.CLASSIFY and STATEMENT_FEEDBACK.GET_FEEDBACK_MESSAGE can help with that task.
Sample code
Here are some samples, starting with your example and adding a few other cases. Each sample splits the string into two statements.
declare
procedure print_split_strings(p_statements nclob) is
v_split_statements nclob_table;
begin
v_split_statements := statement_splitter.split(p_statements);
for i in 1 .. v_split_statements.count loop
dbms_output.put_line('Statement '||i||': '||v_split_statements(i));
end loop;
end;
begin
--This is a simple case.
print_split_strings('select * from dual minus select * from dual; select * from dual minus select * from dual;');
--Ignore semicolons in comments.
print_split_strings('select * from dual a;/* a comment ; */ select * from dual b;');
--Ignore semicolons in strings.
print_split_strings(q'{select '''' || q'!'!' from dual a;select * from dual b;}');
--Ignore semicolons in matching BEGIN/ENDs in PLSQL_DECLARATIONS:
print_split_strings('with function f return number is begin return 1; end; function g return number is begin return 2; end; select f from dual;select 1 from dual;');
end;
/
Statement 1: select * from dual minus select * from dual;
Statement 2: select * from dual minus select * from dual;
Statement 1: select * from dual a;
Statement 2: /* a comment ; */ select * from dual b;
Statement 1: select '''' || q'!'!' from dual a;
Statement 2: select * from dual b;
Statement 1: with function f return number is begin return 1; end; function g return number is begin return 2; end; select f from dual;
Statement 2: select 1 from dual;