PL/SQL how to display data from select statement inside procedure? - sql

I am not sure how to display my select statement in my procedure. This is my code inside my procedure:
CREATE OR REPLACE PROCEDURE numberOfSupplier (X INT:=0)
AS
rName REGION.R_NAME%TYPE;
nName NATION.N_NAME%TYPE;
sNKeyC SUPPLIER.S_NATIONKEY%TYPE;
BEGIN
FOR rec IN(
SELECT R.R_NAME, N.N_NAME, COUNT(S.S_NATIONKEY)
INTO rName, nName, sNKeyC
FROM REGION R, NATION N, SUPPLIER S
WHERE R.R_REGIONKEY = N.N_REGIONKEY
AND S.S_NATIONKEY = N.N_NATIONKEY
GROUP BY R.R_NAME, N.N_NAME
HAVING COUNT(S.S_NATIONKEY) > X)
LOOP
dbms_output.put_line('R_NAME'||rName);
dbms_output.put_line('N_NAME'||nName);
dbms_output.put_line('COUNT(S_NATIONKEY)'||sNKeyC);
END LOOP;
END;
/
--executing numberOfSupplier
EXECUTE numberOfSupplier(130);
This is what I get, which has no errors but not what I want:
SQL> EXECUTE numberOfSupplier(130);
R_NAME
N_NAME
COUNT(S_NATIONKEY)
R_NAME
N_NAME
COUNT(S_NATIONKEY)
R_NAME
N_NAME
COUNT(S_NATIONKEY)
R_NAME
N_NAME
COUNT(S_NATIONKEY)
What I want to get is this:
R_NAME N_NAME COUNT(S.S_NATIONKEY)
------------------------- ------------------------- --------------------
ASIA INDONESIA 131
ASIA CHINA 145
MIDDLE EAST SAUDI ARABIA 132
EUROPE GERMANY 132
I am able to get the above result if I just execute the select statement, however I dont know how to put this select statement in my procedure and get the table above:
SELECT R.R_NAME, N.N_NAME, COUNT(S.S_NATIONKEY)
FROM REGION R, NATION N, SUPPLIER S
WHERE R.R_REGIONKEY = N.N_REGIONKEY
AND S.S_NATIONKEY = N.N_NATIONKEY
GROUP BY R.R_NAME, N.N_NAME
HAVING COUNT(S.S_NATIONKEY) > 130;
Can someone explain to me why and how to fix this. Thankyou very much.

Put headers out of the loop and then, in the loop, concatenate all values you'd want to display. Use RPAD to nicely align values.
Apart from that, you misused FOR loop; you don't select INTO within its select statement, but use cursor variable. I don't have your tables so I used Scott's, for illustration:
SQL> CREATE OR REPLACE PROCEDURE numberOfSupplier (X INT:=0)
2 AS
3 BEGIN
4 dbms_output.put_line(rpad('R_NAME', 15, ' ') ||
5 rpad('N_NAME', 15, ' ') ||
6 'COUNT(S_NATIONKEY)'
7 );
8 dbms_output.put_line(rpad('-', 14, '-') || ' ' ||
9 rpad('-', 14, '-') || ' ' ||
10 rpad('-', 14, '-'));
11 FOR rec IN(
12 SELECT d.dname R_NAME,
13 e.ename N_NAME,
14 COUNT(*) snkeyc
15 FROM emp e join dept d on e.deptno = d.deptno
16 where e.deptno = 10
17 group by d.dname, e.ename
18 )
19 loop
20 dbms_output.put_line(rpad(rec.r_name, 15, ' ') ||
21 rpad(rec.n_name, 15, ' ') ||
22 rec.sNKeyC
23 );
24 END LOOP;
25 END;
26 /
Procedure created.
Testing:
SQL> set serveroutput on
SQL> exec numberofsupplier;
R_NAME N_NAME COUNT(S_NATIONKEY)
-------------- -------------- --------------
ACCOUNTING KING 1
ACCOUNTING CLARK 1
ACCOUNTING MILLER 1
PL/SQL procedure successfully completed.
SQL>

Changing your code slightly and using cursors instead must do your job.
create or replace PROCEDURE numberOfSupplier (X INT:=0)
AS
CURSOR rec IS SELECT R.R_NAME, N.N_NAME, COUNT(S.S_NATIONKEY) counter
FROM REGION R, NATION N, SUPPLIER S
WHERE R.R_REGIONKEY = N.N_REGIONKEY
AND S.S_NATIONKEY = N.N_NATIONKEY
GROUP BY R.R_NAME, N.N_NAME
HAVING COUNT(S.S_NATIONKEY) > X; -- cursor to collect all the Objects
BEGIN
dbms_output.put_line('R_NAME'||CHR(9)||'N_NAME'||CHR(9)||'COUNT(S_NATIONKEY)');
dbms_output.put_line('--------------------------------------------------');
FOR rec_obj IN rec LOOP
dbms_output.put_line(rec_obj.R_NAME||CHR(9)||CHR(9)||rec_obj.N_NAME||CHR(9)||CHR(9)||rec_obj.counter);
END LOOP;
EXCEPTION -- exception handlers begin
WHEN no_data_found THEN --catches exception when No Data Found
dbms_output.put_line('No Data Found');
WHEN TOO_MANY_ROWS THEN -- More than 1 row seleced
dbms_output.put_line('More than 1 row seleced');
WHEN OTHERS THEN -- handles all other errors
ROLLBACK;
dbms_output.put_line('I AM HERE!!!'
|| sqlcode
|| ' '
|| sqlerrm);
END;
/

Related

pl/sql ,oracle add heading in cursor

how I can add heading for table shown at top , in my code like this pic:
enter image description here
declare
E_Name employ.name%type;
E_Salary employ.salary%type;
CURSOR c_employees is
SELECT name , salary from employ order by salary desc;
BEGIN
OPEN c_employees;
LOOP
FETCH c_employees into E_Name,E_Salary ;
EXIT WHEN c_employees%notfound;
dbms_output.put_line( rpad(E_Name, 20, '.') || ' ' || rpad('$', (E_Salary/100), '$')||' '||E_Salary);
END LOOP;
CLOSE c_employees;
END;
/
Include two more DBMS_OUTPUT.PUT_LINEs which will display that header (lines #2 and 3).
For example (using my tables as I don't have yours):
SQL> begin
2 dbms_output.put_line('Emloyee name Salary');
3 dbms_output.put_line('------------ ------');
4
5 for cur_r in (select ename, sal from emp where deptno = 10) loop
6 dbms_output.put_line(rpad(cur_r.ename, 12, ' ') ||' '||
7 to_char(cur_r.sal, '99990'));
8 end loop;
9 end;
10 /
Emloyee name Salary
------------ ------
CLARK 2450
KING 5000
MILLER 1300
PL/SQL procedure successfully completed.
SQL>

WITH clause with FUNCTION AND PROCEDURE, where is the mistake?

I have table 'Studies' (with columns: student_id, name, surname, course_name, mark) and I have a task to write a PL/SQL program, where will be WITH word + FUNCTION word + PROCEDURE word.I decided to make such a program: the function will calculate the average mark for some course (input parameter) and the procedure will display information about students whose mark in this course is higher than the average. I managed to create a function that returns the average score,
create or replace FUNCTION average_mark(co_name IN VARCHAR2) RETURN REAL IS
iter NUMBER := 0;
aver NUMBER := 0;
CURSOR c1
IS
SELECT mark
FROM studies
WHERE course_name = co_name;
BEGIN
FOR student IN c1
LOOP
iter := iter + 1;
aver := aver + student.mark;
END LOOP;
RETURN ROUND((aver/iter),2);
END above_average_mark;
and procedure which displays information about a student whose mark in the course is more than a certain one, how now to connect the procedure and the function and the WITH word?
CREATE OR REPLACE PROCEDURE above(co_name IN VARCHAR2) IS
CURSOR c2
IS
SELECT *
FROM studies
WHERE course_name = co_name;
BEGIN
FOR student IN c2
LOOP
IF (student.mark > 4) THEN
DBMS_OUTPUT.PUT_LINE('name: ' || student.student_name || ', mark: ' || student.mark);
END IF;
END LOOP;
END;
i need something like this:
WITH
PROCEDURE
FUNCTION
I don't have your tables to illustrate it, so I'll use Scott's sample schema to calculate average salaries for departments.
As you said that you need something like WITH PROCEDURE FUNCTION, the only thing you have to do is to follow syntax.
Therefore, here you are: with factoring clause in this example contains a procedure which displays department name and average salary; function calls the procedure (and passes department number and average salary it calculated). Also, as any other function it actually returns a value.
SQL> set serveroutput on;
SQL> with
2 procedure p_deptno (par_deptno in dept.deptno%type,
3 par_avgsal in number)
4 is
5 l_dname dept.dname%type;
6 begin
7 select dname into l_dname
8 from dept
9 where deptno = par_deptno;
10 dbms_output.put_line('Average salary for department ' || l_dname ||
11 ' = ' || par_avgsal);
12 end p_deptno;
13
14 function f_avgsal (par_deptno in dept.deptno%type)
15 return number
16 is
17 l_avgsal number;
18 begin
19 select round(avg(e.sal)) into l_avgsal
20 from emp e
21 where e.deptno = par_deptno;
22
23 p_deptno (par_deptno, l_avgsal);
24
25 return l_avgsal;
26 end f_avgsal;
27 select f_avgsal (a.deptno) avg_sal
28 from dept a;
29 /
Result:
AVG_SAL
----------
2917
2175
1567
Average salary for department ACCOUNTING = 2917
Average salary for department RESEARCH = 2175
Average salary for department SALES = 1567
Average salary for department OPERATIONS =
SQL>
Now, adjust it to your tables & data.

How to use a dynamic variable created using INTO within a new query using IN clause under where condition in PL/SQL

DECLARE
l_filter varchar2(100);
BEGIN
SELECT filter INTO l_filter
FROM dashboard
WHERE id=TARGET_ID_1;
I get l_filter as ('016','018','011','014') from this. Now i want to use this l_filter values within IN clause in the query below dynamically.
select a,b from grid
where filter_value in l_filter;
How can I do this in PL/SQL?
You could use regular expressions:
select g.a, g.b
from grid g
where exists (select 1
from dashboard d.
where d.id = TARGET_ID_1 and
regexp_like(d.filter_value, '^(' || replace(d.filter, '''', ''), ',', '|') || ')$')
);
This is not particularly efficient, but it does not require any PL/SQL or dynamic SQL.
That said, you might want to store your "filters" as rows in a table so you can just join things together, dispensing with the complication.
Use LIKE:
DECLARE
l_filter varchar2(100);
cur SYS_REFCURSOR;
a GRID.A%TYPE;
b GRID.B%TYPE;
BEGIN
SELECT filter
INTO l_filter
FROM dashboard
WHERE id = TARGET_ID_1;
OPEN cur FOR
SELECT a,b
FROM grid
WHERE ','||SUBSTR(l_filter,2,LENGTH(l_filter)-2)||',' LIKE '%,'''||filter_value||''',%';
LOOP
FETCH cur INTO a,b;
EXIT WHEN cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(a || ', ' || b);
END LOOP;
CLOSE cur;
END;
/
db<>fiddle here
Here's how I understood the question.
Sample data first:
SQL> select * from dashboard;
ID FILTER
---------- -----------------------
1 '016','018','011','014'
2 '111', '222'
SQL> select * from grid;
A B FIL
---------- ---------- ---
100 200 016
101 201 011
200 400 xxx
SQL>
Function that returns refcursor; the key (in this example) is to split filter values into rows so that you could use them as a subquery (lines #15 - 18):
SQL> create or replace function f_get (target_id_1 in dashboard.id%type)
2 return sys_refcursor
3 is
4 l_filter varchar2(100);
5 rc sys_refcursor;
6 begin
7 select filter
8 into l_filter
9 from dashboard
10 where id = target_id_1;
11
12 open rc for
13 select g.a, g.b
14 from grid g
15 where g.filter_value in (select regexp_substr(replace(l_filter, chr(39), ''), '[^,]+', 1, level)
16 from dual
17 connect by level <= regexp_count(l_filter, ',') + 1
18 );
19
20 return rc;
21 end;
22 /
Function created.
Testing:
SQL> select f_get(1) from dual;
F_GET(1)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
A B
---------- ----------
100 200
101 201
SQL>

How to print first 5 rows of all the tables in an oracle database (schema)?

I want to open a database (schema) which holds about 100 tables,
Once it's opened I would like to print the top 5 rows of all the tables along with their column names.
My first step was to try and get the schema and table names in the database.
so this is what I used for that.
SELECT owner, table_name
FROM all_tables
The expected result will contain the table name, column names and top 5 rows of the table.
I am using Oracle SQL Developer ( Oracle database)
You can try this if you're running Oracle 12c and above.
SET SERVEROUTPUT ON
SET FEEDBACK OFF
DECLARE
x SYS_REFCURSOR;
BEGIN
FOR tabs IN ( SELECT rownum as rn,owner,table_name
FROM all_tables where owner='HR'
and ROWNUM < 10
) LOOP
OPEN x FOR 'SELECT '''|| tabs.owner||'.'|| tabs.table_name||'''
as "table" FROM DUAL';
dbms_sql.return_result(x);
OPEN x FOR 'select * from '
|| tabs.owner
||'.'
|| tabs.table_name
|| ' FETCH FIRST 5 ROWS ONLY ';
dbms_sql.return_result(x);
END LOOP;
END;
/
Output
table
----------
HR.REGIONS
REGION_ID REGION_NAME
---------- -------------------------
1 Europe
2 Americas
3 Asia
4 Middle East and Africa
table
------------
HR.LOCATIONS
LOCATION_ID STREET_ADDRESS POSTAL_CODE CITY STATE_PROVINCE CO
----------- ---------------------------------------- ------------ ------------------------------ ------------------------- --
1000 1297 Via Cola di Rie 00989 Roma IT
1100 93091 Calle della Testa 10934 Venice IT
1200 2017 Shinjuku-ku 1689 Tokyo Tokyo Prefecture JP
1300 9450 Kamiya-cho 6823 Hiroshima JP
1400 2014 Jabberwocky Rd 26192 Southlake Texas US
table
--------------
HR.DEPARTMENTS
DEPARTMENT_ID DEPARTMENT_NAME MANAGER_ID LOCATION_ID
------------- ------------------------------ ---------- -----------
10 Administration 200 1700
20 Marketing 201 1800
30 Purchasing 114 1700
40 Human Resources 203 2400
50 Shipping 121 1500
Normally we would use dynamic SQL for this sort of thing. But it's messy to handle the output of 100 different tables in PL/SQL. So, you need to generate a script.
Step 1: Run this in SQL Developer.
select 'prompt ' || owner ||'.' || table_name ||chr(10)
||'select * from ' || owner ||'.' || table_name
|| ' where rownum <=5;'
from all_tables
order by owner, table_name;
Step 2: Cut'n'paste the output into a SQL Worksheet. Unless you're luckier than I am you'll need to edit the annoying double quotes at the start and end of each line (a SQL Developer feature caused by the line break, I think).
Step 3: Run the (edited) output as a script and the Script Output pane will give you the results you want.
Is it possible for me to feed the schema(database) name
Add where owner = '&schema_name' after from all_tables. Or just use USER_TABLES if you're interested in the schema to which you're connected.
Following is the code which you are looking for. I have used USER_TAB_COLUMNS view so that only tables owned by your schema in which you are executing the code is considered.
Feel free to format the output according to your need.
SET SERVEROUT ON
DECLARE
LV_QUERY VARCHAR2(32767);
LV_RESULT VARCHAR2(32767);
BEGIN
DBMS_OUTPUT.PUT_LINE('---------------------------------------');
DBMS_OUTPUT.PUT_LINE('---------------------------------------');
FOR I IN (
SELECT
C.TABLE_NAME,
LISTAGG(C.COLUMN_NAME, ' || ''****'' || ') WITHIN GROUP(
ORDER BY
C.COLUMN_ID
) COLS
FROM
USER_TAB_COLUMNS C
GROUP BY
C.TABLE_NAME
ORDER BY
1
) LOOP
LV_QUERY := 'SELECT '
|| q'#RTRIM(XMLAGG(XMLELEMENT(E,VALS,CHR(10)).EXTRACT('//text()') ORDER BY 1).GetClobVal(),CHR(10)) FROM#'
|| '(SELECT '
|| I.COLS
|| ' AS VALS FROM '
|| I.TABLE_NAME
|| ' FETCH FIRST 5 ROWS ONLY)';
BEGIN
-- DBMS_OUTPUT.PUT_LINE(lv_query);
EXECUTE IMMEDIATE LV_QUERY
INTO LV_RESULT;
DBMS_OUTPUT.PUT_LINE(CHR(10));
DBMS_OUTPUT.PUT_LINE('TABLE NAME : ' || I.TABLE_NAME);
DBMS_OUTPUT.PUT_LINE('COLUMN NAMES : '
|| REPLACE(REPLACE(REPLACE(I.COLS, '*', ''), '||', ''), '''', '**'));
DBMS_OUTPUT.PUT_LINE(LV_RESULT);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('NO DATA IN TABLE : ' || I.TABLE_NAME);
END;
DBMS_OUTPUT.PUT_LINE(CHR(10));
DBMS_OUTPUT.PUT_LINE('---------------------------------------');
DBMS_OUTPUT.PUT_LINE('---------------------------------------');
END LOOP;
END;
Hope this is what you are looking for.

Display result from loop tables (oracle, pl/sql)

I'm try to loop some tables and run select as below:
set serveroutput on
declare
type tables_names is table of varchar2(30);
type selectTable is table of varchar2(30);
tName tables_names;
sTableName selectTable;
begin;
tName := tables_names('PERIOD','SETTING','RAP','LOG');
sTableName := selectTable('m_table1','m_table2','m_table3','m_table4','m_table5');
for i in 1..tName.count loop
for j in 1..sTableName.count loop
select col10, count(*) from user.sTableName(j)
where table_name = tName(i) group by col10;
end loop;
end loop;
end;
I got error:PL/SQL: ORA-00933.
Can you please tell me how can I correctly run PL/SQL procedure to have displayed result from my select?
UPDATE: looking result
Normally, to get this I need to run below select's:
select column_name,
count(*) as countColumn
from user.m_table1 where table_name = 'PERIOD' group by column_name;
select column_name,
count(*) as countColumn
from user.m_table2 where table_name = 'PERIOD' group by column_name;
Oracle complains (ORA-00933) that command isn't properly ended. That's probably because of a semi-colon behind the BEGIN; also, you lack the INTO clause.
I'm not sure what PERIOD, SETTING, ... are opposed to m_table1, m_table2, ... Which ones of those are table names? What are those other values, then?
Anyway: here's an example which shows how to do something like that - counting rows from tables. Try to adjust it to your situation, or - possibly - add some more info so that we'd know what you are doing.
SQL> set serveroutput on
SQL> declare
2 tname sys.odcivarchar2list := sys.odcivarchar2list();
3 l_cnt number;
4 l_str varchar2(200);
5 begin
6 tname := sys.odcivarchar2list('EMP', 'DEPT');
7
8 for i in 1 .. tname.count loop
9 l_str := 'select count(*) from ' || tname(i);
10 execute immediate l_str into l_cnt;
11 dbms_output.put_line(tname(i) ||': '|| l_cnt);
12 end loop;
13 end;
14 /
EMP: 14
DEPT: 4
PL/SQL procedure successfully completed.
SQL>
[EDIT: added GROUP BY option]
Here you go; as EMP and DEPT share the DEPTNO column, I chose it for a GROUP BY column.
SQL> declare
2 tname sys.odcivarchar2list := sys.odcivarchar2list();
3 type t_job is record (deptno varchar2(20), cnt number);
4 type t_tjob is table of t_job;
5 l_tjob t_tjob := t_tjob();
6 l_str varchar2(200);
7 begin
8 tname := sys.odcivarchar2list('EMP', 'DEPT');
9
10 for i in 1 .. tname.count loop
11 l_str := 'select deptno, count(*) from ' || tname(i) ||' group by deptno';
12 execute immediate l_str bulk collect into l_tjob;
13
14 for j in l_tjob.first .. l_tjob.last loop
15 dbms_output.put_Line('Table ' || tname(i) || ': Deptno ' || l_tjob(j).deptno||
16 ': number of rows = '|| l_tjob(j).cnt);
17 end loop;
18
19 end loop;
20 end;
21 /
Table EMP: Deptno 30: number of rows = 6
Table EMP: Deptno 20: number of rows = 5
Table EMP: Deptno 10: number of rows = 3
Table DEPT: Deptno 10: number of rows = 1
Table DEPT: Deptno 20: number of rows = 1
Table DEPT: Deptno 30: number of rows = 1
Table DEPT: Deptno 40: number of rows = 1
PL/SQL procedure successfully completed.
SQL>
You are probably looking for something like this. Note that you can't run a simple select statement inside a PL/SQL without INTO clause. use a refcursor and DBMS_SQL.RETURN_RESULT
DECLARE
TYPE tables_names IS TABLE OF VARCHAR2 (30);
TYPE selectTable IS TABLE OF VARCHAR2 (30);
tName tables_names;
sTableName selectTable;
rc SYS_REFCURSOR;
BEGIN
tName :=
tables_names ('PERIOD',
'SETTING',
'RAP',
'LOG');
sTableName :=
selectTable ('m_table1',
'm_table2',
'm_table3',
'm_table4',
'm_table5');
FOR i IN 1 .. tName.COUNT
LOOP
FOR j IN 1 .. sTableName.COUNT
LOOP
OPEN rc FOR
'select col10, count(*) from '||USER||'.'
|| sTableName (j)
|| ' where table_name = '''
|| tName (i)
|| ''' group by col10';
DBMS_SQL.RETURN_RESULT (rc);
END LOOP;
END LOOP;
END;
/