How to display column name and column comment, when try DML - sql

I'm try use DML operations on table, when insert or update. I need to show column name and column comment when the operation failed. For example code:
CREATE TABLE test_test(col1 VARCHAR2(10), col2 VARCHAR2(100) not null);
DECLARE
ex_insert_null EXCEPTION;
PRAGMA EXCEPTION_INIT(ex_insert_null, -1400);
ex_value_too_large EXCEPTION;
PRAGMA EXCEPTION_INIT(ex_value_too_large, -12899);
BEGIN
INSERT INTO test_test
(col1
,col2)
SELECT CASE
WHEN LEVEL = 8 THEN
(LEVEL + 1) || 'qqqqqqqqqqqq'
ELSE
(LEVEL + 2) || 'qqq'
END AS col1
,CASE
WHEN LEVEL = 7 THEN
NULL
ELSE
(LEVEL + 3) || 'wwwwwww'
END AS col2
FROM dual
CONNECT BY LEVEL <= 10;
COMMIT;
EXCEPTION
WHEN ex_insert_null THEN
ROLLBACK;
dbms_output.put_line('ex_insert_null at ' || ' ' /* || column_name || ' ' || column_comment */);
WHEN ex_value_too_large THEN
ROLLBACK;
dbms_output.put_line('ex_value_too_large at ' || ' ' /* || column_name || ' ' || column_comment */);
END;
/

As APC has pointed out, you could use "existing Oracle exceptions" eg if you had something like ...
procedure insert_( col1 varchar2, col2 varchar2 )
is
v_errorcode varchar2(64) ;
v_errormsg varchar2(128) ;
begin
insert into t ( c1, c2 ) values ( col1, col2 ) ;
exception
when others then
if sqlcode = -1400 or sqlcode = -12899 then
v_errorcode := sqlcode;
v_errormsg := substr( sqlerrm, 1, 128 );
dbms_output.put_line( v_errorcode || ' ' || v_errormsg ) ;
raise;
end if;
end insert_ ;
... you could get error messages such as these:
-1400 ORA-01400: cannot insert NULL into ("MYSCHEMA"."T"."C2")
-12899 ORA-12899: value too large for column "MYSCHEMA"."T"."C1" (actual: 13, maximum: 10)
If this is enough information for you, fine. However, you also want to see the COMMENTS for the columns. Although we could get the column names from the SQLERRM strings, it may be more reliable to use user-defined exceptions (as you have hinted).
As a starting point, the following DDL and PACKAGE code may be of use for you. ( see also: dbfiddle here )
Tables:
drop table t cascade constraints ;
drop table errorlog cascade constraints ;
create table t (
c1 varchar2(10)
, c2 varchar2(64) not null
) ;
comment on column t.c1 is 'this is the column comment for c1';
comment on column t.c2 is 'this is the column comment for c2';
create table errorlog (
when_ timestamp
, msg varchar2(4000)
) ;
Package spec
create or replace package P is
-- insert into T, throwing exceptions
procedure insert_( col1 varchar2, col2 varchar2 );
-- use your example SELECT, call the insert_ procedure
procedure insert_test ;
-- retrieve the column comments from user_col_comments
function fetch_comment( table_ varchar2, col_ varchar2 ) return varchar2 ;
end P ;
/
Package body
create or replace package body P is
procedure insert_( col1 varchar2, col2 varchar2 )
is
ex_value_too_large exception ; -- T.c1: varchar2(10)
ex_insert_null exception ; -- T.c2: cannot be null
v_errorcol varchar2(32) := '' ;
v_comment varchar2(128) := '' ;
v_tablename constant varchar2(32) := upper('T') ;
begin
if length( col1 ) > 10 then
v_errorcol := upper('C1') ;
raise ex_value_too_large ;
end if;
if col2 is null then
v_errorcol := upper('C2') ;
raise ex_insert_null ;
end if ;
insert into t ( c1, c2 ) values ( col1, col2 ) ;
exception
when ex_value_too_large then
dbms_output.put_line( ' ex_value_too_large # '
|| v_errorcol || ' (' || fetch_comment( v_tablename, v_errorcol ) || ')' );
when ex_insert_null then
dbms_output.put_line( ' ex_insert_null # '
|| v_errorcol || ' (' || fetch_comment( v_tablename, v_errorcol ) || ')' );
when others then
raise ;
end insert_ ;
procedure insert_test
is
begin
for rec_ in (
select
case
when level = 8 then ( level + 1 ) || 'qqqqqqqqqqqq'
else ( level + 2 ) || 'qqq'
end as col1
, case
when level = 7 then null
else ( level + 3 ) || 'wwwwwww'
end as col2
from dual
connect by level <= 10
) loop
insert_( rec_.col1, rec_.col2 ) ;
end loop;
commit;
end insert_test;
function fetch_comment( table_ varchar2, col_ varchar2 ) return varchar2
is
v_comment varchar2(4000) ; -- same datatype as in user_tab_comments
begin
select comments into v_comment
from user_col_comments
where table_name = table_
and column_name = col_ ;
return v_comment ;
end fetch_comment ;
end P ;
/
For testing the package code, execute the following anonymous block:
begin
P.insert_test ;
end;
/
-- output
ex_insert_null # C2 (this is the column comment for c2)
ex_value_too_large # C1 (this is the column comment for c1)
-- Table T contains:
SQL> select * from T;
C1 C2
3qqq 4wwwwwww
4qqq 5wwwwwww
5qqq 6wwwwwww
6qqq 7wwwwwww
7qqq 8wwwwwww
8qqq 9wwwwwww
11qqq 12wwwwwww
12qqq 13wwwwwww
In the dbfiddle, all output will be written to T and ERRORLOG, respectively. You can also use dbms_output.put_line (which is commented out in the dbfiddle) if needed. Notice that the cursor for loop in the insert_test procedure is inefficient (we could use BULK operations). Also, you need to decide where and how the exceptions are handled. As mentioned, this example is just a starting point - which will probably need lots of refinements.

Related

Passing column name as parameter in Oracle PL_SQL function

I am trying to pass two input parameters, column name and table name . Then the function should output a string value as defined below :
create or replace function get_id5(in_col_name IN VARCHAR2,in_tbl_name IN VARCHAR2)
return VARCHAR2
is
/*in_col_nm varchar(32) := in_col_name;
/*in_tbl_nm varchar(64) := in_tbl_name; */
integer_part NUMBER ;
integer_part_str VARCHAR2(32) ;
string_part VARCHAR2(32) ;
full_id VARCHAR2(32) ;
out_id VARCHAR(32) ;
BEGIN
/*select MAX(in_col_nm) INTO full_id FROM in_tbl_nm ; */
execute immediate 'select MAX('||in_col_name||') FROM' || in_tbl_name ||'INTO' || full_id;
/*select regexp_replace(full_id , '[^0-9]', '') INTO integer_part_str , regexp_replace(full_id , '[^a-z and ^A-Z]', '') INTO string_part from dual ; */
integer_part_str := regexp_replace(full_id , '[^0-9]', '') ;
string_part := regexp_replace(full_id , '[^a-z and ^A-Z]', '') ;
integer_part := TO_NUMBER(integer_part_str);
integer_part := integer_part + 1 ;
integer_part_str := TO_CHAR(integer_part) ;
out_id := string_part + integer_part_str ;
return out_id;
END;
I have a table named BRANDS in Database and the max value for column BRAND_ID is 'Brand05'. The expected output is 'Brand06'.
However when i run:
select get_id5('BRAND_ID' , 'BRANDS') from dual;
or
DECLARE
a VARCHAR(32) ;
BEGIN
a := get_id5('BRAND_ID' , 'BRANDS');
END;
I am getting the below error:
ORA-00923: FROM keyword not found where expected
You need spaces between the FROM and the table_name and the INTO should be outside of the SQL text:
execute immediate 'select MAX('||in_col_name||') FROM ' || in_tbl_name INTO full_id;
You could also use DBMS_ASSERT:
execute immediate 'select MAX('||DBMS_ASSERT.SIMPLE_SQL_NAME(in_col_name)||') FROM ' || DBMS_ASSERT.SIMPLE_SQL_NAME(in_tbl_name) INTO full_id;
Then you need to use || as the string concatenation operator (rather than +).
Your function could be:
create or replace function get_id5(
in_col_name IN VARCHAR2,
in_tbl_name IN VARCHAR2
) RETURN VARCHAR2
IS
full_id VARCHAR2(32);
BEGIN
execute immediate 'select MAX('||DBMS_ASSERT.SIMPLE_SQL_NAME(in_col_name)||') '
|| 'FROM ' || DBMS_ASSERT.SIMPLE_SQL_NAME(in_tbl_name)
INTO full_id;
return regexp_replace(full_id , '\d+', '')
|| TO_CHAR(regexp_replace(full_id , '\D+', '') + 1);
END;
/
For the sample data:
CREATE TABLE brands (brand_id) AS
SELECT 'BRAND5' FROM DUAL;
Then:
select get_id5('BRAND_ID' , 'BRANDS') AS id from dual;
Outputs:
ID
BRAND6
db<>fiddle here
A better solution
To generate the number, use a SEQUENCE or, from Oracle 12, an IDENTITY column and, if you must have a string prefix then use a virtual column to generate it.
CREATE TABLE brands(
ID NUMBER
GENERATED ALWAYS AS IDENTITY
PRIMARY KEY,
brand_id VARCHAR2(10)
GENERATED ALWAYS
AS (CAST('BRAND' || TO_CHAR(id, 'FM000') AS VARCHAR2(10)))
);
BEGIN
INSERT INTO brands (id) VALUES (DEFAULT);
INSERT INTO brands (id) VALUES (DEFAULT);
INSERT INTO brands (id) VALUES (DEFAULT);
END;
/
SELECT * FROM brands;
Outputs:
ID
BRAND_ID
1
BRAND001
2
BRAND002
3
BRAND003
db<>fiddle here

Dynamically select columns for json_table

I have this query:
DECLARE
rc sys_refcursor;
j_keys varchar2(2000);
query_s varchar2(20000);
BEGIN
j_keys := '(
SELECT
listagg(distinct k.COLUMN_VALUE || '' varchar(256) PATH ''$.'' || k.COLUMN_VALUE, '', '') as j_cols
FROM (select json_response as json_value from SOME_TABLE where param=''some_param'') t
CROSS APPLY JSON_TABLE(
t.json_value,
''$[*]''
COLUMNS (
idx FOR ORDINALITY,
json_obj VARCHAR2(4000) FORMAT JSON PATH ''$''
)
) jt
CROSS APPLY get_keys( jt.json_obj ) k
)';
query_s := 'SELECT * FROM json_table((select json_response from SOME_TABLE where param=''some_param''), ''$[*]''
COLUMNS
' || j_keys || ')';
open rc for query_s;
dbms_sql.return_result(rc);
END;
It's a nasty query, meant to test the possibility of dynamically selecting columns for the json_table (and then parse any json-string in the selected clob - named json_response in SOME_TABLE)
Not entirely sure my syntax is set correct, but currently it complains about:
ORA-00904: invalid identifier
on line 22 (the "open rc for '...' line)
You want to run the first query rather than creating a string literal containing the text of the query and then put the output from the first query into the second query string:
DECLARE
rc sys_refcursor;
j_keys varchar2(2000);
query_s varchar2(20000);
BEGIN
SELECT listagg(
k.COLUMN_VALUE || ' varchar(256) PATH ''$.' || k.COLUMN_VALUE || '''',
','
)
INTO j_keys
FROM ( SELECT JSON_QUERY( json_value, '$[1]' RETURNING CLOB) AS json_obj
FROM table_name
)t
CROSS APPLY get_keys( t.json_obj ) k;
query_s := 'SELECT jt.*
FROM table_name t
CROSS APPLY JSON_TABLE(
t.json_value,
''$[*]''
COLUMNS
' || j_keys || ') jt';
open rc for query_s;
DECLARE
col1 VARCHAR2(50);
col2 VARCHAR2(50);
col3 VARCHAR2(50);
BEGIN
LOOP
FETCH rc INTO col1, col2, col3;
EXIT WHEN rc%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( col1 || ', ' || col2 || ', ' || col3 );
END LOOP;
END;
-- or
-- dbms_sql.return_result(rc);
END;
/
Which, given this setup:
CREATE TABLE table_name (
id NUMBER
GENERATED ALWAYS AS IDENTITY
PRIMARY KEY,
json_value CLOB
CHECK( json_value IS JSON )
);
INSERT INTO table_name ( json_value ) VALUES (
'[{"column1":"value1","column2":"value2","column3":"value3"},
{"column1":"value4","column2":"value5","column3":"value6"},
{"column3":"value9","column1":"value7","column2":"value8"}]'
);
CREATE FUNCTION get_keys(
value IN CLOB
) RETURN SYS.ODCIVARCHAR2LIST PIPELINED
IS
js JSON_OBJECT_T := JSON_OBJECT_T( value );
keys JSON_KEY_LIST;
BEGIN
keys := js.get_keys();
FOR i in 1 .. keys.COUNT LOOP
PIPE ROW ( keys(i) );
END LOOP;
END;
/
CREATE FUNCTION get_value(
value IN CLOB,
path IN VARCHAR2
) RETURN VARCHAR2
IS
js JSON_OBJECT_T := JSON_OBJECT_T( value );
BEGIN
RETURN js.get_string( path );
END;
/
Outputs:
value1, value2, value3
value4, value5, value6
value7, value8, value9
db<>fiddle here

How to have XMLForest returning all columns of a table

I have a table with a lot of columns (e.g : Column1, Column 2, Column 3, Column 4, ...)
I'd like to use XMLElement and XMLForest function to generate an XML with each column being a tag.
I'm only able to do this by manually adding each column in the XMLForest :
e.g :
SELECT
XMLElement("ParentTag",
XMLForest(TABLE.Column1,
TABLE.Column2,
TABLE.Column2,
...)
)
FROM ...
Results :
<ParentTag> <Column1>Value1</Column1> <Column2>Value2</Column2> ...</ParentTag>
However i'd like to avoid typing each column as their number could increase in the future.
How can i do something like this ? :
SELECT
XMLElement("ParentTag",
XMLForest(TABLE.*)
)
FROM ...
You can use a PLSQL procedure to get your requirement done. Here in the PLSQL procedure, it would accept a Tablename and then generate the XMLForest and show the result. See below:
-- Creating a type of XMLTYPE
CREATE OR REPLACE TYPE Outpt IS TABLE OF XMLTYPE;
/
--Procedure with In parameter as Tablename and out parameter as resultset
CREATE OR REPLACE PROCEDURE XM_FOREST (tabnm VARCHAR2, v_out IN OUT Outpt)
AS
var VARCHAR2 (4000);
v_sql VARCHAR2 (4000);
BEGIN
FOR i IN (SELECT cname
FROM col
WHERE tname = tabnm)
LOOP
var := var || ',' || i.cname;
END LOOP;
var := LTRIM (var, ',');
v_sql :=
'select XMLElement("ParentTag",XMLForest('
|| var
|| ' ) ) from '
|| tabnm;
EXECUTE IMMEDIATE v_sql BULK COLLECT INTO v_out;
END;
--------------
--Execution
DECLARE
var_out Outpt := Outpt ();
LCLOB CLOB;
BEGIN
var_out.EXTEND;
XM_FOREST (tabnm => 'EMPLOYEE', v_out => var_out);
FOR i IN 1 .. var_out.COUNT
LOOP
LCLOB := var_out (i).getCLOBVAL ();
DBMS_OUTPUT.put_line (LCLOB);
END LOOP;
END;
------
--Result
SQL> /
<ParentTag><EMPLOYEE_ID>1</EMPLOYEE_ID><FIRST_NAME>XXX</FIRST_NAME></ParentTag>
<ParentTag><EMPLOYEE_ID>2</EMPLOYEE_ID><FIRST_NAME>YYY</FIRST_NAME></ParentTag>
PL/SQL procedure successfully completed.
How can i do something like this ? :
SELECT
XMLElement("ParentTag",
XMLForest(TABLE.*)
)
FROM ...
You cannot, you will have to type out all the names individually.
You could generate the query using dynamic SQL
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name (
id NUMBER,
a NUMBER,
b NUMBER,
c NUMBER,
d NUMBER
);
Query 1:
SELECT '
SELECT XMLElement(
"ParentTag",
XMLForest( '
|| LISTAGG( '"' || column_name || '"', ',' )
WITHIN GROUP ( ORDER BY Column_id )
||' ) ) FROM ...' AS query
FROM user_tab_columns
WHERE table_name = 'TABLE_NAME'
Results:
| QUERY |
|-------------------------------------------------------|
| SELECT XMLElement( |
| "ParentTag", |
| XMLForest( "ID","A","B","C","D" ) ) FROM ... |

Generatting insert statement for given table/column value dynamically

I need to write a procedure, which should return insert script for any given table dynamically. Below is the signature of the procedure.
P_export_data_dml(table_name IN, column_name IN, column_value IN, data_dml OUT)
Ex:
table T1
C1 | C2
---------- | ----------
10 | 20
10 | 21
table T2
C1 | C2 | C3
------- | ------- | --------
10 | 20 | 30
10 | 21 | 31
case1
Input:
P_export_data_dml(T1, C1, 10, data_dml)
Expected output:
Insert into T1(C1, C2) values(10,20);
Insert into T1(C1, C2) values(10,21);
case2
Input:
P_export_data_dml(T2, C1, 10, data_dml)
Expected output:
Insert into T2(C1, C2, C3) values(10, 20, 30);
Insert into T2(C1, C2, C3) values(10, 21, 31);
I am able to generate an insert statement when there is only one record in a table for given input. (Using all_tab_columns and then fetching given table for each column to form parts of the Insert statement, and then finally concatenating all the strings to form final INSERT statement).
But I am facing a challenge to form an insert statement when there are multiple records. Can you please help me with the logic to loop through the records and to form INSERT statement for all the records.
Try this procedure. Note that it is good for NUMBER, VARCHAR, DATE (when has a default format) data types
set serveroutput on
create or replace
procedure p_export_data_dml(p_table in varchar2, p_filter_column in varchar2, p_filter_value in varchar2, p_dmls in out varchar2)
is
cursor c_statements(p_table varchar2, p_filter_column varchar2, p_filter_value varchar2) is
select 'select ''insert into '||p_table||' ('||
listagg(column_name, ', ') within group (order by column_id) ||') values(''''''||'||
listagg(column_name, '||'''''', ''''''||') within group (order by column_id)||'||'''''');'' from '||p_table||' where '||p_filter_column||' = '''||p_filter_value||'''' insert_statement
from user_tab_columns
where table_name = upper(p_table);
v_output varchar2(4000);
v_sql varchar2(4000);
type t_cursor is ref cursor;
c_cur t_cursor;
begin
for r_statements in c_statements(p_table, p_filter_column, p_filter_value) loop
v_sql := r_statements.insert_statement;
dbms_output.put_line(v_sql);
open c_cur for v_sql;
loop
fetch c_cur into v_output;
exit when c_cur%notfound;
if p_dmls = null then
p_dmls := v_output;
else
p_dmls := p_dmls || '
'||v_output;
end if;
end loop;
close c_cur;
end loop;
end;
/
Then you can try executing using the below
declare
v_text varchar2(32000);
begin
p_export_data_dml('t1', 'c1', 10, v_text);
dbms_output.put_line(v_text);
end;
/
The output can be something like this:
insert into t1 (C1, C2, C3, C4) values('10', '1', '11', '12-SEP-2017 07:54:38');
insert into t1 (C1, C2, C3, C4) values('10', '2', '12', '12-SEP-2017 07:54:38');
insert into t1 (C1, C2, C3, C4) values('10', '3', '13', '12-SEP-2017 07:54:38');
It isn't clear from your question what data-type you are planning on using for data_dml OUT.
This could be a collection, or a clob, etc.
Please note, that most Oracle-supporting editors like SQL Developer, Intellij IDEA, TOAD, etc. already have this kind of thing built right in, with robust implementation to transform result sets into INSERTS.
That being said, this kind of thing can be achieved using Dynamic SQL.
Here are a couple light-weight examples, built on the premise you plan on using only NUMBERs for column_value params. Overloads could be added for others, or ANYDATA, as needed.
Example 1: This example will return multiple INSERT statements in a CLOB (a collection return type could be used instead). It will only generate INSERTs for NUMBER DATA_TYPE columns. Note no error handling here.
CREATE OR REPLACE PROCEDURE P_EXPORT_DATA_DML(P_TABLE_NAME IN VARCHAR2, P_COLUMN_NAME IN VARCHAR2, P_COLUMN_VALUE IN NUMBER, V_DATA_DML OUT CLOB)
IS
P_COLUMN_LIST CLOB;
V_VALUES_LIST CLOB;
TYPE USER_TAB_COL_TABLE IS TABLE OF USER_TAB_COLUMNS%ROWTYPE;
V_USER_TAB_COLS USER_TAB_COL_TABLE;
V_SELECTER_SQL_TEXT CLOB := '';
BEGIN
SELECT *
BULK COLLECT INTO V_USER_TAB_COLS
FROM USER_TAB_COLUMNS
WHERE USER_TAB_COLUMNS.TABLE_NAME = P_TABLE_NAME
AND USER_TAB_COLUMNS.DATA_TYPE IN ('NUMBER');
P_COLUMN_LIST := P_COLUMN_LIST || V_USER_TAB_COLS(1).COLUMN_NAME;
V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || V_USER_TAB_COLS(1).COLUMN_NAME;
FOR POINTER IN 2..V_USER_TAB_COLS.COUNT
LOOP
P_COLUMN_LIST := P_COLUMN_LIST || ',' || V_USER_TAB_COLS(POINTER).COLUMN_NAME;
V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || Q'!||','|| !' || V_USER_TAB_COLS(POINTER).COLUMN_NAME;
END LOOP;
V_SELECTER_SQL_TEXT := UTL_LMS.FORMAT_MESSAGE(Q'!SELECT LISTAGG('INSERT INTO %s !', P_TABLE_NAME) || '(' || P_COLUMN_LIST || Q'!) VALUES ( '||!' || V_SELECTER_SQL_TEXT || UTL_LMS.FORMAT_MESSAGE(Q'!||');'||CHR(10)||CHR(13) ) WITHIN GROUP (ORDER BY %s ASC) FROM !', P_COLUMN_NAME) || P_TABLE_NAME || ' WHERE ' || P_COLUMN_NAME || ' = ' || P_COLUMN_VALUE;
EXECUTE IMMEDIATE V_SELECTER_SQL_TEXT INTO V_DATA_DML;
END;
/
Then try it out for T1/C1:
DECLARE
V_RESULT CLOB;
BEGIN
P_EXPORT_DATA_DML('T1','C1',10,V_RESULT);
DBMS_OUTPUT.PUT_LINE(V_RESULT);
END;
/
INSERT INTO T1 (C1,C2) VALUES ( 10,20);
INSERT INTO T1 (C1,C2) VALUES ( 10,21);
PL/SQL procedure successfully completed.
Or for T2/C1:
DECLARE
V_RESULT CLOB;
BEGIN
P_EXPORT_DATA_DML('T2','C1',10,V_RESULT);
DBMS_OUTPUT.PUT_LINE(V_RESULT);
END;
/
INSERT INTO T2 (C1,C2,C3) VALUES ( 10,20,30);
INSERT INTO T2 (C1,C2,C3) VALUES ( 10,21,31);
PL/SQL procedure successfully completed.
Or for T2/C2:
...
P_EXPORT_DATA_DML('T2','C2',20,V_RESULT);
...
INSERT INTO T2 (C1,C2,C3) VALUES ( 10,20,30);
PL/SQL procedure successfully completed.
To support other DATA_TYPEs, you'll need to handle DATE/TIMESTAMP -> CHAR conversions, quoting CHARs, etc.
Here's an example that supports NUMBER + VARCHAR2
CREATE OR REPLACE PROCEDURE P_EXPORT_DATA_DML(P_TABLE_NAME IN VARCHAR2, P_COLUMN_NAME IN VARCHAR2, P_COLUMN_VALUE IN NUMBER, V_DATA_DML OUT CLOB)
IS
P_COLUMN_LIST CLOB;
V_VALUES_LIST CLOB;
TYPE USER_TAB_COL_TABLE IS TABLE OF USER_TAB_COLUMNS%ROWTYPE;
V_USER_TAB_COLS USER_TAB_COL_TABLE;
V_SELECTER_SQL_TEXT CLOB := '';
BEGIN
SELECT *
BULK COLLECT INTO V_USER_TAB_COLS
FROM USER_TAB_COLUMNS
WHERE USER_TAB_COLUMNS.TABLE_NAME = P_TABLE_NAME
AND USER_TAB_COLUMNS.DATA_TYPE IN ('NUMBER', 'VARCHAR2');
P_COLUMN_LIST := P_COLUMN_LIST || V_USER_TAB_COLS(1).COLUMN_NAME;
CASE WHEN V_USER_TAB_COLS(1).DATA_TYPE = 'NUMBER'
THEN
V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || V_USER_TAB_COLS(1).COLUMN_NAME;
WHEN V_USER_TAB_COLS(1).DATA_TYPE = 'VARCHAR2'
THEN
V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || Q'!''''||!' || V_USER_TAB_COLS(1).COLUMN_NAME || Q'!||''''!';
END CASE;
FOR POINTER IN 2..V_USER_TAB_COLS.COUNT
LOOP
P_COLUMN_LIST := P_COLUMN_LIST || ',' || V_USER_TAB_COLS(POINTER).COLUMN_NAME;
CASE WHEN V_USER_TAB_COLS(POINTER).DATA_TYPE = 'NUMBER'
THEN
V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || Q'!||','|| !' || V_USER_TAB_COLS(POINTER).COLUMN_NAME;
WHEN V_USER_TAB_COLS(POINTER).DATA_TYPE = 'VARCHAR2'
THEN
V_SELECTER_SQL_TEXT := V_SELECTER_SQL_TEXT || Q'!||','|| !' || Q'!''''||!' || V_USER_TAB_COLS(POINTER).COLUMN_NAME || Q'!||''''!';
END CASE;
END LOOP;
V_SELECTER_SQL_TEXT := UTL_LMS.FORMAT_MESSAGE(Q'!SELECT LISTAGG('INSERT INTO %s !', P_TABLE_NAME) || '(' || P_COLUMN_LIST || Q'!) VALUES ( '||!' || V_SELECTER_SQL_TEXT || UTL_LMS.FORMAT_MESSAGE(Q'!||');'||CHR(10)||CHR(13) ) WITHIN GROUP (ORDER BY %s ASC) FROM !', P_COLUMN_NAME) || P_TABLE_NAME || ' WHERE ' || P_COLUMN_NAME || ' = ' || P_COLUMN_VALUE;
EXECUTE IMMEDIATE V_SELECTER_SQL_TEXT INTO V_DATA_DML;
END;
/
Test it out with a VARCHAR2 table:
CREATE TABLE T3 (C1 VARCHAR2(64), C2 NUMBER, C3 VARCHAR2(64));
INSERT INTO T3 VALUES ('XX',10,'AA');
INSERT INTO T3 VALUES ('XQ',10,'AQ');
INSERT INTO T3 VALUES ('XX',20,'AA');
Getting multiple rows:
DECLARE
V_RESULT CLOB;
BEGIN
P_EXPORT_DATA_DML('T3','C2',10,V_RESULT);
DBMS_OUTPUT.PUT_LINE(V_RESULT);
END;
/
INSERT INTO T3 (C1,C2,C3) VALUES ( 'XQ',10,'AQ');
INSERT INTO T3 (C1,C2,C3) VALUES ( 'XX',10,'AA');
PL/SQL procedure successfully completed.

Cursor inside procedure body

I am trying to declare a cursor inside procedure body.
I know it is supposed to be done in the declare block but the table the cursor refers is created inside the procedure body.
--TABLE MAY OR MAY NOT BE PRESENT PRIOR TO PROCEDURE EXECUTION
SELECT COUNT(*)
INTO ln_cnt
FROM User_Tables
WHERE table_name = 'TMP$UOM_COMBO_GEN';
IF ln_cnt > 0 THEN
EXECUTE IMMEDIATE ' CREATE TABLE TMP$UOM_COMBO_GEN (UOM_ID VARCHAR2(20 BYTE), HIER_CODE VARCHAR2(20 BYTE),NODE_CODE VARCHAR2(200 BYTE))';
END IF;
CURSOR C_HIER
IS
SELECT DISTINCT HIER_CODE FROM TMP$UOM_COMBO_GEN WHERE UOM_ID=P_UOM_ID;
FOR HIER IN C_HIER
LOOP
IF C_HIER%ROWCOUNT = 1 THEN
LV_SQL2 := '(SELECT UOM_ID, NODE_CODE '||HIER.HIER_CODE||' FROM TMP$UOM_COMBO_GEN WHERE UOM_ID='''||P_UOM_ID||''' AND HIER_CODE='''||HIER.HIER_CODE||''')'||HIER.HIER_CODE;
LV_SORT := ' ORDER BY '||HIER.HIER_CODE||'';
LV_SQL := 'SELECT * FROM ' || LV_SQL2;
ELSE
LV_SQL3 := ' LEFT OUTER JOIN(SELECT NODE_CODE '||HIER.HIER_CODE||' FROM TMP$UOM_COMBO_GEN WHERE UOM_ID='''||P_UOM_ID||''' AND HIER_CODE='''||HIER.HIER_CODE||''')'||HIER.HIER_CODE ||' ON 1=1';
LV_SORT := LV_SORT||','||HIER.HIER_CODE||'';
LV_SQL := LV_SQL || LV_SQL3;
END IF;
END LOOP;
I am getting the following error.
Error(17,10): PLS-00103: Encountered the symbol "C_HIER" when expecting one of the following: := . ( # % ;
Well a table once created is stored in database and u can refer it from wherever u want to refer in the schema .
Also there a change may be required in your code in the following part
SELECT COUNT(*)
INTO ln_cnt
FROM User_Tables
WHERE table_name = 'TMP$UOM_COMBO_GEN';
IF ln_cnt > 0 THEN -- means only if table exists u want to create the table which will
--throw an exception if table is already there ,
--so better equate it to 0
EXECUTE IMMEDIATE ' CREATE TABLE TMP$UOM_COMBO_GEN (UOM_ID VARCHAR2(20 BYTE), HIER_CODE VARCHAR2(20 BYTE),NODE_CODE VARCHAR2(200 BYTE))';
END IF;
Now , if u really have a requirement to create a new table every time some condition is true/false and u then want to select the table in a cursor do something like following by using reference cursors
create or replace procedure abc(Table_name varchar2 , param_list varchar2 , where_clause varchar2) is
c_hier sys_refcursor ;
LV_SQL2 varchar2(2000) ;
LV_SORT varchar2(2000) ;
LV_SQL varchar2(2000) ;
LV_SQL3 varchar2(2000) ;
begin
SELECT COUNT(*)
INTO ln_cnt
FROM User_Tables
WHERE table_name = Table_name; -- Use any table here
IF ln_cnt = 0 THEN
EXECUTE IMMEDIATE ' CREATE TABLE '||Table_name||' '||param_list;
END IF;
open c_hier for 'SELECT DISTINCT '||param_list||' FROM '||table_name||' '||where_clause;
FOR HIER IN C_HIER
LOOP
IF C_HIER%ROWCOUNT = 1 THEN
LV_SQL2 := '(SELECT UOM_ID, NODE_CODE '||HIER.HIER_CODE||' FROM TMP$UOM_COMBO_GEN WHERE UOM_ID='''||P_UOM_ID||''' AND HIER_CODE='''||HIER.HIER_CODE||''')'||HIER.HIER_CODE;
LV_SORT := ' ORDER BY '||HIER.HIER_CODE||'';
LV_SQL := 'SELECT * FROM ' || LV_SQL2;
ELSE
LV_SQL3 := ' LEFT OUTER JOIN(SELECT NODE_CODE '||HIER.HIER_CODE||' FROM TMP$UOM_COMBO_GEN WHERE UOM_ID='''||P_UOM_ID||''' AND HIER_CODE='''||HIER.HIER_CODE||''')'||HIER.HIER_CODE ||' ON 1=1';
LV_SORT := LV_SORT||','||HIER.HIER_CODE||'';
LV_SQL := LV_SQL || LV_SQL3;
END IF;
END LOOP;