I have a table with two columes: product (varchar) and usage (varchar). I need to loop the table to one variable in the format "product;usage;product;usage...". How can this be done? The following loop does not work:
FOR product IN (SELECT product FROM licence_check) LOOP
product_usage := (licence_check.product || ';' || licence_check.usage);
END LOOP;
Does it have to be a loop? For not-that-many values, LISTAGG might do the job:
SQL> select listagg(dname||';'||loc, ';') within group (order by deptno) result
2 from dept;
RESULT
--------------------------------------------------------------------------------
ACCOUNTING;NEW YORK;RESEARCH;DALLAS;SALES;CHICAGO;OPERATIONS;BOSTON
SQL>
If it has to be a LOOP, I'd say that you miss concatenation with the PRODUCT_USAGE itself. Have a look:
SQL> declare
2 l_result varchar2(100);
3 begin
4 for cur_r in (select dname, loc from dept) loop
5 l_result := l_result || cur_r.dname ||';'||cur_r.loc ||';';
-----------
This is what you miss
6 end loop;
7 dbms_output.put_line(rtrim(l_result, ';'));
8 end;
9 /
ACCOUNTING;NEW YORK;RESEARCH;DALLAS;SALES;CHICAGO;OPERATIONS;BOSTON
PL/SQL procedure successfully completed.
SQL>
Related
I have simple PL/SQL block with below code
declare
rule1 varchar2(100 char);
begin
for i in (select table_name from all_tables where table_owner='EqEDI') loop
execute immediate 'select rule_stmt from rulebook ' into rule1 ;
execute immediate rule1 into result;
dbms_output.put_line('Result is '||result);
end loop;
end;
the rule statement stored in table rulebook is :
"'select count(1) from '|| tablename"
I want this above statement to be executed for all tables present for given owner
but while executing, it does not replace tablename in query with actual tables.
How can I achieve this with simple PL/SQL block.
Regards
rulebook table's contents is kind of wrong. Not that you can NOT do it the way you stored select statement into it - it is just impractical as you have to remove single quotes, remove tablename (as you can't even bind it, but concatenate what cursor returned) ... too much unnecessary jobs to be done.
Also, check all_tables and names of its columns - there's no table_owner, just owner.
Therefore, I'd suggest you to store such a statement:
SQL> SELECT * FROM rulebook;
RULE_STMT
--------------------------------------------------------------------------------
select count(*) from
Fix your PL/SQL script:
SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
2 rule1 VARCHAR2 (100 CHAR);
3 l_str VARCHAR2 (100);
4 result NUMBER;
5 BEGIN
6 FOR i IN (SELECT table_name
7 FROM all_tables
8 WHERE owner = 'SCOTT'
9 AND table_name = 'EMP')
10 LOOP
11 EXECUTE IMMEDIATE 'select rule_stmt from rulebook '
12 INTO rule1;
13
14 l_str := rule1 || i.table_name;
15
16 EXECUTE IMMEDIATE l_str
17 INTO result;
18
19 DBMS_OUTPUT.put_line ('Result is ' || result);
20 END LOOP;
21 END;
22 /
Result is 14
PL/SQL procedure successfully completed.
SQL>
Let's say I have a table child and I want in a procedure to select random from 1 to 5, random values from the table and print them. How can i do it?
create table child(name varchar2(20), age number);
insert into child(name, age) values('A',5);
insert into child(name, age) values('B',12);
insert into child(name, age) values('C',7);
insert into child(name, age) values('D',4);
create or replace procedure random_child
as
l_name child.name%type;
l_age child.age%type;
begin
for i in(select dbms_random.value(1,5)
from (select name from child sample(50)))
loop
DBMS_OUTPUT.put_line(i.name);
end loop;
end;
It gives me a PLS-00302: name must be declared
You can use:
CREATE PROCEDURE random_child
AS
BEGIN
FOR i in(
SELECT name,
FLOOR(DBMS_RANDOM.VALUE(1,6)) AS value
FROM child
SAMPLE(50)
)
LOOP
DBMS_OUTPUT.put_line(i.name || ' ' || i.value);
END LOOP;
END;
/
Then:
BEGIN
random_child();
END;
/
May randomly output:
B 3
C 2
D 2
db<>fiddle here
There's no i.name there; alias is missing at the end of line #6:
SQL> create or replace procedure random_child
2 as
3 l_name child.name%type;
4 l_age child.age%type;
5 begin
6 for i in(select dbms_random.value(1,5) as name --> here
7 from (select name from child sample(50)))
8 loop
9 DBMS_OUTPUT.put_line(i.name);
10 end loop;
11 end;
12 /
Procedure created.
SQL> exec random_child
1,30966411991963041689918865935551009464
1,13993832387089615287177388489291237644
3,85292920191145794430114472793297022632
PL/SQL procedure successfully completed.
SQL>
I am trying to use variable v_values with set of values inside cursor in the Where clause using IN operator but it returns no record.
create or replace PROCEDURE MyProc IS
/* Cursor decleration */
CURSOR CUR_DUMMY (v_values as varchar2)
IS
SELECT COL1,COL2,COL3
FROM TABLE
WHERE COL1 IN v_values;
l_values varchar2();
BEGIN
l_values:='(''one'',''two'',''three'')';
FOR REC IN CUR_DUMMY (l_values)
LOOP
dbms.output.put_line(REC.col1 || ' ' || REC.col2 || ' ' || REC.col3);
END LOOP;
END;
Any suggestion how to resolve this issue?
IN operator requires a list of values, not the values as comma delimited string.
One simple solution is using nested tables:
create table tab (col1,col2,col3) as
select 'row'||rownum, 2, 3 from dual connect by level<=10
/
create or replace procedure proc is
cursor cur (vl sys.odciVarchar2List) is
select col1,col2,col3
from tab
where col1 in (select column_value val from table (vl));
begin
for rec in cur (sys.odciVarchar2List ('row1','row3','row9')) loop
dbms_output.put_line (rec.col1||' '||rec.col2||' '||rec.col3);
end loop;
end;
/
SQL> exec proc
row1 2 3
row3 2 3
row9 2 3
Here's an example based on Scott's EMP table (as I don't have yours, whose name is invalid anyway). Besides, you should pay attention to fix obvious errors, such as dbms.output (there's no dot but underline), varchar2() variable with no size, v_values as while declaring the cursor (there's no as) and similar stuff which are easy to fix and show that you're actually paying attention to what you're doing, and not typing just because you must.
Read comments within code.
SQL> set serveroutput on;
SQL> create or replace procedure myproc is
2 cursor cur_dummy (v_values varchar2) is
3 -- split V_VALUES into rows
4 select empno, ename, job
5 from emp
6 where job in (select trim(regexp_substr(v_values, '[^,]+', 1, level))
7 from dual
8 connect by level <= regexp_count(v_values, ',') + 1
9 );
10 l_values varchar2(100);
11 begin
12 -- no need to complicate with single quotes and stuff; just name those values
13 l_values:='CLERK, MANAGER';
14
15 for rec in cur_dummy (l_values)
16 loop
17 dbms_output.put_line(rec.empno || ' ' || rec.ename || ' ' || rec.job);
18 end loop;
19 end;
20 /
Procedure created.
SQL> exec myproc;
7369 SMITH CLERK
7566 JONES MANAGER
7698 BLAKE MANAGER
7876 ADAMS CLERK
7900 JAMES CLERK
PL/SQL procedure successfully completed.
SQL>
I would like to replace all the cells of a table that match a specific word. I wrote this query:
UPDATE table_name
SET column_name=REPLACE(column_name
,'string_to_be_replaced'
, 'string_replaced')
What will the procedure that will replace the values for all the columns of table_name not only one column as in the code above?
It is something that I will have to do it againg and again to update some tables.
Thanks
Here is some test data:
SQL> select * from t23;
ID NAME JOB
---------- -------------------- --------------------
10 JACK JANITOR
20 JAN TUTOR
30 MOHAN JAZZ DANCER
40 JOAN MECHANIC
SQL>
I want to replace all instances of 'JA' with 'MO'. This means I need to update NAME and JOB. Obviously I could write an UPDATE statement but I can also generate one using the magic of the data dictionary:
SQL> select column_name, data_type
2 from user_tab_cols
3 where table_name = 'T23';
COLUMN_NAME DATA_TYPE
------------------------------ ----------
ID NUMBER
NAME VARCHAR2
JOB VARCHAR2
SQL>
This seems like a one-off task, for which I need an anonymous PL/SQL block rather than a permanent procedure. So here is a script, saved as gen_upd_stmt.sql.
declare
stmt varchar2(32767);
target_string varchar2(20) := 'JA';
replace_string varchar2(20) := 'MO';
begin
stmt := 'update t23 set ';
for lrec in ( select column_name
, row_number() over (order by column_id) as id
from user_tab_cols
where table_name = 'T23'
and data_type = 'VARCHAR2'
)
loop
if lrec.id > 1 then
stmt := stmt || ',';
end if;
stmt := stmt || lrec.column_name || '=replace('
|| lrec.column_name || ', ''' || target_string
|| ''',''' || replace_string
|| ''')';
end loop;
-- uncomment for debugging
-- dbms_output.put_line(stmt);
execute immediate stmt;
dbms_output.put_line('rows updated = '|| to_char(sql%rowcount));
end;
/
Note that generating dynamic SQL is a gnarly process, because syntax errors are thrown at run time rather than compile time. Escaping quotes can be particularly pestilential. It's a good idea to display the generated statement to make debugging easier.
Also, I restricted the targeted columns to those with the correct datatype. This isn't strictly necessary, as replace() will handle type casting for us (in most cases). But it's more efficient with big tables to exclude columns we know won't match.
Anyway, let's roll!
SQL> set serveroutput on
SQL> #gen_upd_stmt
rows updated = 4
PL/SQL procedure successfully completed.
SQL>
As expected all four rows are updated but not all are changed:
SQL> select * from t23;
ID NAME JOB
---------- -------------------- --------------------
10 MOCK MONITOR
20 MON TUTOR
30 MOHAN MOZZ DANCER
40 JOAN MECHANIC
SQL>
For completeness the generated statement was this:
update t23 set NAME=replace(NAME, 'JA','MO'),JOB=replace(JOB, 'JA','MO')
With a larger table or more complicated requirement I would probably introduce line breaks with chr(13)||chr(10) to make the generated code more readable (for debugging).
You can try something like this. Rest update it as per your requirement.
DECLARE
L_statement VARCHAR2(4000) := 'UPDATE :table_name SET ';
CURSOR c_get_cols IS
SELECT column_name
FROM dba_tab_cols
WHERE table_name = :table_name;
TYPE Cur_tab IS TABLE OF c_get_cols%ROWTYPE;
L_tab Cur_tab;
BEGIN
OPEN c_get_cols;
FETCH C_get_cols INTO L_tab;
CLOSE C_get_cols;
FOR i IN 1..L_tab.COUNT
LOOP
L_statement := L_statement || L_tab(i).column_name || ' = REPLACE(column_name, :string_to_be_replaced, :string_replaced)';
IF i != L_tab.COUNT
THEN
L_statement := L_statement || ',';
END IF;
END LOOP;
EXECUTE IMMEDIATE L_statement;
END;
/
I tried it using cursor: (replace owner and table_name with respective values)
DECLARE
COL_NAME ALL_TAB_COLUMNS.COLUMN_NAME%TYPE;
string_to_be_replaced VARCHAR2(20) ;
string_replaced VARCHAR2 (20) ;
exc_invalid_id EXCEPTION;
PRAGMA EXCEPTION_INIT(exc_invalid_id, -904);
CURSOR c1 IS
SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS WHERE owner=<owner_name> AND TABLE_NAME=<table_name> ;
BEGIN
string_to_be_replaced :='';
string_replaced :='';
OPEN C1;
LOOP
FETCH C1 INTO COL_NAME ;
EXIT WHEN C1%NOTFOUND;
EXECUTE immediate('UPDATE <owner_name>.<table_name> SET '||col_name||'=REPLACE('||col_name||','''||string_to_be_replaced||''','''||string_replaced||''')');
END LOOP;
CLOSE C1;
END;
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