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;
Related
I would like to ask how can i print output in procedure more than one statement.
Assume that you want to show dba_objects and segments row count. But i can not use dbms_sql.return_result my version is 11g.
Something like,
create or replace procedure get_rows_count
(
cursor1 out SYS_REFCURSOR,
cursor2 out SYS_REFCURSOR
)
as
begin
open cursor1 for select count(*) from dba_objects;
open cursor2 for select count(*) from dba_segments;
end get_rows_count;
/
Assume that you want to show dba_objects and segments row count
I assumed it. Conclusion: that's not the way to do it. If you want to get row count from e.g. dba_objects, then you should just
select count(*) from dba_objects;
in any variation you want (pure SQL, function that returns that number, procedure with an OUT parameter (worse option), ...). But, creating a procedure which uses ref cursor for that purpose is ... well, wrong.
If I got you wrong, then: procedure you wrote is OK. You can call it from another PL/SQL procedure (named or anonymous), fetch result into a variable and do something with it (e.g. display it).
Your procedure (selects from Scott's tables; I don't have access to DBA_ views):
SQL> CREATE OR REPLACE PROCEDURE get_rows_count (cursor1 OUT SYS_REFCURSOR,
2 cursor2 OUT SYS_REFCURSOR)
3 AS
4 BEGIN
5 OPEN cursor1 FOR SELECT * FROM emp;
6
7 OPEN cursor2 FOR SELECT * FROM dept;
8 END get_rows_count;
9 /
Procedure created.
How to call it? See line #8:
SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
2 rc1 SYS_REFCURSOR;
3 rc2 SYS_REFCURSOR;
4 --
5 rw1 emp%ROWTYPE;
6 rw2 dept%ROWTYPE;
7 BEGIN
8 get_rows_count (rc1, rc2);
9
10 DBMS_OUTPUT.put_line ('Employees -----------');
11
12 LOOP
13 FETCH rc1 INTO rw1;
14
15 EXIT WHEN rc1%NOTFOUND;
16
17 DBMS_OUTPUT.put_line (rw1.ename);
18 END LOOP;
19
20 --
21 DBMS_OUTPUT.put_line ('Departments ---------');
22
23 LOOP
24 FETCH rc2 INTO rw2;
25
26 EXIT WHEN rc2%NOTFOUND;
27
28 DBMS_OUTPUT.put_line (rw2.dname);
29 END LOOP;
30
31 DBMS_OUTPUT.put_line ('First ref cursor: ' || rc1%ROWCOUNT);
32 DBMS_OUTPUT.put_line ('Second ref cursor: ' || rc2%ROWCOUNT);
33 END;
34 /
Result:
Employees -----------
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER
Departments ---------
ACCOUNTING
RESEARCH
SALES
OPERATIONS
First ref cursor: 14
Second ref cursor: 4
PL/SQL procedure successfully completed.
SQL>
You can use famous DBMS_OUTPUT.PUT_LINE() along with %ROWCOUNT suffix for your case such as
SET serveroutput ON
CREATE OR REPLACE PROCEDURE get_rows_count(
cursor1 OUT SYS_REFCURSOR,
cursor2 OUT SYS_REFCURSOR,
count1 OUT INT,
count2 OUT INT
) AS
cur_rec_obj user_objects%ROWTYPE;
cur_rec_seg user_segments%ROWTYPE;
BEGIN
OPEN cursor1 FOR SELECT * FROM user_objects;
LOOP
FETCH cursor1 INTO cur_rec_obj;
EXIT WHEN cursor1%NOTFOUND;
END LOOP;
OPEN cursor2 FOR SELECT * FROM user_segments;
LOOP
FETCH cursor2 INTO cur_rec_seg;
EXIT WHEN cursor2%NOTFOUND;
END LOOP;
count1 := cursor1%ROWCOUNT;
count2 := cursor2%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE(count1);
DBMS_OUTPUT.PUT_LINE(count2);
END;
/
and you can call as follows from the SQL Window of PL/SQL Developer :
DECLARE
v_cursor1 SYS_REFCURSOR;
v_cursor2 SYS_REFCURSOR;
v_count1 INT;
v_count2 INT;
BEGIN
get_rows_count(v_cursor1, v_cursor2, v_count1, v_count2 );
END;
/
For security purposes, I want to have an entity that returns an entire column value.
Example of an entity: stored-procedure, function, table-valued function.
Example:
CREATE OR REPLACE FUNCTION simpleSelect RETURN VARCHAR2 AS
output1 VARCHAR2(100);
BEGIN
Select (col1 ) INTO output1 from SCHEMA1.TABLE1;
RETURN output1;
END
;
The above gives the following error:
[Error] Execution (17: 9): ORA-01422: exact fetch returns more than requested number of rows
ORA-06512:
What would be the syntax to create the entity and also to call the entity?
Option 1: Use a cursor in a procedure:
CREATE PROCEDURE simpleSelect
(
o_cursor OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN o_cursor FOR
Select col1 from SCHEMA1.TABLE1;
END;
/
Option 2: Use a pipelined function:
CREATE TYPE string_list IS TABLE OF VARCHAR2(100);
CREATE FUNCTION simpleSelect
RETURN string_list PIPELINED
IS
v_cursor SYS_REFCURSOR;
v_value SCHEMA1.TABLE1.COL1%TYPE;
BEGIN
OPEN v_cursor FOR
Select col1 from SCHEMA1.TABLE1;
LOOP
FETCH v_cursor INTO v_value;
EXIT WHEN v_cursor%NOTFOUND;
PIPE ROW (v_value);
END LOOP;
CLOSE v_cursor;
EXCEPTION
WHEN NO_DATA_NEEDED THEN
CLOSE v_cursor;
END;
/
Option 3: Use a (non-pipelined) function:
CREATE TYPE string_list IS TABLE OF VARCHAR2(100);
CREATE FUNCTION simpleSelect
RETURN string_list
IS
v_values string_list;
BEGIN
SELECT col1
BULK COLLECT INTO v_values
FROM SCHEMA1.TABLE1;
RETURN v_values;
END;
/
Note: While this may be short to type, if there are a large number of rows then you will create a huge data structure in memory and is likely to result in performance issues; it may be better to stream the data to a third-party application using a pipelined function or a cursor.
fiddle
Here's a simple option; see if it helps. It uses Oracle built-in type which lets you store strings; another, for numbers, is sys.odcinumberlist (can't tell what datatype is your col1 so I picked one of them). Benefit? You don't even have to create your own type!
SQL> create or replace function simpleselect
2 return sys.odcivarchar2list
3 is
4 retval sys.odcivarchar2list;
5 begin
6 select ename
7 bulk collect into retval
8 from emp;
9 return retval;
10 end;
11 /
Function created.
What is the result?
SQL> select * from table(simpleselect);
COLUMN_VALUE
--------------------------------------------------------------------------------
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER
14 rows selected.
SQL>
Below sql command is not working in procedure
PROCEDURE P_EMPDETAIL
AS
V_WHERE := 'E.EMP_ID = 123'B
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO EMPLOYEE E ' || V_WHERE || ;
END;
It seems to me there are various issues with your syntax and approach (you shouldn't be using dynamic SQL this way), perhaps you should learn PL/SQL and reference the manuals. The insert statement is also wrong. Below is the correct syntax.
CREATE OR REPLACE PROCEDURE P_EMPDETAIL as
V_WHERE varchar2(100);
BEGIN
V_WHERE := 'E.EMP_ID = 123';
EXECUTE IMMEDIATE 'INSERT INTO EMPLOYEE E (colname) values (1) ' || V_WHERE;
END;
Well, not exactly like that (obviously; otherwise, you wouldn't be asking for help).
It is unclear what you want to do because syntax is really strange. If you wanted to insert a row into the table, then:
SQL> CREATE TABLE employees
2 (
3 emp_id NUMBER
4 );
Table created.
SQL> CREATE OR REPLACE PROCEDURE p_empdetail (par_emp_id IN NUMBER)
2 AS
3 l_str VARCHAR2 (200);
4 BEGIN
5 l_str := 'insert into employees (emp_id) values (:1)';
6
7 EXECUTE IMMEDIATE l_str
8 USING par_emp_id;
9 END;
10 /
Procedure created.
Testing:
SQL> EXEC p_empdetail(123);
PL/SQL procedure successfully completed.
SQL> SELECT * FROM employees;
EMP_ID
----------
123
SQL>
I'm trying to send variable schema name to cursor via procedure input
Here is my lame try, but you can see what I want to do:
CREATE OR REPLACE PROCEDURE HOUSEKEEPING
(SCHEMANAME in varchar2)
IS
CURSOR data_instances IS select table_name
from SCHEMANAME.table_name where TYPE='PERMANENT' and rownum<200 ;
BEGIN
DBMS_OUTPUT.PUT_LINE(SCHEMANAME);
END;
/
it throws expected
PL/SQL: ORA-00942: table or view does not exist
is there lawful way to make schema name work as variable? thanks
There is a way; you'll need some kind of dynamic SQL because you can't use schema (or object) names like that. For example, you could use refcursor instead.
Sample table:
SQL> create table table_name as
2 select 'EMP' table_name, 'PERMANENT' type from dual union all
3 select 'DEPT' , 'TEMPORARY' from dual union all
4 select 'BONUS' , 'PERMANENT' from dual;
Table created.
Procedure; note the way I composed SELECT statement first (so that I could display it and check whether it is correct), and then used it in OPEN. Loop is here to ... well, loop through the cursor. I'm just displaying table names I found - you'd probably do something smarter.
SQL> create or replace procedure housekeeping (par_schemaname in varchar2)
2 is
3 l_str varchar2(500);
4 l_rc sys_refcursor;
5 l_table_name varchar2(30);
6 begin
7 l_str := 'select table_name from ' ||
8 dbms_assert.schema_name(upper(par_schemaname)) ||
9 '.table_name where type = ''PERMANENT'' and rownum < 200';
10 open l_rc for l_str;
11
12 loop
13 fetch l_rc into l_table_name;
14 exit when l_rc%notfound;
15
16 dbms_output.put_line(l_table_name);
17 end loop;
18 close l_rc;
19 end;
20 /
Procedure created.
Testing:
SQL> set serveroutput on
SQL> exec housekeeping('SCOTT');
EMP
BONUS
PL/SQL procedure successfully completed.
SQL>
I need to limit the number of rows that a query can return for a specific user. I know that I can limit in the SQL query, but I need to avoid that a specific user can build a query that return a huge amount of rows. for this reason I need to limit in the configuration.
Does anybody know if it is possible?
If you have Enterprise Edition you can implement VPD rule using DBMS_RLS package:
SQL> create or replace package pac1
2 is
3 function limit_rows(owner varchar2, tab varchar2) return varchar2;
4 end;
5 /
Package created.
SQL> create or replace package body pac1
2 is
3 function limit_rows(owner varchar2, tab varchar2) return varchar2
4 is
5 begin
6 return ' rownum <= 3';
7 end;
8 end pac1;
9 /
Package body created.
SQL> begin
2 dbms_rls.add_policy('HR','EMPLOYEES','RULE1','HR','PAC1.LIMIT_ROWS','SELECT');
3 end;
4 /
PL/SQL procedure successfully completed.
SQL> select first_name, last_name from hr.employees;
FIRST_NAME LAST_NAME
-------------------- -------------------------
Ellen Abel
Sundar Ande
Mozhe Atkinson
SQL>
You can achieve this with cursors. See example:
SQL> create or replace function exec_query(sql_text varchar2) return sys_refcursor
2 is
3 num_rows number := 3;
4 c1 sys_refcursor;
5 begin
6 open c1 for 'with test as (' || sql_text || ') select * from test where rownum <=' || num_rows ;
7 return c1;
8 end;
9 /
Function created.
SQL> variable c1 refcursor;
SQL> exec :c1 := exec_query('select last_name, salary from hr.employees');
PL/SQL procedure successfully completed.
SQL> print :c1;
LAST_NAME SALARY
------------------------- ----------
King 24000
Kochhar 17000
De Haan 17000
Amount of data is not necessarily equal to amount of load. A single row query can kill a database if it's complex enough.
Answer to this is really complex.
You can take the SQL and create a SQL PLAN from it, and from that data limit from estimated cost and/or estimated rows.
How To Create and Evolve a SQL Plan Baseline
How do I display and read the execution plans for a SQL statement
Example:
I'll create a temp table:
create table tmp_table2 as
select * from user_objects;
then I use Oracle's plan estimation with out actually running the query
declare
l_sql varchar2 (32767);
begin
delete from plan_table;
l_sql := 'select * from tmp_table2';
execute immediate 'explain plan for ' || l_sql;
for i in (select cardinality,
cost,
bytes,
cpu_cost
from PLAN_TABLE
where operation = 'SELECT STATEMENT') loop
if i.cardinality /* rows */
> 500 then
dbms_output.put_line ('Too many rows');
elsif i.cpu_cost > 500000 then
dbms_output.put_line ('Too much CPU');
else
dbms_output.put_line ('About right');
end if;
end loop;
end;
Result;
==>
PL/SQL block executed
Too many rows
Or you can use the Resource manager to limit per session:
Using the Database Resource Manager