Oracle stored procedure with case when in the where cause - oracle

The following stored procedure complied with errors. It seems the errors are in the case statement in the where cause. How do I fix it?
create or replace PROCEDURE APPEND_HIST_TBLS_PROC AS
CurTerm varchar2(4) := '1222';
PS_ACAD_PROG_HISTORY varchar2(35) := 'PS_ACAD_PROG_HISTORY_' || CurTerm;
Begin
execute immediate 'insert into ' || PS_ACAD_PROG_HISTORY
|| '(select sysdate as date_created,
EFFDT,
ADMIT_TERM,
EXP_GRAD_TERM,
CAMPUS
from ERP_ACAD
where CASE WHEN SUBSTR(ADMIT_TERM,4,1)= '6' THEN SUBSTR(ADMIT_TERM,1,3) || '9' ELSE ADMIT_TERM END = '||CurTerm ||'
)';
END APPEND_HIST_TBLS_PROC;

Dynamic SQL is difficult to maintain and debug. The fact that procedure compiled successfully tells nothing about the dynamic statement itself - it just says that there were no errors in the procedure "as is".
It is a good habit to compose the statement and store it into a local variable and then display its contents; once you verify it is OK, then execute it.
Also, as you have to escape single quotes (sometimes it becomes nasty, with several consecutive single quotes which do what you need), use the q-quoting mechanism which lets you write "normal" statements (single quotes really are single, then).
Something like this:
SQL> CREATE OR REPLACE PROCEDURE append_hist_tbls_proc
2 AS
3 curterm VARCHAR2 (4) := '1222';
4 ps_acad_prog_history VARCHAR2 (35) := 'PS_ACAD_PROG_HISTORY_' || curterm;
5 l_str VARCHAR2 (4000);
6 BEGIN
7 -- Compose the INSERT statement into a VARCHAR2 local variable so that you'd be able
8 -- to check whether you did it right or not.
9 -- Use the q-quoting mechanism as it helps with consecutive single quotes issues
10 l_str :=
11 'INSERT INTO '
12 || ps_acad_prog_history
13 || q'[ SELECT sysdate as date_created,
14 effdt,
15 admit_term,
16 exp_grad_term,
17 campus
18 FROM erp_acad
19 WHERE CASE WHEN SUBSTR(admit_term, 4, 1) = '6' THEN
20 SUBSTR(admit_term, 1, 3) || '9'
21 ELSE ADMIT_TERM
22 END = ]'
23 || curterm;
24
25 -- FIRST check the command you're about to execute
26 DBMS_OUTPUT.put_line (l_str);
27
28 -- When you verified that it is correct, then comment DBMS_OUTPUT.PUT_LINE call
29 -- and uncomment EXECUTE IMMEDIATE
30 -- EXECUTE IMMEDIATE l_str;
31 END append_hist_tbls_proc;
32 /
Procedure created.
Let's try it:
SQL> SET SERVEROUTPUT ON
SQL> EXEC append_hist_tbls_proc;
INSERT INTO PS_ACAD_PROG_HISTORY_1222 SELECT sysdate as date_created,
effdt,
admit_term,
exp_grad_term,
campus
FROM erp_acad
WHERE CASE WHEN SUBSTR(admit_term, 4, 1) = '6'
THEN
SUBSTR(admit_term, 1, 3) || '9'
ELSE
ADMIT_TERM
END = 1222
PL/SQL procedure successfully completed.
SQL>
Output looks ugly (that's the price of having it pretty in the procedure), but - if you format it - it looks like this:
INSERT INTO PS_ACAD_PROG_HISTORY_1222
SELECT SYSDATE AS date_created,
effdt,
admit_term,
exp_grad_term,
campus
FROM erp_acad
WHERE CASE
WHEN SUBSTR (admit_term, 4, 1) = '6'
THEN
SUBSTR (admit_term, 1, 3) || '9'
ELSE
ADMIT_TERM
END = 1222
So, if you can really execute it (I can't, I don't have your tables), then uncomment execute immediate and use the procedure. Otherwise, fix the statement.

Seems like an issue with quoting. Have you tried this:
create or replace PROCEDURE APPEND_HIST_TBLS_PROC AS
CurTerm varchar2(4) := '1222';
PS_ACAD_PROG_HISTORY varchar2(35) := 'PS_ACAD_PROG_HISTORY_' || CurTerm;
Begin
execute immediate 'insert into ' || PS_ACAD_PROG_HISTORY
|| '(select sysdate as date_created,
EFFDT,
ADMIT_TERM,
EXP_GRAD_TERM,
CAMPUS
from ERP_ACAD
where CASE WHEN SUBSTR(ADMIT_TERM,4,1)= ''6''
THEN CONCAT(SUBSTR(ADMIT_TERM,1,3), ''9'') ELSE ADMIT_TERM END = '||CurTerm ||'
)';
END APPEND_HIST_TBLS_PROC;
Also used CONCAT instead of double pipe assuming you want to append 9 at the end of sub string of admin_term.
Really depends on your data and the logic you are trying to implement. This procedure should now compile but whether it achieves the desired result - depends on the logic you are trying to implement here.

Related

Procedure to create partition table on daily basis, truncate old partition & records

I have written 1 Stored Procedure where it should create new partition on daily basis, truncate partition & records older than 9 months. Below is the SP-
create or replace procedure sp_partn as
hm varchar2(255);
mm varchar2(50);
yr varchar2(50);
dd varchar2(10);
ym varchar2(50);
dt varchar2(50);
procedure tbl1_prtn as
begin
for i in (select partition_name, high_value from user_tab_partition where table_name='tbl1')
loop
hm := i.high_value;
mm := substr(hm,6,7);
yr := substr(hm,1,4);
dd := substr(hm,9,10);
ym := yr||mm||dd;
dt := to_date(ym,'yyyymmdd');
if sysdate >= add_months(dt, 9) then
execute immediate 'alter table tbl1 drop partition' || i.partition_name;
end if;
end loop;
end tbl1_prtn;
begin
tbl1_prtn;
end sp_partn;
/
When i execute above SP then neither partition create or neither drop partition of older than 9 months. (eg: tbl_20211116)
run
SELECT HIGH_VALUE FROM USER_TAB_PARTITIONS
because for a date partition, the HIGH_VALUE is normally something like "TO_DATE(' 2016-01-04 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'" so I'm not sure your substr commands will work. A better option might be:
hm := i.high_value;
execute immediate 'select '||hm||' from dual' into dt;
which will be immune to string position etc.
If you store dates as dates instead of strings it will make your life much easier and you can use INTERVAL PARTITIONs and this will solve your problem.
SQL> BEGIN
2 FOR cc IN (SELECT partition_name, high_value --
3 FROM user_tab_partitions
4 WHERE table_name = 'TEST_TABLE') LOOP
5 EXECUTE IMMEDIATE
6 'BEGIN
7 IF sysdate >= ADD_MONTHS(' || cc.high_value || ', 2) THEN
8 EXECUTE IMMEDIATE
9 ''ALTER TABLE TEST_TABLE DROP PARTITION '
10 || cc.partition_name || '
11 '';
12 END IF;
13 END;';
14 END LOOP;
15 END;
16 /

Run query dynamically in stored procedure in Oracle

Select all the tables of database where column match than pass table name to next query using loop. If column name and column values matches than return true and exist for loop using a stored procedure:
CREATE OR REPLACE PROCEDURE TEST
(
NAME IN VARCHAR2 ,
ID IN NUMBER,
RE OUT SYS_REFCURSOR
) AS
BEGIN
OPEN RE FOR SELECT A.TABLE_NAME FROM
user_tables A JOIN user_tab_columns C
ON C.TABLE_NAME = A.TABLE_NAME
WHERE C.COLUMN_NAME = NAME;
FOR RE IN LOOP
v_Sql := 'SELECT COUNT(*) FROM '|| LOOP.TABLE_NAME || 'WHERE COLUMN_NAME =
ID';
EXECUTE IMMEDIATE v_Sql
IF v_Sql%ROWCOUNT > 0 THEN
return true;
EXIT
END LOOP;
END TEST;
For more understanding the problem
//Get all the tables of database where campus_id is exist in any table of
database
Campus, Class, Section (3 tables found)
Apply forloop on the records
Select count(campus_id) as total from (table name using loop) where campus_id = 1(value
pass)
if(total > 0){
Exist for loop and return true
}
else{
Again iterate the loop to next value
}
What you described doesn't make much sense. If there are several tables that contain a column you're checking and you exit the loop as soon as you find the first one, what about the rest of them?
Here's what I'd do, see if it helps. I'll create a function (not a procedure) that returns a table. In order to do that, I'll create type(s) first:
SQL> create or replace type t_record as object (tn varchar2(30), cnt number);
2 /
Type created.
SQL> create or replace type t_table as table of t_record;
2 /
Type created.
SQL>
The function:
in a cursor FOR loop I'm selecting tables that contain that column
L_STR is used to compose the SELECT statement
DBMS_OUTPUT.PUT_LINE is used to display it first, so that I could visually check whether it is correctly set or not.
if it is, I'm running it with the EXECUTE IMMEDIATE
the result is stored into a table type and returned to the caller
SQL> create or replace function f_colname
2 (par_column_name in varchar2,
3 par_column_value in varchar2
4 )
5 return t_table
6 is
7 retval t_table := t_table();
8 l_str varchar2(200);
9 l_cnt number;
10 begin
11 for cur_r in (select table_name
12 from user_tab_columns
13 where column_name = par_column_name
14 )
15 loop
16 l_str := 'select count(*) from ' || cur_r.table_name ||
17 ' where ' || par_column_name || ' = ' ||
18 chr(39) || par_column_value || chr(39);
19 -- Display l_str first, to make sure that it is OK:
20 -- dbms_output.put_line(l_str);
21 execute immediate l_str into l_cnt;
22 retval.extend;
23 retval(retval.count) := t_record(cur_r.table_name, l_cnt);
24 end loop;
25 return retval;
26 end;
27 /
Function created.
Testing:
SQL> select * from table (f_colname('DEPTNO', '10'));
TN CNT
------------------------------ ----------
TEST_201812 1
DEPT 1
EMP 3
SQL> select * from table (f_colname('ENAME', 'KING'));
TN CNT
------------------------------ ----------
EMP 1
BONUS 1
SQL>
That won't work properly for some datatypes (such as DATE) and will have to be adjusted, if necessary.
[EDIT: after you edited the question]
OK then, that's even simpler. It should still be a function (that returns a Boolean, as you said that - in case that something's being found - you want to return TRUE). Code is pretty much similar to the previous function.
SQL> create or replace function f_colname
2 (par_column_name in varchar2,
3 par_column_value in varchar2
4 )
5 return boolean
6 is
7 l_str varchar2(200);
8 l_cnt number;
9 retval boolean := false;
10 begin
11 for cur_r in (select table_name
12 from user_tab_columns
13 where column_name = par_column_name
14 )
15 loop
16 l_str := 'select count(*) from ' || cur_r.table_name ||
17 ' where ' || par_column_name || ' = ' ||
18 chr(39) || par_column_value || chr(39);
19 -- Display l_str first, to make sure that it is OK:
20 -- dbms_output.put_line(l_str);
21 execute immediate l_str into l_cnt;
22 if l_cnt > 0 then
23 retval := true;
24 exit;
25 end if;
26 end loop;
27 return retval;
28 end;
29 /
Function created.
Testing: as you can't return Boolean at SQL layer, you have to use an anonymous PL/SQL block, as follows:
SQL> declare
2 l_ret boolean;
3 begin
4 if f_colname('DEPTNO', '15') then
5 dbms_output.put_line('It exists');
6 else
7 dbms_output.put_line('It does not exist');
8 end if;
9 end;
10 /
It does not exist
PL/SQL procedure successfully completed.
SQL>

Search data from any column of the table from Oracle

I want to implement one search logic here. What I want is,
A user enters any text in the search box and presses enter. Then what should happen is, it should search in the table on any column and it the record exist then it should display.
Currently what I tried is, it search from one of the column from the table. Below is the code,
PROCEDURE GET_SEARCH_DATA
(
P_INPUTTEXT IN NVARCHAR2,
P_RETURN OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN P_RETURN FOR
SELECT APP_MST_ID, APPLICATIONNAME, PROJECTNO, VSS_FOLDER_LOC FROM
APPLICATION_MASTER WHERE APPLICATIONNAME LIKE '%'|| P_INPUTTEXT || '%';
END;
So what I want is, it should search from every column of the table and display the result.
This is a rather rudimentary "solution" which checks all tables and their columns (from USER_TAB_COLUMNS) and checks which ones of them contain a search string; it displays a table, a column and number of occurrences.
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)
7 loop
8 l_str :=
9 'SELECT COUNT(*) FROM '
10 || cur_r.table_name
11 || ' WHERE '
12 || upper (cur_r.column_name)
13 || ' like (''%&search_string%'')';
14
15 execute immediate (l_str) into l_cnt;
16
17 if l_cnt > 0
18 then
19 dbms_output.put_line (
20 cur_r.table_name || '.' || cur_r.column_name || ': ' || l_cnt);
21 end if;
22 end loop;
23 end;
24 /
Enter value for search_string: MANAGE
EMP.JOB: 3
PL/SQL procedure successfully completed.
SQL> /
Enter value for search_string: ACCOU
DEPT.DNAME: 1
PL/SQL procedure successfully completed.
SQL>

pl/sql block code issue with multiple records return

can't make this work... have a multiple tables names which i need to pass into select. Each select will return multiple records. Resultset should be printed to the user on a screen .
SQL> set serveroutput on;
SQL> DECLARE
vs_statement VARCHAR2 (1000);
my_var1 VARCHAR2(100);
my_var2 VARCHAR2(100);
CURSOR c1 IS
SELECT table_name
FROM all_tables
WHERE table_name LIKE Upper('redit_1%');
BEGIN
FOR table_rec IN c1 LOOP
vs_statement :=
'select a.userinfo, a.userstatus into my_var1, my_var12 from '
|| table_rec.table_name
|| ' A, FILES b where A.objectid = B.id order by 1';
EXECUTE IMMEDIATE vs_statement INTO my_var1,
my_var2;
dbms_output.Put_line(my_var1
||' '
|| my_var2);
END LOOP;
END;
/
This line should look like the following (i.e. remove INTO clause):
vs_statement :=
'select a.userinfo, a.userstatus from '
|| table_rec.table_name
|| ' A, FILES b where A.objectid = B.id order by 1';
Also, make sure that this SELECT returns only 1 row, otherwise you'll end up with the TOO-MANY-ROWS error.
Other than that, I guess it should work.
[EDIT, regarding fear of TOO-MANY-ROWS]
Huh, I'd create a whole new PL/SQL BEGIN-END block, containing a loop which would do the job, displaying all rows returned by SELECT statement.
As I don't have your tables, I used HR's. Have a look:
SQL> DECLARE
2 vs_statement VARCHAR2 (1000);
3 my_var1 VARCHAR2(100);
4 my_var2 VARCHAR2(100);
5 CURSOR c1 IS
6 SELECT table_name
7 FROM all_tables
8 WHERE table_name LIKE Upper('%departments%');
9 BEGIN
10 FOR table_rec IN c1 LOOP
11 vs_statement := 'begin for cur_r in ( '
12 || Chr(10)
13 || 'select distinct a.department_id, a.department_name from '
14 || Chr(10)
15 || table_rec.table_name
16 ||
17 ' A, employees b where A.department_id = B.department_id order by 1) loop'
18 || Chr(10)
19 || ' dbms_output.put_line(cur_r.department_name); '
20 || Chr(10)
21 || ' end loop; end;';
22
23 EXECUTE IMMEDIATE vs_statement; -- into my_var1, my_var2;
24 END LOOP;
25 END;
26 /
Administration
Marketing
Purchasing
Human Resources
Shipping
IT
Public Relations
Sales
Executive
Finance
Accounting
PL/SQL procedure successfully completed.
SQL>
"Each select will return multiple records...Resultset should be printed to the user on a screen"
Open a ref cursor for each generated statement. Loop round that and display the values.
DECLARE
rc sys_refcursor;
vs_statement VARCHAR2 (1000);
my_var1 VARCHAR2(100);
my_var2 VARCHAR2(100);
CURSOR c1 IS
SELECT table_name
FROM all_tables
WHERE table_name LIKE Upper('redit_1%');
BEGIN
<< tab_loop >>
FOR table_rec IN c1 LOOP
vs_statement :=
'select a.userinfo, a.userstatus from '
|| table_rec.table_name
|| ' A, FILES b where A.objectid = B.id order by 1';
open rc for vs_statement;
dbms_output.put_line('records for '||table_rec.table_name);
<< rec_loop >>
loop
fetch rc into my_var1,my_var2;
exit when rc%notfound;
dbms_output.Put_line(my_var1
||' '
|| my_var2);
end loop rec_loop;
close rc;
END LOOP tab_loop;
END;
/

Generating Dynamic SQL in Oracle

I have a problem with my plsql code and try almost everything. Now i'm loosing my mind and power to solve my problem :)
The case is that I want to search all tables schema for specific string assigned to variable v_ss and print it to DBMS_OUTPUT. I know that there are ready-made solutions for that kind of cases, but I wanted to code it by myself. Below code gives me an error that "table does not exist" in my v_stmt. I assume that this select does not recognize rec.column_name, but why?
Here is my code:
DECLARE
v_stmt VARCHAR2(1000);
v_ss VARCHAR2(30) := 'Argentina';
v_own ALL_TAB_COLUMNS.OWNER%TYPE;
v_tab_nam ALL_TAB_COLUMNS.TABLE_NAME%TYPE;
v_col_nam ALL_TAB_COLUMNS.COLUMN_NAME%TYPE;
CURSOR cur_asc IS
SELECT t.owner, t.table_name, t.column_name
FROM SYS.ALL_TAB_COLUMNS t
WHERE t.OWNER LIKE 'HR'
AND t.DATA_TYPE LIKE 'VARCHAR2';
BEGIN
FOR rec IN cur_asc LOOP
v_stmt := 'SELECT rec.owner, rec.table_name, rec.column_name FROM rec.table_name WHERE rec.column_name LIKE :1';
EXECUTE IMMEDIATE v_stmt INTO v_own, v_tab_nam, v_col_nam USING v_ss;
DBMS_OUTPUT.put_line(v_own || ':' || v_tab_nam || ':' || v_col_nam);
END LOOP;
END;
/
Error report -
ORA-00942: table or view does not exist
ORA-06512: at line 19
00942. 00000 - "table or view does not exist"
*Cause:
*Action:
Could You give me an explanation for my bug and how to fix it? Thanks in advance
The dynamic statement is executed in a context that cannot see your PL/SQL variables, so when it runs rec.table_name etc. are interpreted as SQL-level objects - which don't exist.
You have to concatenate the variable values into the dynamic statement for the from and where clause; you can do the same in the select list (though they'd need to be wrapped in escaped single-quotes as they are strings), or use bind variables there:
v_stmt := 'SELECT :owner, :table_name, :column_name FROM '
|| rec.table_name || ' WHERE ' || rec.column_name || ' LIKE :ss';
EXECUTE IMMEDIATE v_stmt INTO v_own, v_tab_nam, v_col_nam
USING rec.owner, rec.table_name, rec.column_name, v_ss;
You can't use bind variables for object identifiers, hence needing the concatenation for those parts.
Unless you run this as the HR user, in which case you could be using user_tables instead of all_tables, you also need to specify the schema in the query (As Tony and Lalit mentioned); either hard-coding HR or using the queried owner:
v_stmt := 'SELECT :owner, :table_name, :column_name FROM '
|| rec.owner || '.' || rec.table_name
|| ' WHERE ' || rec.column_name || ' LIKE :ss';
This is going to error for all the tables that don't contain exactly one matching value though - if the dynamic select gets zero rows, or more than one row. But that's a separate issue.
v_stmt := 'SELECT rec.owner, rec.table_name, rec.column_name FROM rec.table_name WHERE rec.column_name LIKE :1';
Your dynamic sql statement is malformed. If you enclose the variables between single-quotation marks then they are treated as literals and not variables any more.
You must prefix the schema before the table_name else you must run the script while you are connected as HR user.
v_stmt := 'SELECT '''||rec.owner||''', '''|| rec.table_name||''', '
||rec.column_name||' FROM '||rec.owner||'.'
||rec.table_name||' WHERE '||rec.column_name||' LIKE :1';
Always remember, whenever working with dynamic statements, always use DBMS_OUTPUT to first verify the actual SQL being generated. This is the best way to debug dynamic queries.
You need to handle NO_DATA_FOUND exception because it will throw error for all those tables which do not have any match for the filter you are using.
SQL> DECLARE
2 v_stmt VARCHAR2(1000);
3 v_ss VARCHAR2(30) := 'Argentina';
4 v_own ALL_TAB_COLUMNS.OWNER%TYPE;
5 v_tab_nam ALL_TAB_COLUMNS.TABLE_NAME%TYPE;
6 v_col_nam ALL_TAB_COLUMNS.COLUMN_NAME%TYPE;
7 CURSOR cur_asc
8 IS
9 SELECT t.owner,
10 t.table_name,
11 t.column_name
12 FROM SYS.ALL_TAB_COLUMNS t
13 WHERE t.OWNER LIKE 'HR'
14 AND t.DATA_TYPE LIKE 'VARCHAR2';
15 BEGIN
16 FOR rec IN cur_asc
17 LOOP
18 v_stmt := 'SELECT '''||rec.owner||''', '''|| rec.table_name||''', '
19 || rec.column_name||' FROM '||rec.owner||'.'
20 || rec.table_name||' WHERE '||rec.column_name||' LIKE :1';
21 BEGIN
22 EXECUTE IMMEDIATE v_stmt INTO v_own,
23 v_tab_nam,
24 v_col_nam USING v_ss;
25 DBMS_OUTPUT.put_line(v_own || ':' || v_tab_nam || ':' || v_col_nam);
26 EXCEPTION
27 WHEN NO_DATA_FOUND THEN
28 NULL;
29 END;
30 END LOOP;
31 END;
32 /
HR:COUNTRIES:Argentina
PL/SQL procedure successfully completed.
There are 2 issues here:
You need to concatenate the table and column names into the dynamic SQL because 'SELECT rec.owner...' is trying to select a column called owner from a table with alias rec, it does not reference your for loop record.
Since there could be no matching rows or many matching rows in a table, you cannot use select into, you need to use a cursor.
try this:
DECLARE
v_stmt VARCHAR2(1000);
v_ss VARCHAR2(30) := 'Argentina';
v_own ALL_TAB_COLUMNS.OWNER%TYPE;
v_tab_nam ALL_TAB_COLUMNS.TABLE_NAME%TYPE;
v_col_nam ALL_TAB_COLUMNS.COLUMN_NAME%TYPE;
CURSOR cur_asc IS
SELECT t.owner, t.table_name, t.column_name
FROM ALL_TAB_COLUMNS t
WHERE t.DATA_TYPE LIKE 'VARCHAR2'
AND ROWNUM < 10;
c SYS_REFCURSOR;
BEGIN
FOR rec IN cur_asc LOOP
v_stmt := 'SELECT ''' || rec.owner || ''',''' || rec.table_name || ''', ''' || rec.column_name || ''' FROM ' || rec.table_name || ' WHERE ' || rec.column_name || ' LIKE :1';
OPEN c FOR v_stmt USING v_ss;
LOOP
FETCH c INTO v_own, v_tab_nam, v_col_nam;
EXIT WHEN c%NOTFOUND;
DBMS_OUTPUT.put_line(v_own || ':' || v_tab_nam || ':' || v_col_nam);
END LOOP;
CLOSE c;
END LOOP;
END;
/

Resources