Retrieving the data from the table using the PL/SQL - oracle

I want to retrieve all the information about each department from the DEPARTMENT table and display the information on the screen.
Column name Data type Constraints
DEPARTMENT_ID NUMBER(5) PK
DEPARTMENT_NAME VARCHAR2(25) NOT NULL
LOCATION_ID VARCHAR2(15)
Sample Output:
Department Details are :
1000, ADMIN, HQ-101
1010, DEVELOPMENT, CBE-103
1020, TESTING, CHN-102
I have a code which is as follows-
set serveroutput on;
declare
v_dno department.department_id%type;
v_dname department.department_name%type;
v_loc department.location_id%type;
begin
dbms_output.put_line('Department Details are :');
loop
dbms_output.put_line(v_dno || ', ' || v_dname || ', ' || v_loc);
end loop;
commit;
end;
/
But this isn't producing any output, please help. Thanks in advance!

Yes, a simple way to do that is to use a loop. But, you're looping through nothing (never fetch anything into those variables) and never exit the loop. Besides, what exactly are you committing?
Here's how you might have done it (based on Scott's DEPT table which is similar to yours):
SQL> set serveroutput on
SQL> begin
2 for cur_r in (select deptno, dname, loc from dept) loop
3 dbms_output.put_line(cur_r.deptno ||' '|| cur_r.dname ||' '|| cur_r.loc);
4 end loop;
5 end;
6 /
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
PL/SQL procedure successfully completed.
SQL>

Related

How to pass list of table names as parameter in stored procedure in Oracle SQL Developer? How to use PLSQL VARRAY or nested table?

I have a code which accepts a table name as parameter and creates subpartitions on that table name for the partitions. My table is partitioned on list of source system codes and range subpartitoned on monthly basis.
Question: If suppose I have variable list of tables and I want to create subpartitions for all then I need to modify this procedure every time to pass varying list of tables.
Can I use PLSQL VARRAY or nested table to hold my list of tables, and pass this VARRAY or nested table as a parameter to the below procedure and create subpartitions for all the table names the VARRAY or nested table is holding?
Your help is much appreciated. Many thanks!
Code:
CREATE OR REPLACE PROCEDURE execute_subpartition ( table_name IN varchar2)
IS
tbl_nm varchar2(30) := table_name;
sqlstr VARCHAR2(1000);
CURSOR TabSubPartition IS
SELECT TABLE_NAME, PARTITION_NAME
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = tbl_nm
ORDER BY PARTITION_NAME;
BEGIN
FOR aSubPart IN TabSubPartition LOOP
IF TRUNC(LAST_DAY(SYSDATE)) = TRUNC(SYSDATE)
sqlstr := 'ALTER TABLE TUCEL001.' || aSubPart.TABLE_NAME || ' MODIFY PARTITION ' ||
aSubPart.PARTITION_NAME ||' ADD SUBPARTITION ' || aSubPart.PARTITION_NAME || '_' ||
TO_CHAR(TRUNC(LAST_DAY(SYSDATE) + 1), 'MON_YYYY') ||' VALUES LESS THAN (TIMESTAMP ''' ||
TRIM(to_char(add_months((TRUNC(LAST_DAY(SYSDATE))+1), 1), 'SYYYY-MM-DD HH24:MI:SS',
'NLS_CALENDAR=GREGORIAN')) || ''')';
dbms_output.put_line(sqlstr);
EXECUTE IMMEDIATE sqlstr;
ELSE
dbms_output.put_line('the condition did not match');
END IF;
END LOOP;
Exception
WHEN OTHERS
THEN
dbms_output.put_line('encountered an error, because the sub-partitions which are being created
already exists');
END;
My 11g doesn't have partitioning enabled so I can't demonstrate it.
But, see if this example (of creating some tables) helps. You don't have to declare your own type - sys.odcivarchar2list should do. Read it using the table function and use its column_value in your dynamic SQL.
Procedure:
SQL> create or replace procedure p_test (par_tables in sys.odcivarchar2list) as
2 l_str varchar2(200);
3 begin
4 for cur_r in (select column_value as table_name
5 from table(par_tables)
6 )
7 loop
8 dbms_output.put_line('table name = ' || cur_r.table_name);
9 l_str := 'create table ' || dbms_assert.qualified_sql_name(cur_r.table_name) ||
10 ' (id number,' ||
11 ' name varchar2(20))';
12 execute immediate(l_str);
13 end loop;
14 end;
15 /
Procedure created.
Testing:
SQL> exec p_test(sys.odcivarchar2list('tab_a', 'tab_b'));
PL/SQL procedure successfully completed.
SQL> desc tab_a;
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(20)
SQL> desc tab_b;
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(20)
SQL>

Searching specific row for a particular data by the use of cursor in PL/SQL in Oracle

I am new to PL/SQL and I am trying to search a specific data from schema. I want to use nested cursors and make the query dynamic. Please help me out with the approach.
Here's an example which uses a loop (though, not nested loops, as you want - I'm not sure why) and dynamic SQL (execute immediate).
It is based on Scott's schema; I'm looking for number of appearances of the employee name "KING" in all tables in current schema (querying USER_TABLES), within the ENAME column (querying USER_TAB_COLUMNS).
This is just to give you an idea; feel free to develop it further.
Some sample data:
SQL> set serveroutput on
SQL> select ename from emp order by ename;
ENAME
----------
ALLEN
BLAKE
CLARK
FORD
JAMES
JONES
KING
MARTIN
MILLER
SMITH
TURNER
WARD
12 rows selected.
SQL> select ename from bonus;
ENAME
----------
KING
Let's search for that KING person:
SQL> declare
2 l_str varchar2(500);
3 l_cnt number := 0;
4 begin
5 for cur_r in (select u.table_name, u.column_name
6 from user_tab_columns u, user_tables t
7 where u.table_name = t.table_name
8 and u.column_name = 'ENAME'
9 )
10 loop
11 l_str := 'SELECT COUNT(*) FROM ' || cur_r.table_name ||
12 ' WHERE ' || cur_r.column_name || ' like (''%KING%'')';
13
14 execute immediate (l_str) into l_cnt;
15
16 if l_cnt > 0 then
17 dbms_output.put_line(l_cnt ||' : ' || cur_r.table_name);
18 end if;
19 end loop;
20 end;
21 /
1 : EMP
1 : BONUS
PL/SQL procedure successfully completed.
SQL>
The result says that KING appears once in EMP and BONUS tables.
try this..
SET SERVEROUTPUT ON SIZE 100000
----Final OP----------------------
CREATE OR REPLACE PROCEDURE StringQuery(names VARCHAR2)
IS
match_count INTEGER;
v_data_type VARCHAR2(255) :='VARCHAR2';
v_search_string VARCHAR2(4000) :=names;
BEGIN
FOR t IN (SELECT table_name, column_name FROM user_tab_columns where data_type = v_data_type) LOOP
EXECUTE IMMEDIATE
'SELECT COUNT(*) FROM '||t.table_name||' WHERE '||t.column_name||' = :1'
INTO match_count
USING v_search_string;
IF match_count > 0 THEN
dbms_output.put_line( 'Table Name: '||t.table_name ||', Column Name: '||t.column_name||', Found This Many Times: '||match_count );
--EXECUTE IMMEDIATE 'SELECT * FROM '||t.table_name||'WHERE '||t.column_name||'='||v_search_string||' ';
END IF;
END LOOP;
END;
/
CALL StringQuery('&Enter_String_Value');

replace values in all columns of a table with oracle

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;

Oracle, limit the row number per user

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

How to use a variable from a cursor in the select statement of another cursor in pl/sql

I want to run a query, get the results and then iterate through the results of that query with another select statement using the values of the first statement in my 2nd statement (cursor).
I have 40 users in my db.
All the users have the same db schema structure.
I want to get the username via :
SELECT distinct username
from all_users
then use the user name to run a query like this:
Select lastname, firstname, email, email2 from username.member.
My results set will return multiple rows so I need a row type as well.
I have tried many different pl/sql combinations:
DECLARE
CURSOR client_cur IS
SELECT distinct username
from all_users
where length(username) = 3;
-- client cursor
CURSOR emails_cur (cli all_users.username%TYPE) IS
SELECT id, name
FROM cli.org;
BEGIN
FOR client IN client_cur LOOP
dbms_output.put_line('Client is '|| client.username);
FOR email_rec in client_cur(client.username) LOOP
dbms_output.put_line('Org id is ' ||email_rec.id || ' org nam ' || email_rec.name);
END LOOP;
END LOOP;
END;
/
and
DECLARE
CURSOR c1 IS
SELECT distinct username from all_users where length(username) = 3;
client c1%rowtype;
cursor c2 is Select id, name, allow_digest_flg from c1.username.org;
digest c2%rowtype;
-- declare record variable that represents a row fetched from the employees table
-- employee_rec c1%ROWTYPE;
BEGIN
-- open the explicit cursor and use it to fetch data into employee_rec
OPEN c1;
loop
FETCH c1 INTO client;
open c2;
loop
fetch c2 into digest;
DBMS_OUTPUT.PUT_LINE('digest is : ' || c2.id || ' and name is ' || c2.name || ' flg is ' || c2.allow_digest_flg );
end loop;
end loop;
END;
/
AND MANY VARIATIONS OF THESE.
Can someone help me.
THANKS
You need to use dynamic SQL to achieve this; something like:
DECLARE
TYPE cur_type IS REF CURSOR;
CURSOR client_cur IS
SELECT DISTING username
FROM all_users
WHERE length(username) = 3;
emails_cur cur_type;
l_cur_string VARCHAR2(128);
l_email_id <type>;
l_name <type>;
BEGIN
FOR client IN client_cur LOOP
dbms_output.put_line('Client is '|| client.username);
l_cur_string := 'SELECT id, name FROM '
|| client.username || '.org';
OPEN emails_cur FOR l_cur_string;
LOOP
FETCH emails_cur INTO l_email_id, l_name;
EXIT WHEN emails_cur%NOTFOUND;
dbms_output.put_line('Org id is ' || l_email_id
|| ' org name ' || l_name);
END LOOP;
CLOSE emails_cur;
END LOOP;
END;
/
Edited to correct two errors, and to add links to 10g documentation for OPEN-FOR and an example.
Edited to make the inner cursor query a string variable.
You can certainly do something like
SQL> ed
Wrote file afiedt.buf
1 begin
2 for d in (select * from dept)
3 loop
4 for e in (select * from emp where deptno=d.deptno)
5 loop
6 dbms_output.put_line( 'Employee ' || e.ename ||
7 ' in department ' || d.dname );
8 end loop;
9 end loop;
10* end;
SQL> /
Employee CLARK in department ACCOUNTING
Employee KING in department ACCOUNTING
Employee MILLER in department ACCOUNTING
Employee smith in department RESEARCH
Employee JONES in department RESEARCH
Employee SCOTT in department RESEARCH
Employee ADAMS in department RESEARCH
Employee FORD in department RESEARCH
Employee ALLEN in department SALES
Employee WARD in department SALES
Employee MARTIN in department SALES
Employee BLAKE in department SALES
Employee TURNER in department SALES
Employee JAMES in department SALES
PL/SQL procedure successfully completed.
Or something equivalent using explicit cursors.
SQL> ed
Wrote file afiedt.buf
1 declare
2 cursor dept_cur
3 is select *
4 from dept;
5 d dept_cur%rowtype;
6 cursor emp_cur( p_deptno IN dept.deptno%type )
7 is select *
8 from emp
9 where deptno = p_deptno;
10 e emp_cur%rowtype;
11 begin
12 open dept_cur;
13 loop
14 fetch dept_cur into d;
15 exit when dept_cur%notfound;
16 open emp_cur( d.deptno );
17 loop
18 fetch emp_cur into e;
19 exit when emp_cur%notfound;
20 dbms_output.put_line( 'Employee ' || e.ename ||
21 ' in department ' || d.dname );
22 end loop;
23 close emp_cur;
24 end loop;
25 close dept_cur;
26* end;
27 /
Employee CLARK in department ACCOUNTING
Employee KING in department ACCOUNTING
Employee MILLER in department ACCOUNTING
Employee smith in department RESEARCH
Employee JONES in department RESEARCH
Employee SCOTT in department RESEARCH
Employee ADAMS in department RESEARCH
Employee FORD in department RESEARCH
Employee ALLEN in department SALES
Employee WARD in department SALES
Employee MARTIN in department SALES
Employee BLAKE in department SALES
Employee TURNER in department SALES
Employee JAMES in department SALES
PL/SQL procedure successfully completed.
However, if you find yourself using nested cursor FOR loops, it is almost always more efficient to let the database join the two results for you. After all, relational databases are really, really good at joining. I'm guessing here at what your tables look like and how they relate based on the code you posted but something along the lines of
FOR x IN (SELECT *
FROM all_users,
org
WHERE length(all_users.username) = 3
AND all_users.username = org.username )
LOOP
<<do something>>
END LOOP;
Use alter session set current_schema = <username>, in your case as an execute immediate.
See Oracle's documentation for further information.
In your case, that would probably boil down to (untested)
DECLARE
CURSOR client_cur IS
SELECT distinct username
from all_users
where length(username) = 3;
-- client cursor
CURSOR emails_cur IS
SELECT id, name
FROM org;
BEGIN
FOR client IN client_cur LOOP
-- ****
execute immediate
'alter session set current_schema = ' || client.username;
-- ****
FOR email_rec in client_cur LOOP
dbms_output.put_line(
'Org id is ' || email_rec.id ||
' org nam ' || email_rec.name);
END LOOP;
END LOOP;
END;
/

Resources