I am new to oracle and trying to create a stored procedure with the following pl/sql code.
What is wrong with the code?when i replace the searchString with hard code value of '1605%' then the query is executed but not with the variable.
create or replace PACKAGE BODY MAT_INFO AS
PROCEDURE mat_details(mat_id IN varchar, mat_cur OUT SYS_REFCURSOR) AS
searchString varchar2(700);
BEGIN
searchString := ''''||mat_id||'%''';
OPEN mat_cur FOR SELECT mi.* FROM S_JAMM_MAT_INFO mi WHERE mi.MAT_ID LIKE searchString;
END mat_details;
END MAT_INFO;
Can anyone help me what am i doing wrong here?
Thanks
You are using unnecessary quotes:
SQL> declare
2 mat_id varchar2(100) := 'X';
3 searchString varchar2(100) := ''''||mat_id||'%''';
4 searchString2 varchar2(100) := ''||mat_id||'%';
5 num number;
6 begin
7 select count(1)
8 into num
9 from dual
10 where 'X' like searchString;
11 --
12 dbms_output.put_line('1. num= ' || num);
13 --
14 select count(1)
15 into num
16 from dual
17 where 'X' like searchString2;
18 --
19 dbms_output.put_line('2. num= ' || num);
20 end;
21 /
1. num= 0
2. num= 1
PL/SQL procedure successfully completed.
SQL>
Your double quoting could be useful if you would use a dynamic SQL, but in your static query it means that you are looking for a pattern containing quotes as part of the string to look for.
create or replace PACKAGE BODY MAT_INFO AS
PROCEDURE mat_details(
i_mat_id IN varchar,
mat_cur OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN mat_cur FOR
SELECT *
FROM S_JAMM_MAT_INFO
WHERE MAT_ID LIKE i_mat_id || '%';
END mat_details;
END MAT_INFO;
/
You can try to change the procedure like below.
CREATE OR REPLACE PACKAGE BODY MAT_INFO AS
PROCEDURE MAT_DETAILS(MAT_ID IN VARCHAR, MAT_CUR OUT SYS_REFCURSOR) AS
SEARCHSTRING VARCHAR2(700);
CURSOR MAT_CUR (SEARCHSTRING VARCHAR) IS
SELECT MI.* FROM S_JAMM_MAT_INFO MI WHERE MI.MAT_ID LIKE SEARCHSTRING;
BEGIN
FOR DATA IN MAT_CUR(MAT_ID)
LOOP
--YOUR CODE
END LOOP;
END MAT_DETAILS;
END MAT_INFO;
Related
Select all the tables of database where column match than pass table name to next query using loop. If column name and column values matches than return true and exist for loop using a stored procedure:
CREATE OR REPLACE PROCEDURE TEST
(
NAME IN VARCHAR2 ,
ID IN NUMBER,
RE OUT SYS_REFCURSOR
) AS
BEGIN
OPEN RE FOR SELECT A.TABLE_NAME FROM
user_tables A JOIN user_tab_columns C
ON C.TABLE_NAME = A.TABLE_NAME
WHERE C.COLUMN_NAME = NAME;
FOR RE IN LOOP
v_Sql := 'SELECT COUNT(*) FROM '|| LOOP.TABLE_NAME || 'WHERE COLUMN_NAME =
ID';
EXECUTE IMMEDIATE v_Sql
IF v_Sql%ROWCOUNT > 0 THEN
return true;
EXIT
END LOOP;
END TEST;
For more understanding the problem
//Get all the tables of database where campus_id is exist in any table of
database
Campus, Class, Section (3 tables found)
Apply forloop on the records
Select count(campus_id) as total from (table name using loop) where campus_id = 1(value
pass)
if(total > 0){
Exist for loop and return true
}
else{
Again iterate the loop to next value
}
What you described doesn't make much sense. If there are several tables that contain a column you're checking and you exit the loop as soon as you find the first one, what about the rest of them?
Here's what I'd do, see if it helps. I'll create a function (not a procedure) that returns a table. In order to do that, I'll create type(s) first:
SQL> create or replace type t_record as object (tn varchar2(30), cnt number);
2 /
Type created.
SQL> create or replace type t_table as table of t_record;
2 /
Type created.
SQL>
The function:
in a cursor FOR loop I'm selecting tables that contain that column
L_STR is used to compose the SELECT statement
DBMS_OUTPUT.PUT_LINE is used to display it first, so that I could visually check whether it is correctly set or not.
if it is, I'm running it with the EXECUTE IMMEDIATE
the result is stored into a table type and returned to the caller
SQL> create or replace function f_colname
2 (par_column_name in varchar2,
3 par_column_value in varchar2
4 )
5 return t_table
6 is
7 retval t_table := t_table();
8 l_str varchar2(200);
9 l_cnt number;
10 begin
11 for cur_r in (select table_name
12 from user_tab_columns
13 where column_name = par_column_name
14 )
15 loop
16 l_str := 'select count(*) from ' || cur_r.table_name ||
17 ' where ' || par_column_name || ' = ' ||
18 chr(39) || par_column_value || chr(39);
19 -- Display l_str first, to make sure that it is OK:
20 -- dbms_output.put_line(l_str);
21 execute immediate l_str into l_cnt;
22 retval.extend;
23 retval(retval.count) := t_record(cur_r.table_name, l_cnt);
24 end loop;
25 return retval;
26 end;
27 /
Function created.
Testing:
SQL> select * from table (f_colname('DEPTNO', '10'));
TN CNT
------------------------------ ----------
TEST_201812 1
DEPT 1
EMP 3
SQL> select * from table (f_colname('ENAME', 'KING'));
TN CNT
------------------------------ ----------
EMP 1
BONUS 1
SQL>
That won't work properly for some datatypes (such as DATE) and will have to be adjusted, if necessary.
[EDIT: after you edited the question]
OK then, that's even simpler. It should still be a function (that returns a Boolean, as you said that - in case that something's being found - you want to return TRUE). Code is pretty much similar to the previous function.
SQL> create or replace function f_colname
2 (par_column_name in varchar2,
3 par_column_value in varchar2
4 )
5 return boolean
6 is
7 l_str varchar2(200);
8 l_cnt number;
9 retval boolean := false;
10 begin
11 for cur_r in (select table_name
12 from user_tab_columns
13 where column_name = par_column_name
14 )
15 loop
16 l_str := 'select count(*) from ' || cur_r.table_name ||
17 ' where ' || par_column_name || ' = ' ||
18 chr(39) || par_column_value || chr(39);
19 -- Display l_str first, to make sure that it is OK:
20 -- dbms_output.put_line(l_str);
21 execute immediate l_str into l_cnt;
22 if l_cnt > 0 then
23 retval := true;
24 exit;
25 end if;
26 end loop;
27 return retval;
28 end;
29 /
Function created.
Testing: as you can't return Boolean at SQL layer, you have to use an anonymous PL/SQL block, as follows:
SQL> declare
2 l_ret boolean;
3 begin
4 if f_colname('DEPTNO', '15') then
5 dbms_output.put_line('It exists');
6 else
7 dbms_output.put_line('It does not exist');
8 end if;
9 end;
10 /
It does not exist
PL/SQL procedure successfully completed.
SQL>
I have a procedure which receive as input parameter a record with 170 columns (it is based on the structure of a table).
In the procedure I want to call a debugging procedure one of whose parameters is a text string containing all the field names and values of this record.
For example:
CREATE OR REPLACE PROCEDURE xxx (pi_record IN table_name%ROWTYPE) as
text VARCHAR2(10000) := NULL;
BEGIN
...
text := 'pi_record.column1 = ' || pi_record.column1 || CHR(13) ||
'pi_record.column2 = ' || pi_record.column2 || CHR(13) ||
...
'pi_record.column170 = ' || pi_record.column170;
logging_procedure (text);
...
END;
Is there any simple way to achieve this in a dynamic way (looping through record fields names and values) without enumerating all of them?
Maybe something like this:
CREATE OR REPLACE PROCEDURE xxx (pi_record IN table_name%ROWTYPE) as
text VARCHAR2(10000) := NULL;
BEGIN
...
LOOP in pi_record.columns
text := text || CHR(13) || pi_record.column.name || ' : ' || pi_record.column.value
END LOOP
logging_procedure (text);
...
END;
Many thanks,
Here's one way to do that. A package spec contains a variable whose type matches the one we'll use in a procedure.
SQL> set serveroutput on
SQL> create or replace package pkg_xxx
2 as
3 dept_rec dept%rowtype;
4 end;
5 /
Package created.
SQL> create or replace procedure xxx (pi_record in dept%rowtype)
2 as
3 text varchar2 (10000) := null;
4 l_str varchar2 (200);
5 l_var varchar2 (200);
6 begin
7 pkg_xxx.dept_rec := pi_record;
8
9 for cur_r in ( select column_name
10 from user_tab_columns
11 where table_name = 'DEPT'
12 order by column_id)
13 loop
14 l_str :=
15 'begin '
16 || ':x := to_char(pkg_xxx.dept_rec.'
17 || cur_r.column_name
18 || '); '
19 || 'end; ';
20
21 execute immediate l_str using out l_var;
22
23 text := text || chr (10) || cur_r.column_name || ' = ' || l_var;
24 end loop;
25
26 dbms_output.put_line (text);
27 end;
28 /
Procedure created.
Now, let's pass something to the procedure and see what happens:
SQL> declare
2 cursor c1
3 is
4 select *
5 from dept
6 where deptno = 10;
7
8 c1r c1%rowtype;
9 begin
10 open c1;
11 fetch c1 into c1r;
12 close c1;
13
14 xxx (c1r);
15 end;
16 /
DEPTNO = 10
DNAME = ACCOUNTING
LOC = NEW YORK
PL/SQL procedure successfully completed.
SQL>
Huh, kind of works (if that's what you asked). Of course, it is just an example, you'll have to modify it if you want to get something really smart (hint: DATE columns).
The only idea I have is to insert the record into a TEMP table:
CREATE OR REPLACE PROCEDURE xxx (pi_record IN TABLE_NAME%ROWTYPE) AS
TEXT VARCHAR2(10000) := NULL;
item VARCHAR2(1000);
TABLE_DOES_NOT_EXIST EXCEPTION;
PRAGMA EXCEPTION_INIT(TABLE_DOES_NOT_EXIST, -942);
BEGIN
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE TABLE_NAME_TMP';
EXCEPTION
WHEN TABLE_DOES_NOT_EXIST then null;
END;
EXECUTE IMMEDIATE 'CREATE GLOBAL TEMPORARY TABLE TABLE_NAME_TMP AS SELECT * FROM TABLE_NAME WHERE ROWNUM = 0';
DELETE FROM TABLE_NAME_TMP;
INSERT INTO TABLE_NAME_TMP VALUES pi_record;
FOR aCol IN (SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS WHERE table_name = 'TABLE_NAME' ORDER BY COLUMN_ID) LOOP
EXECUTE IMMEDIATE 'SELECT '||aCol.COLUMN_NAME||' FROM TABLE_NAME_TMP' INTO item;
TEXT := TEXT || CHR(13) || aCol.COLUMN_NAME || ' : ' || item;
END LOOP;
DBMS_OUTPUT.PUT_LINE ( TEXT );
END;
In case table TABLE_NAME has static attributes then you should skip dynamic DROP TABLE ... and CREATE GLOBAL TEMPORARY TABLE ... and create the TEMP table only once.
everyone!
I got a different approach to get the difference between records dynamically:
You just have to create the global variables on the package header as bellow:
v_NAME_OF_TABLE_new NAME_OF_TABLE%rowtype;
v_NAME_OF_TABLE_old NAME_OF_TABLE%rowtype;
then create the function on your pkg body that return a boolean even if a field is different:
function is_different(p_old NAME_OF_TABLE%rowtype, p_new NAME_OF_TABLE%rowtype)
return boolean
is
cursor cols is
select tb.COLUMN_NAME
from all_tab_columns tb
where tb.OWNER = 'DW'
and tb.TABLE_NAME = 'NAME_OF_TABLE'
order by tb.COLUMN_ID;
l_sql varchar2(4000);
l_new varchar2(4000);
l_old varchar2(4000);
begin
pkg_NAME.v_NAME_OF_TABLE_new := p_new;
pkg_NAME.v_NAME_OF_TABLE_old := p_old;
for reg in cols loop
l_sql := '
begin
:x := pkg_NAME.v_NAME_OF_TABLE_new.'||reg.COLUMN_NAME||';'||'
end;';
execute immediate l_sql using out l_new;
l_sql := '
begin
:x := pkg_NAME.v_NAME_OF_TABLE_old.'||reg.COLUMN_NAME||';'||'
end;';
execute immediate l_sql using out l_old;
--- dbms_output.put_line(l_new||' - '||l_old);
if nvl(l_new,'NULO') <> nvl(l_old,'NULO') then
return true;
end if;
end loop;
return false;
end;
Atention: This can turn your process heavier and slower.
That's all!
Hope this can be helpful!
I have problem with the compilation of my stored procedure.
create or replace type CartLine as object (
offeringId OfferingIdList
,productLine varchar2(50)
,equipment char(1)
,installment CHAR(1)
,cartItemProcess varchar2(50)
,minimalPrice decimal
);
create or replace type CartLineType is table of CartLine;
create or replace PROCEDURE GetOfferingRecommendation (
cartLineList IN CartLineType,
user IN UserType,
customer IN CustomerType,
processContext IN ProcessContextType,
recommendation out SYS_REFCURSOR )
IS
prodLine VARCHAR2(20);
prodPrice NUMBER(5,0);
BEGIN
FOR i IN cartLineList.FIRST .. cartLineList.LAST
LOOP
SELECT productLine, minimalPrice
INTO prodLine, prodPrice
FROM TABLE(cartLineList(i));
OPEN recommendation FOR
SELECT CAST(REKOM_ID_SEQ.NEXTVAL AS VARCHAR(10))
||'_'||cp.ID_REKOM_OFERTA
||'_'||TO_CHAR(SYSDATE, 'yyyymmdd') AS recommendationId
,cp.ID_REKOM_OFERTA AS offeringId
,cp.PRIORYTET AS priority
FROM REKOM_CROSS_PROM cp
WHERE cp.LINIA_PROD = prodLine
AND prodPrice BETWEEN cp.CENA_MIN AND cp.CENA_MAX
;
END LOOP;
END GetOfferingRecommendation;
It is not getting compiled cause the following statement is wrong:
SELECT productLine, minimalPrice
INTO prodLine, prodPrice
FROM TABLE(cartLineList(i));
I want to select only single value every all new iteration of my loop.
Can somebody help me to resolve my problem?
-- EDIT 1/9/2018 4:26 PM
According to topic:
How to return result of many select statements as one custom table
I tried to rebuild my procedure.
I created types for test:
create or replace TYPE tst AS OBJECT (
rekom_id varchar2(50)
,rekom_priorytet number(5,4)
);
/
create or replace TYPE tst_list IS TABLE OF tst;
After that, I changed my procedure like below:
CREATE OR REPLACE PROCEDURE GetOfferingRecommendation (cartLineList IN CartLineType, recommendation out SYS_REFCURSOR )
IS
CURSOR CUR_TAB IS SELECT productLine, minimalPrice FROM TABLE(cartLineList);
v_tst tst_list;
BEGIN
FOR i IN CUR_TAB
LOOP
EXECUTE IMMEDIATE 'SELECT tst_list(
CAST(REKOM_ID_SEQ.NEXTVAL AS VARCHAR(10))||''_''||cp.ID_REKOM_OFERTA||''_''||TO_CHAR(SYSDATE, ''yyyymmdd'')
,cp.PRIORYTET)
FROM REKOM_CROSS_PROM cp
WHERE cp.LINIA_PROD ='||i.productLine||' AND '||i.minimalPrice||' BETWEEN cp.CENA_MIN AND cp.CENA_MAX'
BULK COLLECT INTO v_tst;
EXIT WHEN CUR_TAB%NOTFOUND;
FOR REC IN 1 .. v_tst.COUNT
LOOP
PIPE ROW (v_tst(REC));
END LOOP;
END LOOP;
OPEN recommendation FOR SELECT * FROM TABLE(v_tst);
END IF;
END GetOfferingRecommendation;
But I can't compile because error occured: PLS-00629
Would you please told me what I do wrong?
You cannot assign variables using a select statement from a collection in a loop like below.
SELECT productLine, minimalPrice
INTO prodLine, prodPrice
FROM TABLE(cartLineList(i));
The collection elements cannot be referred inside a SELECT statement 1 by 1 using a loop. You can loop through the collection as
For i in 1..collection.count
loop
...
..
End loop;
Collection has a number of rows and when you do so, you try to assign many rows to a single variable, which is wrong. You can do either of the below explained. There relevant explanation is inline.
CREATE OR REPLACE PROCEDURE GETOFFERINGRECOMMENDATION (
CARTLINELIST IN CARTLINETYPE,
RECOMMENDATION OUT SYS_REFCURSOR)
IS
TYPE V_PRODLINE IS TABLE OF VARCHAR2 (20)
INDEX BY PLS_INTEGER;
TYPE V_PRODPRICE IS TABLE OF NUMBER (5, 0)
INDEX BY PLS_INTEGER;
PRODLINE V_PRODLINE;
PRODPRICE V_PRODPRICE;
BEGIN
--Putting the collection result to another collection
SELECT PRODUCTLINE,
MINIMALPRICE
BULK COLLECT INTO PRODLINE,
PRODPRICE
FROM TABLE (CARTLINELIST);
-- Assuming number of elements will be same in both prodLine, prodPrice colection, loop can be iterated as below
FOR I IN 1 .. PRODLINE.LAST
LOOP
OPEN RECOMMENDATION FOR
SELECT CAST (REKOM_ID_SEQ.NEXTVAL AS VARCHAR (10) )
|| '_'
|| CP.ID_REKOM_OFERTA
|| '_'
|| TO_CHAR (SYSDATE, 'yyyymmdd') AS RECOMMENDATIONID,
CP.ID_REKOM_OFERTA AS OFFERINGID,
CP.PRIORYTET AS PRIORITY
FROM REKOM_CROSS_PROM CP
WHERE CP.LINIA_PROD = PRODLINE (I)
AND PRODPRICE (I) BETWEEN CP.CENA_MIN AND CP.CENA_MAX;
END LOOP;
END GETOFFERINGRECOMMENDATION;
OR as per #krokodilko.. You can do as below:
CREATE OR REPLACE PROCEDURE GETOFFERINGRECOMMENDATION (
CARTLINELIST IN CARTLINETYPE,
RECOMMENDATION OUT SYS_REFCURSOR)
IS
PRODLINE VARCHAR2 (20);
PRODPRICE NUMBER (5, 0);
BEGIN
FOR I IN 1 .. CARTLINELIST.LAST
LOOP
--Assign the values of the collection to the variable declared.
PRODUCTLINE := CARTLINELIST (I).PRODUCTLINE;
MINIMALPRICE := CARTLINELIST (I).MINIMALPRICE;
OPEN RECOMMENDATION FOR
SELECT CAST (REKOM_ID_SEQ.NEXTVAL AS VARCHAR (10) )
|| '_'
|| CP.ID_REKOM_OFERTA
|| '_'
|| TO_CHAR (SYSDATE, 'yyyymmdd') AS RECOMMENDATIONID,
CP.ID_REKOM_OFERTA AS OFFERINGID,
CP.PRIORYTET AS PRIORITY
FROM REKOM_CROSS_PROM CP
WHERE CP.LINIA_PROD = PRODLINE
AND PRODPRICE BETWEEN CP.CENA_MIN AND CP.CENA_MAX;
END LOOP;
END GETOFFERINGRECOMMENDATION;
Demo:
SQL> CREATE OR REPLACE TYPE CARTLINE AS OBJECT (
2 PRODUCTLINE VARCHAR2 (50),
3 MINIMALPRICE DECIMAL
4 );
5 /
Type created.
SQL> CREATE OR REPLACE TYPE CARTLINETYPE IS TABLE OF CARTLINE;
2 /
Type created.
SQL> CREATE OR REPLACE PROCEDURE GETOFFERINGRECOMMENDATION (
2 CARTLINELIST IN CARTLINETYPE)
3 IS
4 TYPE V_PRODLINE IS TABLE OF VARCHAR2 (20)
5 INDEX BY PLS_INTEGER;
6
7 TYPE V_PRODPRICE IS TABLE OF NUMBER (5, 0)
8 INDEX BY PLS_INTEGER;
9
10 PRODLINE V_PRODLINE;
11 PRODPRICE V_PRODPRICE;
12 BEGIN
13 SELECT PRODUCTLINE,
14 MINIMALPRICE
15 BULK COLLECT INTO PRODLINE,
16 PRODPRICE
17 FROM TABLE (CARTLINELIST);
18
19 FOR I IN 1 .. PRODLINE.COUNT
20 LOOP
21 DBMS_OUTPUT.PUT_LINE ( 'Prod Line '
22 || PRODLINE (I)
23 || ' Prod Price '
24 || PRODPRICE (I) );
25 END LOOP;
26 END GETOFFERINGRECOMMENDATION;
27 /
Procedure created.
Output:
SQL> DECLARE
2 VAR CARTLINETYPE := CARTLINETYPE ();
3 BEGIN
4 --Popuating the collection
5 VAR.EXTEND (2);
6 VAR (1) := CARTLINE ('TypeA', 6.0);
7 VAR (2) := CARTLINE ('TypeB', 7.1);
8
9 --Calling the procedure
10 GETOFFERINGRECOMMENDATION (CARTLINELIST => VAR);
11 END;
12 /
Prod Line TypeA Prod Price 6
Prod Line TypeB Prod Price 7
PL/SQL procedure successfully completed.
SQL>
Use simple assignments instead of SELECT ... FROM TABLE(cartLineList(i));:
LOOP
/* SELECT productLine, minimalPrice INTO prodLine, prodPrice FROM TABLE(cartLineList(i)); */
productLine := cartLineList(i).productLine;
minimalPrice := cartLineList(i).minimalPrice;
.....
.....
END LOOP;
I am trying to dynamically resolve the value from cursor something like below.
create or replace
PROCEDURE test(
PI_JANUS_ID IN VARCHAR2,
PO_dummy out Types.CursorType
)AS
PO_ACTUALCUROSR Types.CursorType;
cur_row tab%ROWTYPE;
val1 varchar2(100);
val2 varchar2(200);
BEGIN
open PO_ACTUALCUROSR for select * from tab;
LOOP
FETCH PO_ACTUALCUROSR into cur_row;
EXIT WHEN PO_ACTUALCUROSR%NOTFOUND;
val1 := 'TNAME';
SELECT 'cur_row.'||val1 INTO val2 FROM DUAL;
dbms_output.put_line('Column Value ' || val2);
END LOOP ;
CLOSE PO_ACTUALCUROSR;
END;
Here if you see I have to call cur_row. to get the value but here I have the column name in a variable (val1). So how will I take the value from the cursor.
If I execute the above block I would see "cur_row.TNAME" but I actually need the value in the cursor.
Is there a way of doing this.
Any help is very much appreciated.
just do
BEGIN
open PO_ACTUALCUROSR for select * from tab;
LOOP
FETCH PO_ACTUALCUROSR into cur_row;
EXIT WHEN PO_ACTUALCUROSR%NOTFOUND;
val2 := cur_row.tname;
dbms_output.put_line(val2);
END LOOP ;
CLOSE PO_ACTUALCUROSR;
end;
or
dbms_output.put_line(cur_row.tname);
directly will also work.
p.s. your variable
val2 varchar2(200);
is better as
val2 tab.tname%type;
if you're saying you need to dynamically pull columns without knowing the column name ahead of time you'd have to use dynamic sql, and as you're using a pl/sql array, we need to put that in a package spec so that we can reference it (as you cannot pass pl/sql only types into dynamic SQL).
SQL> create table tab(id number, col1 varchar2(10));
Table created.
SQL> insert into tab values (1, 'a');
1 row created.
SQL> commit;
Commit complete.
SQL> create package global_var
2 as
3 cur_row tab%rowtype;
4 end;
5 /
Package created.
SQL> declare
2 po_actualcurosr sys_refcursor;
3 val1 varchar2(10) := 'COL1';
4 val2 tab.col1%type;
5 begin
6 open po_actualcurosr for select * from tab;
7
8 loop
9 fetch po_actualcurosr into global_var.cur_row;
10 exit when po_actualcurosr%notfound;
11 execute immediate 'begin :a := global_var.cur_row.'||dbms_assert.simple_sql_name(val1)||'; end;' using out val2;
12 dbms_output.put_line(val2);
13 end loop ;
14 close po_actualcurosr;
15 end;
16 /
a
PL/SQL procedure successfully completed.
Can I access a cursor's column dynamically? I mean by name? something like this:
declare
v_cursor := select * from emp;
begin
FOR reg IN v_cursor LOOP
dbms_output.put_line(**reg['column_name_as_string']**);
end loop;
end;
I know the bold part is not PL/SQL, but I'm looking for something like that and can't find it anywhere.
You can use the package DBMS_SQL to create and access cursors with dynamic queries.
However it's not really straightforward to access a column by name because the DBMS_SQL package uses positioning and in a dynamic query we may not know the order of the columns before the execution.
Furthermore, in the context of this question, it appears that we may not know which column we want to display at compile time, we will assume that the column we want to display is given as a parameter.
We can use DBMS_SQL.describe_columns to analyze the columns of a SELECT query after it has been parsed to build a dynamic mapping of the columns. We will assume that all columns can be cast into VARCHAR2 since we want to display them with DBMS_OUTPUT.
Here's an example:
SQL> CREATE OR REPLACE PROCEDURE display_query_column(p_query VARCHAR2,
2 p_column VARCHAR2) IS
3 l_cursor INTEGER;
4 l_dummy NUMBER;
5 l_description_table dbms_sql.desc_tab3;
6 TYPE column_map_type IS TABLE OF NUMBER INDEX BY VARCHAR2(32767);
7 l_mapping_table column_map_type;
8 l_column_value VARCHAR2(4000);
9 BEGIN
10 l_cursor := dbms_sql.open_cursor;
11 dbms_sql.parse(l_cursor, p_query, dbms_sql.native);
12 -- we build the column mapping
13 dbms_sql.describe_columns3(l_cursor, l_dummy, l_description_table);
14 FOR i IN 1 .. l_description_table.count LOOP
15 l_mapping_table(l_description_table(i).col_name) := i;
16 dbms_sql.define_column(l_cursor, i, l_column_value, 4000);
17 END LOOP;
18 -- main execution loop
19 l_dummy := dbms_sql.execute(l_cursor);
20 LOOP
21 EXIT WHEN dbms_sql.fetch_rows(l_cursor) <= 0;
22 dbms_sql.column_value(l_cursor, l_mapping_table(p_column), l_column_value);
23 dbms_output.put_line(l_column_value);
24 END LOOP;
25 dbms_sql.close_cursor(l_cursor);
26 END;
27 /
Procedure created
We can call this procedure with a query known only at run-time:
SQL> set serveroutput on
SQL> exec display_query_column('SELECT * FROM scott.emp WHERE rownum < 5', 'ENAME');
SMITH
ALLEN
WARD
JONES
PL/SQL procedure successfully completed
SQL> exec display_query_column('SELECT * FROM scott.emp WHERE rownum < 5', 'EMPNO');
7369
7499
7521
7566
PL/SQL procedure successfully completed
Use caution with dynamic SQL: it has the same privileges as the user and can therefore execute any DML and DDL statement allowed for this schema.
For instance, the above procedure could be used to create or drop a table:
SQL> exec display_query_column('CREATE TABLE foo(id number)', '');
begin display_query_column('CREATE TABLE foo(id number)', ''); end;
ORA-01003: aucune instruction analysée
ORA-06512: à "SYS.DBMS_SQL", ligne 1998
ORA-06512: à "APPS.DISPLAY_QUERY_COLUMN", ligne 13
ORA-06512: à ligne 1
SQL> desc foo
Name Type Nullable Default Comments
---- ------ -------- ------- --------
ID NUMBER Y
It's probably easiest to make the query dynamic if you can.
DECLARE
v_cursor SYS_REFCURSOR;
dynamic_column_name VARCHAR2(30) := 'DUMMY';
column_value VARCHAR2(32767);
BEGIN
OPEN v_cursor FOR 'SELECT ' || dynamic_column_name || ' FROM dual';
LOOP
FETCH v_cursor INTO column_value;
EXIT WHEN v_cursor%NOTFOUND;
dbms_output.put_line( column_value );
END LOOP;
CLOSE v_cursor;
END;
If you really want to have a hardcoded SELECT * and dynamically select a column from that by name, I think you could do that using DBMS_SQL as Vincent suggests, but it will be somewhat more complex.
You mean something like:
declare
cursor sel_cur is
select * from someTable;
begin
for rec in sel_cur
loop
dbms_output.put_line('col1: ' || rec.col1);
end loop;
end;