Oracle - store large string in CLOB - plsql

I need to save a procedure body into a Clob column with a use of variable. String is longer than 4000 characters, so I can't use VarChar2, but with CLOB variable I receive error "ORA-01422: exact fetch returns more than requested number of rows". Same error appears with Varchar2. My PL/SQL block:
DECLARE
txt_procedure CLOB;
BEGIN
SELECT text INTO txt_procedure
FROM all_source
WHERE name = 'My_procedure'
ORDER BY line;
INSERT INTO TABLE1(ID,DATE,CLOB_COLUMN)
VALUES (my_seq.NEXTVAL,'11.10.2018',txt_procedure);
END;
/
How could I insert procedure body into clob column ?

As you will get multiple rows from your query for every line of your source, the following might help:
DECLARE
txt_procedure CLOB;
BEGIN
FOR source_r IN ( SELECT text
FROM all_source
WHERE name = 'My_procedure'
ORDER BY line
)
LOOP
txt_procedure := txt_procedure || chr(10) || source_r.text;
END LOOP;
INSERT INTO TABLE1(ID,DATE,CLOB_COLUMN)
VALUES (my_seq.NEXTVAL,'11.10.2018',txt_procedure);
END;
/
UPDATE
As an alternative, you might also use the DBMS_METADATA package for this:
DECLARE
txt_procedure CLOB;
BEGIN
txt_procedure := DBMS_METADATA.get_ddl(
object_type => 'PROCEDURE',
name => 'My_procedure',
owner => 'YOUR_SCHEMA'
);
INSERT INTO TABLE1(ID,DATE,CLOB_COLUMN)
VALUES (my_seq.NEXTVAL,'11.10.2018',txt_procedure);
END;
/

Related

Stored Procedure not reading from variable in IF-ELSE statement

I started writing this stored procedure and I faced some issues when I try to pass a variable in my conditional statement.
I can use the parameter BANKN which works fine, but when never I passed the declared variable AC_t somehow the PL/SQL ignores it.
Any idea please what I'm missing here?
create or replace PROCEDURE TTEST1 (
CR IN VARCHAR2,
BANKN IN VARCHAR2,
P_CURSOR OUT SYS_REFCURSOR
)
AS
G_AC CHAR(10);
AC_t Billing.Account %Type;
BEGIN
IF BANKN = 'WLNV' AND AC_t = 'Private'
THEN
IF CR IN (
'EUR',
'CZK',
'USD'
)
THEN
OPEN P_CURSOR
FOR
SELECT G_AC AS GL_ACC,
Billing.Account AS ACC_Type
INTO
G_AC,
AC_t
FROM Billing
INNER JOIN invoice ON Billing.ACC_NO = invoice.ACC_NO;
END IF ;
END IF ;
END;
My aim here is to expand this code by using AC_t value from Billing.ACCount and retrieve what ever data that can be 'Private' or 'Public'.
To do this, I need to use case or IF statement, however when I use
Billing.ACCount, I got an error "not allowed in this context", for this reason I use synonym AC_t but this don't read values from Billing table unless I use it in WHERE clause.
ACC_NO
Account
1
Private
2
Public
Extended code:
IF BANKN = 'WLNV' AND AC_t = 'Private'
THEN
...
...
ELSIF IF BANKN = 'WLNV' AND AC_t = 'Public'
THEN
....
...
You cannot use SELECT ... INTO with a cursor and you need to declare a value for the ac_t variable (but since it is a column in the table you may want a WHERE clause in the cursor). Like this:
CREATE PROCEDURE TTEST1 (
p_CR IN VARCHAR2,
p_BANKN IN VARCHAR2,
P_CURSOR OUT SYS_REFCURSOR
)
AS
BEGIN
IF p_BANKN = 'WLNV'
AND p_CR IN ( 'EUR', 'CZK', 'USD' )
THEN
OPEN P_CURSOR FOR
SELECT G_AC AS GL_ACC,
b.account AS ACC_Type
FROM Billing b
INNER JOIN invoice i
ON b.ACC_NO = i.ACC_NO
WHERE b.account = 'Private';
END IF;
END;
/
Which, if you have the sample data:
CREATE TABLE invoice (acc_no, g_AC) AS
SELECT 1, 2 FROM DUAL;
CREATE TABLE billing (acc_no, account) AS
SELECT 1, 'Private' FROM DUAL;
Then you can call the procedure and print the contents of the cursor using:
DECLARE
cur SYS_REFCURSOR;
v_g_ac INVOICE.G_AC%TYPE;
v_ac_t BILLING.ACCOUNT%TYPE;
BEGIN
ttest1('EUR', 'WLNV', cur);
LOOP
FETCH cur INTO v_g_ac, v_ac_t;
EXIT WHEN cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( v_g_ac || ', ' || v_ac_t );
END LOOP;
CLOSE cur;
END;
/
Which outputs:
2, Private
db<>fiddle here

Does Oracle support non-scalar cursor parameter?

This is a question about Oracle PL/SQL.
I have a procedure in which the exact WHERE clause is not known until the run time:
DECLARE
CURSOR my_cursor is
SELECT ...
FROM ...
WHERE terms in (
(SELECT future_term2 FROM term_table), -- whether this element should be included is conditional
(SELECT future_term1 FROM term_table),
(SELECT present_term FROM term_table)
);
BEGIN
(the processing)
END;
/
What the (SELECT ... FROM term_table) query returns is a 4-character string.
For a solution to this, I am thinking of using a parameterized cursor:
DECLARE
target_terms SOME_DATATYPE;
CURSOR my_cursor (pi_terms IN SOME_DATATYPE) IS
SELECT ...
FROM ...
WHERE terms in my_cursor.pi_terms;
BEGIN
target_terms := CASE term_digit
WHEN '2' THEN (
(SELECT future_term2 FROM term_table),
(SELECT future_term1 FROM term_table),
(SELECT present_term FROM term_table)
) ELSE (
(SELECT future_term1 FROM term_table),
(SELECT present_term FROM term_table)
)
END;
FOR my_record IN my_cursor (target_terms) LOOP
(the processing)
END LOOP;
END;
/
The problem is what the datatype for SOME_DATATYPE should be is not known to me, nor is it known whether Oracle supports such a cursor parameter at all. If supported, is the way shown above to fabricate the value for target_terms correct? If not, how?
Hope someone who know can advise. And thanks a lot for the help.
You can certainly pass a parameter to a cursor, just like you can to a function - but only IN parameters. However, PL/SQL is a strongly typed language, so the datatype must be specified at the time of compilation.
It looks to me like what you will need to do is construct the query dynamically and then use
OPEN cursor FOR l_query;
where l_query is the constructed string. This should give you a feel for what you can do:
CREATE OR REPLACE PACKAGE return_id_sal
AUTHID DEFINER
IS
TYPE employee_rt IS RECORD
(
employee_id employees.employee_id%TYPE,
salary employees.salary%TYPE
);
FUNCTION allrows_by (append_to_from_in IN VARCHAR2 DEFAULT NULL)
RETURN SYS_REFCURSOR;
END return_id_sal;
/
CREATE OR REPLACE PACKAGE BODY return_id_sal
IS
FUNCTION allrows_by (append_to_from_in IN VARCHAR2 DEFAULT NULL)
RETURN SYS_REFCURSOR
IS
l_return SYS_REFCURSOR;
BEGIN
OPEN l_return FOR
'SELECT employee_id, salary FROM employees ' || append_to_from_in;
RETURN l_return;
END allrows_by;
END return_id_sal;
/
DECLARE
l_cursor SYS_REFCURSOR;
l_row return_id_sal.employee_rt;
BEGIN
l_cursor := return_id_sal.allrows_by ('WHERE department_id = 10');
LOOP
FETCH l_cursor INTO l_row;
EXIT WHEN l_cursor%NOTFOUND;
END LOOP;
END;
/
You will need to take precautions against SQL injection with this sort of code. Certainly a user should never be able to pass SQL text directly to such a function!
You can use also some built-in VARRAY SQL types like SYS.ODCIVARCHAR2LIST or create your own :
CREATE OR REPLACE NONEDITIONABLE TYPE VARCHARLIST
AS VARRAY(32767) OF VARCHAR2(4000);
Then you can use it with SELECT COLUMN_VALUE FROM TABLE(COLLECTION) statement in your cursor:
DECLARE
l_terms SYS.ODCIVARCHAR2LIS; --or VARCHARLIST
CURSOR my_cursor (p_terms IN SYS.ODCIVARCHAR2LIS) IS
SELECT your_column
FROM your_table
WHERE terms in (select COLUMN_VALUE from table (p_terms));
BEGIN
select term
bulk collect into l_terms
from (
select 'term1' term from dual
union all
select 'term2' term from dual
);
FOR my_record IN my_cursor (l_terms) LOOP
--process data from your cursor...
END LOOP;
END;

procedure using record type

I would like to create a procedure which returns a list of the first five records. I must use record type and table type. What am I doing wrong?
CREATE OR REPLACE PROCEDURE procedure_example(v_table OUT v_rec) IS
CURSOR cur1 IS
SELECT * FROM (
SELECT
type_note,
note
FROM dd_note ORDER BY type_note)
WHERE rownum < 5;
TYPE v_rec IS RECORD ( v_type_note NUMBER(2)
, v_note VARCHAR(30));
TYPE v_table IS TABLE OF v_rec INDEX BY BINARY_INTEGER;
BEGIN
OPEN cur1;
LOOP
FETCH cur1 INTO v_type_note, v_note;
dbms_output.put_line(v_type_note || '. --- ' || v_note);
EXIT WHEN cur1%NOTFOUND;
--enter code here
END LOOP;
CLOSE cur1;
END procedure_example;
The place where you declared your types is wrong. You must declare the type "v_rec" and type "v_table" in the package where this procedure belongs, NOT inside this procedure. Here's an improved version of your code. First, declare these types in your package:
TYPE v_rec IS RECORD(
v_type_note number(2),
v_note varchar(30));
TYPE v_table is table of v_rec index by pls_integer;
Then here's your function:
create or replace PROCEDURE procedure_example (out_table OUT v_table) IS
BEGIN
select type_note, note BULK COLLECT into out_table from (select type_note, note from dd_note order by type_note) where rownum < 5
FOR i in 1..out_table.COUNT LOOP
dbms_output.put_line(out_table(i).v_type_note || '. --- ' || out_table(i).v_note);
end loop;
END procedure_example;

Assigning object type in plsql

I need your help to know how to assign the object type through a string in PLSQL
Below is the problem description:
I first created the object types as below:
create or replace type picu_obj is object(Customer_ID varchar2(32767),Customer_Name varchar2(32767),Server_Name varchar2(32767),Time_stamp varchar2(32767));
create or replace type picu_obj_tab is table of picu_obj;
and I have a PLSQL block as below:
declare
l_str1 varchar2(1000);
l_str2 varchar2(10000);
l_newstr1_1 varchar2(10000);
picu_var picu_obj_tab;
cursor c1cudetails
is
select item,current_value
from
(select rownum,
last_value(category ignore nulls) over (order by rownum) category ,
last_value(item ignore nulls) over (order by rownum) item,
current_value
from pi_perfdata_new
order by rownum
)
where upper(category) like '%CUSTOMER%DETAILS%' ;
type cudet is table of c1cudetails%rowtype index by pls_integer;
l_cudet cudet;
begin
/* create dynamic string for items */
open c1cudetails;
fetch c1cudetails bulk collect into l_cudet limit 50;
for i in l_cudet.first..l_cudet.last loop
l_str1:=l_str1||','||''''||l_cudet(i).current_value||'''';
l_str2:=trim(leading ',' from l_str1);
l_newstr1_1:='picu_obj_tab(picu_obj('||l_str2||'))';
end loop;
-- dbms_output.put_line(''||l_newstr1_1||'');
-- picu_var := l_newstr1_1;
close c1cudetails;
end;
For the string "l_newstr1_1" following value is retruned from above PLSQL block
picu_obj_tab(picu_obj('CSCO5','DXRTYE','PI22-pro-333','2015-07-22-22:48:56'))
Now I want to assign the above result to variable "picu_var" which I have declared.
Basically I need to convert to the following during runtime.
picu_var := picu_obj_tab(picu_obj('CSCO5','DXRTYE','PI22-pro-333','2015-07-22-22:48:56'))
How to achieve the same?
Please suggest how to initialize the object type variable to the string values.
Use dynamic PL/SQL like this:
execute immediate 'begin :x := ' || l_newstr1_1|| '; end;'
using out picu_var;

PLS-00103 ERROR, what is wrong in the code

CREATE OR REPLACE PROCEDURE proc2_del_rows
(v_tname VARCHAR2,
v_condition VARCHAR2 DEFAULT NULL)
AS
sql_stmt VARCHAR2(500);
where_clause VARCHAR2(200) := 'WHERE'||' '||v_condition;
BEGIN
IF v_condition IS NULL THEN
where_clause := NULL;
END IF;
sql_stmt := 'DELETE FROM :1'||' '||where_clause;
EXECUTE IMMEDIATE sql_stmt USING v_tname;
COMMIT;
END;
/
The table name can't be a bind variable. Do a DBMS_ASSERT on the input table name parameter and make sure it is a valid table name literal, and then directly concatenate it to the delete statement. This will at least protect you against sql injection.
I'd like to know the reason behind doing a delete using a procedure and granting execute on this procedure to individual users, rather than granting a delete on the table to a user directly, which would somewhat be easier to control/restrict. I don't see how this is better in terms on security if that is what you are going for.
CREATE or replace PROCEDURE proc2_del_rows
(v_tname VARCHAR2,
v_condition VARCHAR2 DEFAULT NULL)
AS
sql_stmt VARCHAR2(500);
where_clause VARCHAR2(200) := 'WHERE'||' '||v_condition;
BEGIN
IF v_condition IS NULL THEN
where_clause := NULL;
END IF;
sql_stmt := 'DELETE FROM '||v_tname||' '||where_clause;
EXECUTE IMMEDIATE sql_stmt;
END;
/
To include a single-quote character within a string literal you need to double up the single quotes, as in proc2_del_rows('EMP', 'JOB=''CLERK''').
Documentation here

Resources