I have a table and if data exists I have to display data with dbms_output. If the table is empty I have to write a message "table is empty'.
I want to use cursors and I don't know how the condition should look like when the table is empty.
That works fine:
declare
cursor cursor_name is select nr, name from branch;
begin
for i in cursor_name
loop
dbms_output.put_line('Number: ' || to_char(i.nr) || ' Name: ' || i.name);
end loop;
end;
/
Just use a variable to store a flag which you can set if rows are found:
DECLARE
CURSOR cursor_name IS
SELECT nr, name FROM branch;
no_rows BOOLEAN := TRUE;
BEGIN
FOR i IN cursor_name LOOP
DBMS_OUTPUT.PUT_LINE('Number: ' || to_char(i.nr) || ' Name: ' || i.name);
no_rows := FALSE;
END LOOP;
IF no_rows THEN
DBMS_OUTPUT.PUT_LINE('Table is empty');
END IF;
END;
/
Or:
DECLARE
CURSOR cursor_name IS
SELECT nr, name FROM branch;
cursor_row cursor_name%ROWTYPE;
BEGIN
OPEN cursor_name;
FETCH cursor_name INTO cursor_row;
IF cursor_name%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE('Table is empty');
END IF;
LOOP
EXIT WHEN cursor_name%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(
'Number: ' || to_char(cursor_row.nr) || ' Name: ' || cursor_row.name
);
FETCH cursor_name INTO cursor_row;
END LOOP;
CLOSE cursor_name;
END;
/
Which, for the sample data:
CREATE TABLE branch (nr, name) AS
SELECT 1, 'Aspen' FROM DUAL UNION ALL
SELECT 2, 'Beech' FROM DUAL UNION ALL
SELECT 3, 'Cedar' FROM DUAL;
Both output:
Number: 1 Name: Aspen
Number: 2 Name: Beech
Number: 3 Name: Cedar
and after:
DELETE FROM branch;
Both output:
Table is empty
db<>fiddle here
You can use such a simple code block
SQL> SET serveroutput ON
SQL> DECLARE
v_mes VARCHAR2(50);
BEGIN
SELECT DECODE(COUNT(*),0,'Table is empty')
INTO v_mes
FROM t;
DBMS_OUTPUT.PUT_LINE(v_mes);
END;
/
if your aim is displaying only for the case that the table has no data
If you want to use a cursor, then this might be one option:
SQL> set serveroutput on;
Creating an empty table:
SQL> create table branch (nr number, name varchar2(20));
Table created.
Anonymous PL/SQL block: declare a cursor and its variable, fetch, check whether anything has been found:
SQL> declare
2 cursor c1 is select nr, name from branch;
3 c1r c1%rowtype;
4 begin
5 open c1;
6 fetch c1 into c1r;
7 if c1%notfound then
8 dbms_output.put_line('Table is empty');
9 end if;
10 close c1;
11 end;
12 /
Table is empty --> as expected
PL/SQL procedure successfully completed.
SQL>
Integrated solution using a database view - use with cursor and dbms_outputas you like:
create view v_branch as
select nr, name from branch
union all
select null, 'Table is empty' name from dual
where 0 = (select count(*) from branch);
select * from v_branch;
NR NAME
---------- --------------
1 x
delete from branch;
select * from v_branch;
NR NAME
---------- --------------
Table is empty
You will not want to use this for a very large tables - but it works fine for tables where you intend to print every row with put_line.
Related
I have a stored procedure like below where multiple employee IDs will be passed as comma-separated value (multiple IDs). It is throwing error as "ORA-01722: invalid number". I know it's because of passing varchar2 variable for the numeric ID column. But is there any way we can achieve this simply?
create or replace PROCEDURE Fetch_Emp_Name(Emp_id in varchar2)
IS
BEGIN
select Name from EMP where id in (emp_id);
END;
You can use dynamic sql.
create or replace PROCEDURE Fetch_Emp_Name(emp_id in varchar2) IS
v_result varchar2;
begin
execute immediate
'select Name from EMP where id in (' || 'emp_id' || ')'
into
v_result;
end;
Also you can use package dbms_sql for dynamic sql.
Update
Another approach. I think may be better.
create or replace PROCEDURE Fetch_Emp_Name(emp_id in varchar2) IS
v_result varchar2;
begin
select
Name
from
EMP
where
id in
(
select
to_number(regexp_substr(emp_id, '[^,]+', 1, level))
from
dual
connect by regexp_substr(emp_id, '[^,]+', 1, level) is not null
);
exception
when no_data_found then
-- error1;
when too_many_rows then
-- error2;
end;
Sorry for before, I did not get the question in the right way. If you get a lot of IDs as different parameters, you could retrieve the list of names as an string split by comma as well. I put this code where I handled by regexp_substr the name of different emp_ids you might enter in the input parameter.
Example ( I am assuming that the IDs are split by comma )
create or replace PROCEDURE Fetch_Emp_Name(p_empid in varchar2) IS
v_result varchar2(4000);
v_append emp.name%type;
v_emp emp.emp_id%type;
counter pls_integer;
i pls_integer;
begin
-- loop over the ids
counter := REGEXP_COUNT(p_empid ,'[,]') ;
--dbms_output.put_line(p_empid);
if counter > 0
then
i := 0;
for r in ( SELECT to_number(regexp_substr(p_empid,'[^,]+',1,level)) as mycol FROM dual CONNECT BY LEVEL <= REGEXP_COUNT(p_empid ,'[,]')+1 )
loop
--dbms_output.put_line(r.mycol);
v_emp := r.mycol ;
select name into v_append from emp where emp_id = v_emp;
if i < 1
then
v_result := v_append ;
else
v_result := v_result ||','|| v_append ;
end if;
i := i + 1;
end loop;
else
v_emp := to_number(p_empid);
select name into v_result from emp where emp_id = v_emp;
end if;
dbms_output.put_line(v_result);
exception
when no_data_found
then
raise_application_error(-20001,'Not Employee found for '||v_emp||' ');
when too_many_rows
then
raise_application_error(-20002,'Too many employees for id '||v_emp||' ');
end;
Test
SQL> create table emp ( emp_id number, name varchar2(2) ) ;
Table created.
SQL> insert into emp values ( 1 , 'AA' );
1 row created.
SQL> insert into emp values ( 2 , 'BB' ) ;
1 row created.
SQL> commit;
SQL> insert into emp values ( 3 , 'CC' ) ;
1 row created.
SQL> select * from emp ;
EMP_ID NA
---------- --
1 AA
2 BB
3 CC
SQL> exec Fetch_Emp_Name('1') ;
AA
PL/SQL procedure successfully completed.
SQL> exec Fetch_Emp_Name('1,2,3') ;
AA,BB,CC
PL/SQL procedure successfully completed.
SQL>
Is there any way to assign a select query to local variable in PL/SQL other than select into statement?. Because select into throwing null value exception if the select query returns null value. Thanks
It would be helpful to post your code, but here is an example that should show the behavior you need. Assume there is a table called courses_tbl:
declare
cnumber number := NULL;
CURSOR c1
IS
SELECT course_number
FROM courses_tbl
WHERE course_name = 'XYZ';
BEGIN
open c1;
fetch c1 into cnumber;
if c1%notfound then
-- Do something here if you care about not found.
cnumber := 999; -- whatever
end if;
you can read about cursor attributes here
Seems that you need to use the exception handling as follows:
... -- Your procedure other code
BEGIN
SELECT <COLUMN_NAME> INTO <YOUR_VARIABLE>
FROM .....
WHERE ....
EXCEPTION WHEN NO DATA FOUND THEN
<YOUR_VARIABLE> := NULL;
END;
... -- Your procedure other code
You can use EXECUTE IMMEDIATE...INTO...:
DECLARE
nCnumber NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT CNUMBER FROM COURSES_TBL WHERE CNUMBER = 1'
INTO nCnumber;
DBMS_OUTPUT.PUT_LINE('SELECT #1 : nCnumber = ' || nCnumber);
nCnumber := NULL;
BEGIN
EXECUTE IMMEDIATE 'SELECT CNUMBER FROM COURSES_TBL WHERE CNUMBER = 100'
INTO nCnumber;
DBMS_OUTPUT.PUT_LINE('SELECT #2 : nCnumber = ' || nCnumber);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('SELECT #2 : NO DATA FOUND');
END;
END;
db<>fiddle here
You've seen how to do it using a cursor or exception handling section (which is - in my opinion - the right way to do it). However, as we're discussing, here's yet another option - an aggregate function. It won't return NO_DATA_FOUND but NULL.
This is what you have now:
SQL> declare
2 l_job emp.job%type;
3 begin
4 select job
5 into l_job
6 from emp
7 where ename = 'Does not exist';
8 end;
9 /
declare
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at line 4
This is what you might do:
SQL> declare
2 l_job emp.job%type;
3 begin
4 select max(job)
5 into l_job
6 from emp
7 where ename = 'Does not exist';
8 end;
9 /
PL/SQL procedure successfully completed.
SQL>
I would like to know where is the mistake in this procedure
set serveroutput on
create or replace procedure insert_table(column_name in varchar2, dat in varchar2)
as
table_name varchar2(100):= column_name || '_' || dat;
sql_create varchar2(100) := 'create table '||table_name||'(FIRSTNAME varchar2(100))';
sql_str varchar2(100);
CURSOR c_emp is
select FIRSTNAME
from employees
where column_name = dat;
begin
execute immediate sql_create;
for r_reg in c_emp
loop
sql_str:='INSERT INTO '||table_name||'('''||r_reg.firstname||''')';
execute immediate sql_str;
end loop;
end;
/
execute insert_table('CITY','London');
Edit:
Ok i add the correction mentioned below in the syntax error, but how can I do so that the parameter of the column name can be taken at the cursor, because for now it is of type varchar and 'CITY' should be a row name.
You will need to use the dynamic query in the cursor as follows. Also, you missed VALUES keyword in the INSERT statement which I have added (Please see inline comment in the codes)
Oracle sample data creation:
SQL> CREATE TABLE "EMPLOYEES" (
2 "ID" NUMBER,
3 "FIRSTNAME" VARCHAR2(100 BYTE),
4 "CITY" VARCHAR2(100 BYTE),
5 PRIMARY KEY ( "ID" ) USING INDEX ENABLE
6 );
Table created.
SQL>
Now, Let's create your procedure
SQL> CREATE OR REPLACE PROCEDURE INSERT_TABLE (
2 COLUMN_NAME IN VARCHAR2,
3 DAT IN VARCHAR2
4 ) AS
5
6 TABLE_NAME VARCHAR2(100) := COLUMN_NAME || '_' || DAT;
7 SQL_CREATE VARCHAR2(100) := 'create table ' || TABLE_NAME || '(FIRSTNAME varchar2(100))';
8 C_EMP SYS_REFCURSOR; -- declaration of cursor
9 LV_FNAME EMPLOYEES.FIRSTNAME%TYPE; -- to store the each value from cursor
10 BEGIN
11 EXECUTE IMMEDIATE SQL_CREATE;
12 OPEN C_EMP FOR 'SELECT FIRSTNAME
13 FROM EMPLOYEES
14 WHERE ' || COLUMN_NAME || ' = ''' || DAT || '''';
15 -- above statement used dynamic query in cursor
16 LOOP
17 FETCH C_EMP INTO LV_FNAME;
18 EXIT WHEN C_EMP%NOTFOUND;
19 EXECUTE IMMEDIATE 'INSERT INTO ' || TABLE_NAME || ' VALUES (''' || LV_FNAME || ''')'; -- added VALUES keyword in INSERT statement.
20 END LOOP;
21
22 COMMIT;
23 END INSERT_TABLE;
24 /
Let's execute it and see the result now.
SQL>
SQL> EXECUTE INSERT_TABLE('CITY', 'London');
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL> SELECT * FROM CITY_LONDON;
FIRSTNAME
--------------------------------------------------------------------------------
TEJASH
SQL>
Ohh Yes, It created the desired table and also data is populated correctly.
Cheers!!
You are missing quotes. This
sql_str:='INSERT INTO '||table_name||'('||r_reg.firstname||');';
should be
sql_str:='INSERT INTO '||table_name||'('''||r_reg.firstname||''');';
This will turn INSERT INTO CITY_London (Peter); into INSERT INTO CITY_London ('Peter');.
And according to the docs (e.g. https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/dynamic.htm):
When constructing a single SQL statement in a dynamic string, do not include a semicolon (;) at the end inside the quotation mark.
So the two SQL strings for EXECUTE IMMEDIATE should be:
sql_create varchar2(100) := 'create table ' || table_name || '(FIRSTNAME varchar2(100))';
and
sql_str := 'INSERT INTO ' || table_name || '(''' || r_reg.firstname || ''')';
It is the answer for your last question. You can pass parameters to the cursor like there:
declare
CURSOR c_emp(p_param varchar2) is
select p_param val
from dual;
begin
for r_reg in c_emp('b')
loop
dbms_output.put_line(r_reg.val);
end loop;
end;
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;
Lets say I have a table called student:
When I select from that table the result is:
SQL> select * from student;
NAME ROLL_NO
-------------------- --------------------
gokul 3
gokul1 34
Instead can we get the output like this? without using PIVOT keyword
SQL> select * from student;
NAME gokul gokul1
ROLL_NO 3 34
The simple answer is: no, you cannot.
In Oracle, it is possible to do a dynamic pivot on an unknown number of columns but it is not something that you can do using a simple SELECT * FROM student. You will need to use PL/SQL to build some dynamic SQL:
VARIABLE cur REFCURSOR;
DECLARE
p_sql CLOB;
BEGIN
SELECT 'SELECT ''ROLL_NO'' AS "NAME", '
|| LISTAGG(
'MAX( CASE name WHEN '''
|| name
|| ''' THEN roll_no END ) AS "'
|| name || '"',
','
) WITHIN GROUP ( ORDER BY ROWNUM )
|| ' FROM students'
INTO p_sql
FROM students;
OPEN :cur FOR p_sql;
END;
/
PRINT cur;
Output:
NAME gokul gokul1
------- ----------- -----------
ROLL_NO 3 34
Edit
The above code will work where the LISTAGG output is under 4000 bytes but if you want to go over that then you need a slightly different method:
VARIABLE cur REFCURSOR;
DECLARE
TYPE name_t IS TABLE OF students.name%type;
p_sql CLOB;
names name_t;
BEGIN
SELECT DISTINCT name
BULK COLLECT INTO names
FROM students;
p_sql := EMPTY_CLOB() || 'SELECT ''ROLL_NO'' AS "NAME"';
FOR i IN 1 .. names.COUNT LOOP
p_sql := p_sql || ', MAX( CASE name WHEN '''
|| names(i)
|| ''' THEN roll_no END ) AS "'
|| names(i)
|| '"';
END LOOP;
p_sql := p_sql || ' FROM students';
OPEN :cur FOR p_sql;
END;
/
PRINT cur;