Oracle sql terminator screening (+strange behavior) - sql

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;

Related

Why can I cast a multiset and not a collection as a odcivarchar2list in a query

This query works
WITH
FUNCTION f (i_tab IN SYS.odcivarchar2list)
RETURN INTEGER
IS
BEGIN
RETURN 22;
END;
s (a, b)
AS
(SELECT 1, 'ff' FROM DUAL
UNION ALL
SELECT 1, 'ee' FROM DUAL
UNION ALL
SELECT 1, 'ee' FROM DUAL
UNION ALL
SELECT 2, 'ee' FROM DUAL),
t (a, b)
AS
(SELECT s1.a,
CAST (MULTISET (SELECT s2.b
FROM s s2
WHERE s2.a = s1.a) AS SYS.odcivarchar2list)
FROM s s1)
SELECT *
FROM t;
But this query doesn't.
WITH
FUNCTION f (i_tab IN SYS.odcivarchar2list)
RETURN INTEGER
IS
BEGIN
RETURN 2;
END;
s (a, b)
AS
(SELECT 1, 'ff' FROM DUAL
UNION ALL
SELECT 1, 'ee' FROM DUAL
UNION ALL
SELECT 1, 'ee' FROM DUAL
UNION ALL
SELECT 2, 'ee' FROM DUAL),
t (a, b)
AS
( SELECT s.a, CAST(COLLECT (s.b) as sys.odcivarchar2list)
FROM s
GROUP BY s.a)
SELECT *
FROM t
ORA-00932: inconsistent datatypes: expected - got ANYDATA
Why can't I cast a collection as sys.odcivarchar2list.
The problem is the cast function. Because the following query works.
WITH
FUNCTION f (i_tab IN SYS.odcivarchar2list)
RETURN INTEGER
IS
BEGIN
RETURN 2;
END;
s (a, b)
AS
(SELECT 1, 'ff' FROM DUAL
UNION ALL
SELECT 1, 'ee' FROM DUAL
UNION ALL
SELECT 1, 'ee' FROM DUAL
UNION ALL
SELECT 2, 'ee' FROM DUAL),
t (a, b)
AS
( SELECT s.a, COLLECT (s.b)
FROM s
GROUP BY s.a)
SELECT *
FROM t;
I want to cast the collection in order to use function f.
Like that:
see in code (stackoverflow message: It looks like your post is mostly code; please add some more details)
code

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.

Checking a specific date format in Oracle SQL using RegexLike

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

How can I pass comma separated value in cursor's select statement where clause

I have following Block which has a cursor and a select query. I want to pass the output of select, which is comma separated into
the cursor's select statement, into the where clause.
I know below code will throw an error because of SQL query in Declare section but how I achieve this using array or collection.
here, id column is number
code snippet:
declare
test varchar2(30);
SELECT LISTAGG(value, ', ') WITHIN GROUP (ORDER BY value2) into test from table3 where value2=12;
cursor c1 (select * from table where id in (test))
begin
for i in c1 loop
null;
end loop;
end;
Why would you ever do this?
You can simple write you select as:
Select * from table where id in (select value from table3 where value2=12)
Edit:
Also you need to open your cursor c1, for it to work AFAIK.
You can use a collection and the MEMBER OF operator:
Oracle Setup:
CREATE TYPE IntegerList AS TABLE OF NUMBER(8,0);
/
CREATE TABLE table1 ( value, value2 ) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 3, 4 FROM DUAL UNION ALL
SELECT 5, 3 FROM DUAL UNION ALL
SELECT 7, 2 FROM DUAL;
CREATE TABLE table2 ( id, value ) AS
SELECT 1, 11 FROM DUAL UNION ALL
SELECT 2, 22 FROM DUAL UNION ALL
SELECT 3, 33 FROM DUAL UNION ALL
SELECT 7, 77 FROM DUAL;
PL/SQL:
DECLARE
test IntegerList;
c1 SYS_REFCURSOR;
BEGIN
SELECT value
BULK COLLECT INTO test
FROM table1;
FOR r IN ( SELECT * FROM table2 WHERE id MEMBER OF test ) LOOP
DBMS_OUTPUT.PUT_LINE( r.id || ', ' || r.value );
END LOOP;
END;
/
Output:
1, 11
3, 33
7, 77
db<>fiddle here

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;