Oracle, limit the row number per user - oracle

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

Related

Oracle SQL -- (Procedure of function) that returns entire column

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>

How to execute Dynamic sql with insert statement in Oracle

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>

Select Query in cursor FOR LOOP

Pretty new to Oracle, need help on this Procedure, using Oracle 11g
Sample table data (TAB1):
ID
Amount
Currency
10
300
GBP
15
500
GBP
20
100
GBP
Requirement is to select all the ID's from TAB1 based on currency and store it in a variable and later use these ID's in other select Queries within the same Stored Procedure.
CREATE OR REPLACE PROCEDURE myproc (i_id IN VARCHAR, i_curr IN VARCHAR)
AS
CURSOR GET_I IS
SELECT ID, CURRENCY
FROM TAB1
WHERE CURRENCY = 'GBP';
-- This will give me 3 ID's (10, 15 & 20) which I am storing in variable r_1 below.
r_1 VARCHAR (5) : NULL;
BEGIN
OPEN GET_I;
LOOP
FETCH GET_I INTO r_1;
IF GET_I%NOTFOUND
THEN
EXIT;
---In the below ELSE PART can we run a select query using the value stored in r_1??
--ELSE
--Data stored in r_1 to be used in further select queries in later part and output of the below
--be returned as SYS_REFCURSOR;
--BELOW two lines gives error
--FOR I in r_1 (
--SELECT ID FROM TAB2 WHERE TAB2.ID=r_1);
END IF;
END LOOP;
CLOSE GET_ID;
END;
It looks you want to use a nested FOR loop.
I'd suggest you to use cursor FOR loops - they are easier to maintain as you don't have to declare cursor variable (by the way, in your case it wouldn't work anyway as you'd like to store both ID and CURRENCY into a scalar R_1 variable), open the cursor, pay attention about exiting the loop and closing the cursor. In a cursor FOR loop, Oracle does all that for you.
Here's an example:
Sample table:
SQL> select * from tab1;
ID AMOUNT CUR
---------- ---------- ---
10 300 GBP
15 500 GBP
20 100 GBP
Procedure:
SQL> create or replace procedure myproc as
2 begin
3 for cur_id in (select id from tab1) loop
4 dbms_output.put_line('ID = ' || cur_id.id);
5 for cur_other in (select amount, currency
6 from tab1
7 where id = cur_id.id --> use ID fetched in outer loop
8 )
9 loop
10 dbms_output.put_line(cur_other.amount ||' - '|| cur_other.currency);
11 end loop;
12 end loop;
13 end;
14 /
Procedure created.
Testing:
SQL> set serveroutput on
SQL> exec myproc;
ID = 10
300 - GBP
ID = 15
500 - GBP
ID = 20
100 - GBP
PL/SQL procedure successfully completed.
SQL>
How to return a refcursor?
SQL> create or replace procedure myproc (par_id in tab1.id%type,
2 par_rc out sys_refcursor) as
3 begin
4 for cur_id in (select id
5 from tab1
6 where id = par_id
7 ) loop
8
9 open par_rc for select amount, currency
10 from tab1
11 where id = cur_id.id;
12 end loop;
13 end;
14 /
Procedure created.
SQL> var l_rc refcursor
SQL>
SQL> exec myproc(10, :l_rc);
PL/SQL procedure successfully completed.
SQL> print l_rc
AMOUNT CUR
---------- ---
300 GBP
SQL>

How to run a query on different schemes at once

I want to run a query on different schemes to get data and export it. I use the following code
DECLARE
sql_statment VARCHAR2(2000);
BEGIN
FOR c IN (SELECT brchcode FROM brchs) LOOP
sql_statment := 'select distinct ''' || c.brchcode ||''', t.risuid from ' || c.brchcode ||
'.reg_individualacnt_detail t
where t.historytypecode = 60';
EXECUTE IMMEDIATE sql_statment;
END LOOP;
END;
where brchcode is the name of different schemes
I can't see any output. what can I do?
Code you wrote can't work as you have to return the result into something; it is PL/SQL and requires an INTO clause. As you chose to return two values (columns) and multiple rows, that can't be a scalar variable; you could pick ref cursor or a collection, for example.
Here's an example which shows one option.
I'll be using two schemas: SCOTT (current schema) and HR. Both will be having the DEPT table.
As Scott already has it, I'll create one in HR schema and grant access to Scott (otherwise, Scott won't even see it and the procedure (i.e. the function) will fail):
SQL> connect hr/hr
Connected.
SQL> create table dept (deptno number, dname varchar2(10), loc varchar2(10));
Table created.
SQL> insert into dept values (55, 'IT', 'Zagreb');
1 row created.
SQL> grant select on dept to scott;
Grant succeeded.
SQL> commit;
Commit complete.
Back to Scott, to create a table (which contains schema names I'll be selecting from) and a function. I chose to return REF CURSOR; you could return something else, if you want.
SQL> connect scott/tiger
Connected.
SQL> create table brchs (brchcode varchar2(10));
Table created.
SQL> insert into brchs (brchcode)
2 select 'scott' from dual union all
3 select 'hr' from dual;
2 rows created.
SQL> create or replace function f_br
2 return sys_refcursor
3 is
4 l_str varchar2(4000);
5 l_rc sys_refcursor;
6 begin
7 for cur_r in (select brchcode from brchs) loop
8 l_str := l_str ||
9 'union all
10 select ' || chr(39)|| cur_r.brchcode ||chr(39) || ', d.dname
11 from ' || cur_r.brchcode ||'.dept d
12 where d.deptno > 0';
13 end loop;
14
15 l_str := ltrim(l_str, 'union all');
16
17 open l_rc for l_str;
18 return l_rc;
19 end;
20 /
Function created.
SQL>
Finally, testing:
SQL> select f_br from dual;
F_BR
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
'SCOT DNAME
----- --------------
scott ACCOUNTING
scott RESEARCH
scott SALES
scott OPERATIONS
hr IT
SQL>

Access cursor by column name dynamically

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;

Resources