Output parameter value is shown as 'invalid identifier' - oracle

I am trying to retrieve values from temporarily created tables. But the return value throws the error 'Invalid Identifier'
create or replace procedure edu_stream (input in varchar2,vals out varchar2)
as
inp varchar2(30);
valu varchar2(30);
begin
inp:=input;
if inp='secondary education' then
Execute immediate'WITH secedu as (
(SELECT "ICSE" as name FROM dual ) UNION
(SELECT "CBSE" as name FROM dual ) UNION
(SELECT "STATE BOARD" as name FROM dual)
)
SELECT name into valu from(SELECT name
FROM secedu ORDER BY DBMS_RANDOM.RANDOM)where rownum<2';
vals:=valu;
else
if inp='intermediate education' then
Execute immediate'WITH intedu as (
(SELECT "MPC" as name FROM dual ) UNION
(SELECT "BIPC" as name FROM dual ) UNION
(SELECT "MBIPC" as name FROM dual) UNION
(SELECT "CEC" as name FROM dual)
)
SELECT name into valu from(SELECT name
FROM intedu ORDER BY DBMS_RANDOM.RANDOM)where rownum<2';
vals:=valu;
else
if inp='Graduation' then
Execute immediate'WITH gedu as (
(SELECT "ECE" as name FROM dual ) UNION
(SELECT "CSE" as name FROM dual ) UNION
(SELECT "CE" as name FROM dual) UNION
(SELECT "EEE" as name FROM dual)UNION
(SELECT "ME" as name FROM dual)UNION
(SELECT "AE" as name FROM dual)UNION
(SELECT "BIOTECH" as name FROM dual)UNION
(SELECT "EIE" as name FROM dual)
)
SELECT name into valu from(SELECT name
FROM gedu ORDER BY DBMS_RANDOM.RANDOM)where rownum<2';
vals:=valu;
else
if inp='post-graduation' then
Execute immediate'WITH pgedu as (
(SELECT "MCA" as name FROM dual ) UNION
(SELECT "MTECH" as name FROM dual ) UNION
(SELECT "MSC" as name FROM dual) UNION
(SELECT "MBA" as name FROM dual)
)
SELECT name into valu from(SELECT name
FROM pgedu ORDER BY DBMS_RANDOM.RANDOM)where rownum<2';
vals:=valu;
else
if inp='phd'then
Execute immediate' WITH phdedu as (
(SELECT "Doctorate of philosophy" as name FROM dual ) UNION
(SELECT "doctorate of medicine" as name FROM dual ) UNION
(SELECT "doctorate of science" as name FROM dual) UNION
(SELECT "Doctorate of computer sciences" as name FROM dual)
)
SELECT name into valu from(SELECT name
FROM phdgedu ORDER BY DBMS_RANDOM.RANDOM)where rownum<2';
vals:=valu;
end if;
end if;
end if;
end if;
end if;
end;
Execution:
declare
value1 varchar2(30);
cv varchar2(30);
begin
cv:='secondary education';
edu_stream(cv,value1);
dbms_output.put_line('val is'||value1);
end;
Error report:
Error starting at line 2 in command: declare value1 varchar2(30); cv
varchar2(30); begin cv:='secondary education'; edu_stream(cv,value1);
dbms_output.put_line('val is'||value1); end; Error report: ORA-00904:
"ICSE": invalid identifier ORA-06512: at "DATAFOCUS_GROUP.EDU_STREAM",
line 9 ORA-06512: at line 6
00904. 00000 - "%s: invalid identifier"
*Cause:
*Action:
if I use 'ICSE'instead of "ICSE"
ERROR SHOWS-
PLS-00103: Encountered the symbol "ICSE" when expecting one of the
following:
ERROR 103 * & = - + ; < / > at in is mod remainder not rem
return
returning <> or != or ~= >= <= <> and or
like like2 like4 likec between into using || multiset bulk
member submultiset

You can avoid dynamic SQL; also, probably due to confusion created by dynamic sql, you are using " instead of '.
You can re-write your code as:
CREATE OR REPLACE PROCEDURE edu_stream(input IN VARCHAR2, vals OUT VARCHAR2) AS
inp VARCHAR2(30);
valu VARCHAR2(30);
BEGIN
inp := input;
IF inp = 'secondary education'
THEN
WITH secedu AS
((SELECT 'ICSE' AS name FROM DUAL)
UNION
(SELECT 'CBSE' AS name FROM DUAL)
UNION
(SELECT 'STATE BOARD' AS name FROM DUAL))
SELECT name
INTO valu
FROM ( SELECT name
FROM secedu
ORDER BY DBMS_RANDOM.RANDOM)
WHERE ROWNUM < 2;
vals := valu;
ELSE
IF inp = 'intermediate education'
THEN
WITH intedu AS
((SELECT 'MPC' AS name FROM DUAL)
UNION
(SELECT 'BIPC' AS name FROM DUAL)
UNION
(SELECT 'MBIPC' AS name FROM DUAL)
UNION
(SELECT 'CEC' AS name FROM DUAL))
SELECT name
INTO valu
FROM ( SELECT name
FROM intedu
ORDER BY DBMS_RANDOM.RANDOM)
WHERE ROWNUM < 2;
vals := valu;
ELSE
IF inp = 'Graduation'
THEN
WITH gedu AS
((SELECT 'ECE' AS name FROM DUAL)
UNION
(SELECT 'CSE' AS name FROM DUAL)
UNION
(SELECT 'CE' AS name FROM DUAL)
UNION
(SELECT 'EEE' AS name FROM DUAL)
UNION
(SELECT 'ME' AS name FROM DUAL)
UNION
(SELECT 'AE' AS name FROM DUAL)
UNION
(SELECT 'BIOTECH' AS name FROM DUAL)
UNION
(SELECT 'EIE' AS name FROM DUAL))
SELECT name
INTO valu
FROM ( SELECT name
FROM gedu
ORDER BY DBMS_RANDOM.RANDOM)
WHERE ROWNUM < 2;
vals := valu;
ELSE
IF inp = 'post-graduation'
THEN
WITH pgedu AS
((SELECT 'MCA' AS name FROM DUAL)
UNION
(SELECT 'MTECH' AS name FROM DUAL)
UNION
(SELECT 'MSC' AS name FROM DUAL)
UNION
(SELECT 'MBA' AS name FROM DUAL))
SELECT name
INTO valu
FROM ( SELECT name
FROM pgedu
ORDER BY DBMS_RANDOM.RANDOM)
WHERE ROWNUM < 2;
vals := valu;
ELSE
IF inp = 'phd'
THEN
WITH phdedu AS
((SELECT 'Doctorate of philosophy' AS name FROM DUAL)
UNION
(SELECT 'doctorate of medicine' AS name FROM DUAL)
UNION
(SELECT 'doctorate of science' AS name FROM DUAL)
UNION
(SELECT 'Doctorate of computer sciences' AS name FROM DUAL))
SELECT name
INTO valu
FROM ( SELECT name
FROM phdgedu
ORDER BY DBMS_RANDOM.RANDOM)
WHERE ROWNUM < 2;
vals := valu;
END IF;
END IF;
END IF;
END IF;
END IF;
END;
Also, please notice that the variables inp and valu are not strictly necessary: you can simply use the parameters to check the input value and build the output parameter.
In case you need do use dynamic SQL (not here, but maybe in the future) the right way is something like:
declare
a number;
begin
execute immediate 'select 1 from dual' into a;
end;

As an alternative answer sticking with your dynamic SQL.
personal preference: i´d allways end a line with ' ||, to make it clear you are not missing a whitespace (even though it should just execute fine).
remove the into valu from the dynamic sql and make it a execute immediate 'query' into vals clause.
As an example i´m just using your first query:
Execute immediate 'WITH secedu as ( ' ||
'(SELECT ''ICSE'' as name FROM dual ) UNION ' ||
'(SELECT ''CBSE'' as name FROM dual ) UNION ' ||
'(SELECT ''STATE'' BOARD" as name FROM dual) ' ||
') ' ||
'SELECT name from(SELECT name ' ||
'FROM secedu ORDER BY DBMS_RANDOM.RANDOM)where rownum<2'
into vals;

You might have your own learning objectives but when those put aside the code is simply awful and unnecessary complex PL/SQL. Here is a partial but completely functional rewrite with references to relevant Oracle PL/SQL documentation. Hope you'll find the presented ideas useful !
-- blocks: http://docs.oracle.com/database/121/LNPLS/overview.htm#LNPLS141
declare
-- nested tables: http://docs.oracle.com/database/121/LNPLS/composites.htm#LNPLS99981
type str_list_t is table of varchar2(32767);
-- subprograms: http://docs.oracle.com/database/121/LNPLS/subprograms.htm#LNPLS008
-- common parts of f() refactored to r()
function r(p_list in str_list_t) return varchar2 is
begin
return p_list(floor(dbms_random.value(1, p_list.count + 1)));
end;
function f(p_edu_level in varchar2) return varchar2 is
begin
return
-- simple case: http://docs.oracle.com/database/121/LNPLS/controlstatements.htm#LNPLS394
case p_edu_level
when 'secondary' then r(str_list_t('ICSE', 'CBSE', 'STATE BOARD'))
when 'intermediate' then r(str_list_t('MPC', 'BIPC', 'MIPC', 'CEC'))
-- add here your other education levels, you should see the pattern ...
else null
end;
end;
begin
-- for loop: http://docs.oracle.com/database/121/LNPLS/controlstatements.htm#LNPLS411
for i in 1 .. 10
loop
dbms_output.put_line('secondary: ' || f('secondary'));
dbms_output.put_line('intermediate: ' || f('intermediate'));
end loop;
dbms_output.put_line('batman: ' || f('batman'));
end;
/

Related

how can I increment a variable with the next value of a column

I have a function that have this code
for i in cursor
loop
variavel1=i.id
while i.id=variavel1
loop
---- do someting
--- incriment variavel1 with next i.id
end loop;
end loop;
I need to increment the variavel1 with the next id that i have in the i (object that have the date from the cursor).
If concatenation is everything you need, you can use pure sql:
select id, listagg(text) within group (order by text) as list
from (
select 1 id, 'abc' text from dual union all
select 1 id, 'def' text from dual union all
select 1 id, 'ghi' text from dual union all
select 3 id, 'jkl' text from dual union all
select 7 id, 'mno' text from dual union all
select 7 id, 'pqr' text from dual)
group by id;
Other possibility, like in this PLSQL block:
declare
cursor crsr is
select 1 id, 'abc' text from dual union all
select 1 id, 'def' text from dual union all
select 1 id, 'ghi' text from dual union all
select 3 id, 'jkl' text from dual union all
select 7 id, 'mno' text from dual union all
select 7 id, 'pqr' text from dual;
variavel1 number;
variavel2 varchar2(1000);
begin
for i in crsr loop
if variavel1 is null or variavel1 <> i.id then
if variavel1 is not null then
dbms_output.put_line(variavel1||' - '||variavel2);
end if;
variavel1 := i.id;
variavel2 := i.text;
else
variavel2 := variavel2 || i.text;
end if;
end loop;
dbms_output.put_line(variavel1||' - '||variavel2);
end;
You can also define simple type as table of objects (id, text) and in loop add items to variable of this type.
declare
type tob is record (id number, text varchar2(1000));
type ttb is table of tob;
variavel2 ttb := ttb();
cursor crsr is
select 1 id, 'abc' text from dual union all
select 1 id, 'def' text from dual union all
select 1 id, 'ghi' text from dual union all
select 3 id, 'jkl' text from dual union all
select 7 id, 'mno' text from dual union all
select 7 id, 'pqr' text from dual;
begin
for i in crsr loop
if variavel2.count = 0 or variavel2(variavel2.count).id <> i.id then
variavel2.extend();
variavel2(variavel2.count).id := i.id;
end if;
variavel2(variavel2.count).text := variavel2(variavel2.count).text||i.text;
end loop;
-- now we have array of values
for x in 1..variavel2.count loop
dbms_output.put_line(variavel2(x).id||' - '||variavel2(x).text);
end loop;
end;
I assumed that id is not nullable, if it is you need minor changes. Output for all solutions is:
ID LIST
------ --------------
1 abcdefghi
3 jkl
7 mnopqr
open cursor;
FETCH cursor INTO cursor_result;
WHILE cursor %found
loop
variavel1=cursor_result.id
WHILE( variavel1=cursor_result.id AND cursor %found)
LOOP
---dO SOMITHING----
FETCH cursor INTO cursor_result; ----PASS TO NEXT VALUE
END LOOP;
end loop;

Oracle - Loop through a list of columns

I have a base SQL query that I'm hoping to turn into a dynamic cursor.
I have a list of columns that I am checking to see if the values have changed since previous run, columns that I am checking include: Income, Ethnicity, etc.
The output of the before-and-after values need to be stored in a temp or permanent table for further investigation - such as source of the value change etc...
Because the list of the columns is more than 600+, I don't think I want to compile the base SQL 600 times..
Is there a better way to write out a dynamic SQL cursor to accomplish this task?
Thanks!
---base sql
SELECT a.*,
'Last_name' AS "field_name",
b.LAST_name AS last_name_updated
from
(SELECT person_id, last_name
FROM person
WHERE batch_id = (select max(batch_id) from person)
) a
FULL OUTER JOIN
(SELECT person_id, last_name
FROM person
WHERE batch_id = (select max(batch_id) - 1 from person)
) b
ON a.person_id = b.person_id
WHERE nvl(a.last_name,0) <> nvl(b.last_nm,0)
UNION
SELECT a.*,
'Income',
b.income AS income_updated
from
(SELECT person_id, income
FROM person
WHERE batch_id = (select max(batch_id) from person)
) a
FULL OUTER JOIN
(SELECT person_id, income
FROM person
WHERE batch_id = (select max(batch_id) - 1 from person)
) b
ON a.person_id = b.person_id
WHERE nvl(a.income,0) <> nvl(b.income,0)
---desired output
person_id || field_name || previous_value || updated_value
8783 || income || 95000 || 98000
235731 || last_name || Dawson || Dawson Jr.
Here is something to get you started. Rather than generate one enormous query with 600 unions, it generates 600 queries and runs them sequentially. You would need to add code to save the results from each iteration, perhaps into another table:
declare
l_sql long;
l_default varchar2(10);
l_max_batch_id number;
rc sys_refcursor;
begin
select max(batch_id)
into l_max_batch_id
from person;
for r in (select column_name, data_type
from user_tab_columns
where table_name = 'PERSON'
and column_name not in ('PERSON_ID') -- Exclude any other columns you don't want to compare
)
loop
l_sql := q'[SELECT a.person_id,
a.col AS new_value
'#COL#' AS column_name,
b.#COL# AS old_value
from
(SELECT person_id, #COL#
FROM person
WHERE batch_id = #MAXBATCH#
) a
FULL OUTER JOIN
(SELECT person_id, #COL#
FROM person
WHERE batch_id = #MAXBATCH#-1
) b
ON a.person_id = b.person_id
WHERE nvl(a.#COL#,#DEFAULT#) <> nvl(b.#COL#,#DEFAULT#)]';
l_sql := replace (l_sql, '#COL#', r.column_name);
l_sql := replace (l_sql, '#MAXBATCH#', l_max_batch_id);
l_default := case r.data_type
when 'NUMBER' then '0'
when 'DATE' then q'[DATE '4000-31-12']'
else q'['~~~']'
end;
l_sql := replace (l_sql, '#DEFAULT#', l_default);
open rc for l_sql;
-- Now fetch all the data and e.g. write to a table...
end loop;
end;

Count of each characters in given string

I require the count of each character in a string.
Example:
SELECT ('aabcccdee') from dual;
Result:
a(1),b(2), c(3), d(1),e(2).
Thanks in advance.
You can use a hierarchical query to split your string into it individual characters; this is using a CTE just to supply your example value:
with t (value) as (
select 'aabcccdee' from dual
)
select substr(value, level, 1) as a_char
from t
connect by level <= length(value);
Then you can use aggregation to count how may times each appears:
with t (value) as (
select 'aabcccdee' from dual
)
select a_char, count(*) a_count
from (
select substr(value, level, 1) as a_char
from t
connect by level <= length(value)
)
group by a_char
order by a_char;
A_CH A_COUNT
---- ----------
a 2
b 1
c 3
d 1
e 2
And you can use listagg() (if you're on 11g or above) to aggregate those characters and counts into a single string if that's what you really want:
with t (value) as (
select 'aabcccdee' from dual
)
select listagg(a_char || '(' || count(*) || ')', ',') within group (order by a_char)
from (
select substr(value, level, 1) as a_char
from t
connect by level <= length(value)
)
group by a_char;
LISTAGG(A_CHAR||'('||COUNT(*)||')',',')WITHINGROUP(ORDERBYA_CHAR)
-----------------------------------------------------------------
a(2),b(1),c(3),d(1),e(2)
If you particularly want to do this in PL/SQL - because you value is already in a PL/SQL variable perhaps - you can do the same thing with a context switch:
set serveroutput on
declare
l_value varchar2(30) := 'aabcccdee';
l_result varchar2(100);
begin
select listagg(a_char || '(' || count(*) || ')', ',') within group (order by a_char)
into l_result
from (
select substr(l_value, level, 1) as a_char
from dual
connect by level <= length(l_value)
)
group by a_char;
dbms_output.put_line(l_result);
end;
/
a(2),b(1),c(3),d(1),e(2)
PL/SQL procedure successfully completed.

oracle read column names from select statement

I wonder how read column names in oracle. Ok, I know that there is table named USER_TAB_COLUMNS which gives info about it, but if I have 2 or 3 level nested query and I don't know column names. Or I just have simple query with join statement and i want to get column names. How to do that? any idey?
select * from person a
join person_details b where a.person_id = b.person_id
thanks
I would go for:
select 'select ' || LISTAGG(column_name , ',') within group (order by column_id) || ' from T1'
from user_tab_columns
where table_name = 'T1';
to get a query from database. To get columns with types to fill map you can use just:
select column_name , data_type
from user_tab_columns
where table_name = 'T1';
I assume you are looking for this:
DECLARE
sqlStr VARCHAR2(1000);
cur INTEGER;
columnCount INTEGER;
describeColumns DBMS_SQL.DESC_TAB2;
BEGIN
sqlStr := 'SELECT a.*, b.*, SYSDATE as "Customized column name"
FROM person a JOIN person_details b
WHERE a.person_id = b.person_id';
cur := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(cur, sqlStr, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS2(cur, columnCount, describeColumns);
FOR i IN 1..columnCount LOOP
DBMS_OUTPUT.PUT_LINE ( describeColumns(i).COL_NAME );
END LOOP;
DBMS_SQL.CLOSE_CURSOR(cur);
END;

Get XMLType from a cursor

Anyone knows if I can generate a XMLType from a cursor without having to specify each row's name manually ?
I would like to be able to loop over my query, and to get a separate XML for each row.
I couldn't get a solution using DBMS_XMLGEN.getXMLType, but perhaps I didn't use it properly.
CREATE OR REPLACE PROCEDURE "MY_SCHEMA"."TEST" AS
CURSOR mySelectCursor is
SELECT '1a' as "column1", '1b' as "column2" FROM DUAL
UNION ALL
SELECT '2a' as "column1", '2b' as "column2" FROM DUAL;
myXMLType XMLType;
BEGIN
FOR mySelect in mySelectCursor
LOOP
-- I would like to replace the following line of code
myXMLType := XMLType('<row><column1>' || mySelect."column1" || '</column1><column2>' || mySelect."column2" || '</column2></row>');
-- by something similar to this (not working) one
--myXMLType := mySelect.getXMLType();
dbms_output.put_line(myXMLType.getClobVal());
END LOOP;
END;
--The following code outputs
--<row><column1>1a</column1><column2>1b</column2></row>
--<row><column1>2a</column1><column2>2b</column2></row>
I finally have found a solution.
Thank you for your help.
CREATE OR REPLACE PROCEDURE "MY_SCHEMA"."TEST" AS
CURSOR mySelectCursor is
SELECT
VALUE(table_temp) as "XMLTYPE"
FROM
table(
XMLSequence(
Cursor(
SELECT '1a' as "column1", '1b' as "column2" FROM DUAL
UNION ALL
SELECT '2a' as "column1", '2b' as "column2" FROM DUAL
)
)
) table_temp;
BEGIN
FOR mySelect in mySelectCursor
LOOP
dbms_output.put_line('===');
dbms_output.put_line(mySelect."XMLTYPE".getClobVal());
END LOOP;
END;
-- Output is :
--===
-- <ROW>
-- <column1>1a</column1>
-- <column2>1b</column2>
-- </ROW>
--
--===
-- <ROW>
-- <column1>2a</column1>
-- <column2>2b</column2>
-- </ROW>
Here is the code snippet, you need to use DBMS_XMLGEN.getxml:
DECLARE
v_xml CLOB;
v_more BOOLEAN := TRUE;
BEGIN
v_xml := DBMS_XMLGEN.getxml('select * from dual');
dbms_output.put_line(v_xml);
END;
/
Here is the code snippet, to loop in a cursor
CREATE OR REPLACE TYPE test_type AS OBJECT (
col1 CHAR(2),
col2 CHAR(2)
);
/
create or replace view test_view
as SELECT '1a' as column1, '1b' as column2 FROM DUAL
UNION ALL
SELECT '2a' as column1, '2b' as column2 FROM DUAL;
DECLARE
CURSOR c_xml IS
SELECT SYS_XMLAGG(
SYS_XMLGEN(
test_type(column1, column2),
sys.xmlgenformatType.createFormat('TABLE')
),
sys.xmlgenformatType.createFormat('TEST_VIEW')
).getStringVal() AS xml_row
FROM test_view;
BEGIN
FOR cur_rec IN c_xml LOOP
dbms_output.put_line(cur_rec.xml_row);
END LOOP;
end;
/
Does this query fit you needs:
WITH t AS
(SELECT '1a' AS c1, '1b' AS c2 FROM DUAL
UNION ALL
SELECT '2a' AS c1, '2b' AS c2 FROM DUAL)
SELECT XMLELEMENT("row",
XMLELEMENT("column1", c1 ),
XMLELEMENT("column2", c2 )
) AS myXMLType
FROM t;
Another way would be this one:
CREATE OR REPLACE TYPE "row" AS OBJECT (
"column1" VARCHAR2(100),
"column2" VARCHAR2(100))
/
WITH t AS
(SELECT '1a' AS c1, '1b' AS c2 FROM DUAL
UNION ALL
SELECT '2a' AS c1, '2b' AS c2 FROM DUAL)
SELECT XMLTYPE("row"(c1,c2)) AS myXMLType
FROM t;
Here is the solution with XMLTABLE rather than XMLSEQUENCE.
CREATE OR REPLACE PROCEDURE "MY_SCHEMA"."TEST" AS
CURSOR mySelectCursor IS
SELECT
VALUE(table_temp) AS "XMLTYPE"
FROM
XMLTABLE('/ROWSET/ROW' PASSING
DBMS_XMLGEN.GETXMLTYPE('
SELECT ''1a'' as "column1", ''1b'' as "column2" FROM DUAL
UNION ALL
SELECT ''2a'' as "column1", ''2b'' as "column2" FROM DUAL
')
) table_temp;
BEGIN
FOR mySelect IN mySelectCursor
LOOP
dbms_output.put_line(mySelect."XMLTYPE".getClobVal());
END LOOP;
END;
-- Output is :
--<ROW><column1>1a</column1><column2>1b</column2></ROW>
--<ROW><column1>2a</column1><column2>2b</column2></ROW>

Resources