I am trying to construct query dynamically but after string concatenation the select statement not producing any result in pl/sql.
Please help me on this
DECLARE
person_id NUMBER;
BEGIN
DECLARE
age_where VARCHAR2(100 CHAR);
TEMP_WHERE VARCHAR2(100 CHAR) := '';
add_temp_where BOOLEAN := true;
begin
age_where := q'[ and age=28]';
IF(ADD_TEMP_WHERE) THEN
TEMP_WHERE := age_where;
END IF;
SELECT id INTO person_id FROM PERSON WHERE name = 'David' || TEMP_WHERE ;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('no data');
END;
DBMS_OUTPUT.PUT_LINE('result : ' || person_id);
END;
Table entries
ID NAME AGE ADDRESS SALARY
-----------------------------------------------
1 David 28 PURAM 30000
2 Vimal 30 MARUR 20000
Output:
anonymous block completed
no data
result :
You'll need dynamic SQL for that.
SQL> DECLARE
2 person_id NUMBER;
3 age_where VARCHAR2 (100 CHAR);
4 TEMP_WHERE VARCHAR2 (100 CHAR) := '';
5 add_temp_where BOOLEAN := TRUE;
6 l_str VARCHAR2 (400);
7 BEGIN
8 age_where := q'[ and age=28]';
9
10 IF (ADD_TEMP_WHERE)
11 THEN
12 TEMP_WHERE := age_where;
13 END IF;
14
15 l_str := q'[SELECT id FROM PERSON WHERE name = 'David']' || TEMP_WHERE;
16
17 EXECUTE IMMEDIATE l_str
18 INTO person_id;
19
20 DBMS_OUTPUT.PUT_LINE ('result : ' || person_id);
21 END;
22 /
result : 1
PL/SQL procedure successfully completed.
SQL>
Your objective is to have dynamic predicate in a query, based on input parameters. This is a frequent task.
The most important think is to realize, that you should always use bind variables for production queries, i.e. ommit construction such as WHERE name = 'David' || TEMP_WHERE.
There are three options in PL/SQL
Use IF - ELSE
This is the simplest option where for each combination of the input parameter there is one IF/ELSE branch
declare
l_person_id int;
l_name varchar2(10) := 'David';
l_age int := 28;
add_temp_where int := 1;
begin
if add_temp_where = 0 then
select id into l_person_id from person where name = l_name;
else
select id into l_person_id from person where name = l_name and age = l_age;
end if;
dbms_output.put_line(l_person_id);
end;
/
This will work in your case with one optional parameter, but is not practicable with more parameters. For 3 parameters you will need 8 branches.
Use OR to Disable Predicates
This option use only one static statement and use the OR logic to disable unwanted parameters.
Note that I'm using the parameter add_temp_where and int 0,1 to be able to use it in SQL.
declare
l_person_id int;
l_name varchar2(10) := 'David';
l_age int := 28;
add_temp_where int := 1;
begin
select id into l_person_id from person where name = l_name and (add_temp_where = 0 or age = l_age);
dbms_output.put_line(l_person_id);
end;
/
So basically if add_temp_where = 0 than the predicate age = l_age is ignored due to shortcut evaluation.
This option works fine in case that for all options the query returns similar number of rows (technically the same execution plan is used).
It fails badly in case that for one option the whole table is returned and for other ony some small part (via index).
In such case you need to use dynamic SQL.
Dynamic SQL
You use the same trick as above with OR, so you generate following SQL strings
-- for add_temp_where = 1
select id from person where name = :1 and age = :2
-- for add_temp_where = 0
select id from person where name = :1 and (0=0 or age = :2)'
Note that both statement have two bind variables, so they can be used in one EXECUTE IMMEDIATE as follows:
declare
l_person_id int;
l_name varchar2(10) := 'David';
l_age int := 28;
add_temp_where BOOLEAN := true;
--
l_sql1 varchar2(1000) := 'select id from person where name = :1 and age = :2';
l_sql2 varchar2(1000) := 'select id from person where name = :1 and (0=0 or age = :2)';
begin
execute immediate l_sql1 into l_person_id using l_name, l_age;
dbms_output.put_line(l_person_id);
execute immediate l_sql2 into l_person_id using l_name, l_age;
dbms_output.put_line(l_person_id);
end;
/
Both statements are independent and can have therefore different execution plans, which solves the problem of the second option.
More info and credits to this option here and here
Related
I am new to PL/SQL and I try to have the table name of a SELECT dynamically set by a parameter.
This is working fine.
DECLARE
FUNCTION foo (pat VARCHAR) RETURN NUMBER IS
tabname VARCHAR (100) := 'my_table';
n NUMBER := -1;
sqlcmd VARCHAR (100) := 'SELECT COUNT(*) FROM ' || tabname || ' WHERE bezeichnung LIKE :1';
BEGIN
EXECUTE IMMEDIATE sqlcmd INTO n USING pat;
RETURN n;
END foo;
BEGIN
dbms_output.put_line (foo ('bla%'));
END;
If I try to have tabname set by a parameter as it is with pat then it fails with ther error :
invalid table name
DECLARE
FUNCTION defval (pat VARCHAR, offs NUMBER) RETURN NUMBER IS
tabname VARCHAR (100) := 'A_KGL_EIGENSCHAFTEN';
n NUMBER := -1;
sqlcmd VARCHAR (100) := 'SELECT COUNT(*) FROM :1 WHERE bezeichnung LIKE :2';
BEGIN
EXECUTE IMMEDIATE sqlcmd INTO n USING tabname, pat;
dbms_output.put_line ('tabname: ' || tabname);
dbms_output.put_line ('n: ' || n);
RETURN n;
END defval;
BEGIN
dbms_output.put_line (defval ('LPG.GAX.%.DBE', 2));
END;
How can I set the table name by this bound parameters?
If you are looking to prevent the sql injection using concate then you can use the sys.DBMS_ASSERT.SQL_OBJECT_NAME(p_table_name)
So your string variable should look like this:
sqlcmd VARCHAR (100) := 'SELECT COUNT(*) FROM '
|| sys.DBMS_ASSERT.SQL_OBJECT_NAME(tabname)
|| ' WHERE bezeichnung LIKE :1';
You can learn more about DBMS_ASSERT from oracle documentation.
You cannot use bind arguments to pass the names of schema objects to a dynamic SQL statement.
From the Oracle documentation : https://docs.oracle.com/cd/B12037_01/appdev.101/b10807/13_elems017.htm
So yes, your 2nd query is bound to fail. Your concat in the first one seem the good way to go.
I am trying to write a procedure with the following functionality. Namely, is looking for records from the tables in the schema. In particular, it is a typepkstring column in these tables. At the same time, I have the composedtypes table on the same schema, which has the column pk. The pk column contains all number identifiers from the aforementioned typepkstring column. And now the problem is that in typepkstring we have additionally keys that are not in the column pk in tabel composedtypes. And I have to search the schema and write it out together with the name of the table in which they are located.
at this point, my procedure looks as follows:
create or replace PROCEDURE SIEROT
(i_table_name VARCHAR2)
is
CURSOR c is
SELECT DISTINCT i_table_name.TYPEPKSTRING
FROM i_table_name
LEFT OUTER JOIN COMPOSEDTYPES
ON i_table_name.TYPEPKSTRING=COMPOSEDTYPES.PK
WHERE COMPOSEDTYPES.PK IS NULL;
TYPE c_list IS TABLE of PRODUCTS.TYPEPKSTRING%type INDEX BY binary_integer;
TYPEPK_list c_list;
counter integer :=0;
BEGIN
FOR n IN c LOOP
counter := counter +1;
TYPEPK_list(counter) := n.TYPEPKSTRING;
dbms_output.put_line('TABLE: '||i_table_name||'('||counter||'):'||TYPEPK_list(counter));
END LOOP;
END;
and calling:
set serveroutput on
DECLARE
ind integer := 0;
BEGIN
FOR ind IN (select table_name from all_tab_columns where column_name='TYPEPKSTRING' AND table_name!='COMPOSEDTYPES')
LOOP
BEGIN
SIEROT(ind.table_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
null;
END;
END LOOP;
END;
this is the second approach to the problem I used, it seemed easier to me. The second one, also not functional, was based on cursors using the array type.
My problem is:
calling certainly works fine, but when I compile the same procedure I get the following error:
Procedure SIEROT compiled
LINE/COL ERROR
--------- -------------------------------------------------------------
5/7 PL/SQL: SQL Statement ignored
6/31 PL/SQL: ORA-00942: table or view does not exist
17/7 PL/SQL: Statement ignored
17/31 PLS-00364: loop index variable 'N' use is invalid
Errors: check compiler log
Same select for permanently entered table names, where I have put 2 records that meet the conditions of the task myself, works correctly:
SELECT DISTINCT TESTOWY.TYPEPKSTRING
FROM TESTOWY
LEFT OUTER JOIN COMPOSEDTYPES
ON TESTOWY.TYPEPKSTRING=COMPOSEDTYPES.PK
WHERE COMPOSEDTYPES.PK IS NULL;
and for select so typed in the procedure, it gets the intended effect but, I need to parameterize the name of the source table if it wants to search all of the whole schema, not just on specific one. Only one of the abovementioned selects would be sufficient for one particular:
TABLE: TESTOWY(1):8790000000098
TABLE: TESTOWY(2):8790000000124
PL/SQL procedure successfully completed.
I really do not have the strength for this procedure. Write me how to improve it to work but also fulfill your opinion. Thanks for any hints or corrections;)
You can't use the value of a parameter as a table name in a query directly - you'll need to build your SELECT statement dynamically and then use a loop to fetch the data:
CREATE OR REPLACE PROCEDURE SIEROT(i_table_name VARCHAR2) IS
strSelect VARCHAR2(32767);
c SYS_REFCURSOR;
vTYPEPKSTRING PRODUCTS.TYPEPKSTRING%TYPE;
TYPE c_list IS TABLE of PRODUCTS.TYPEPKSTRING%type INDEX BY binary_integer;
TYPEPK_list c_list;
counter integer := 0;
BEGIN
strSelect := 'SELECT DISTINCT i.TYPEPKSTRING ' ||
' FROM ' || i_table_name || ' i ' ||
' LEFT OUTER JOIN COMPOSEDTYPES c ' ||
' ON i.TYPEPKSTRING = c.PK ' ||
' WHERE c.PK IS NULL';
OPEN c FOR strSelect;
FETCH c INTO vTYPEPKSTRING;
WHILE c%FOUND LOOP
counter := counter + 1;
TYPEPK_list(counter) := vTYPEPKSTRING;
dbms_output.put_line('TABLE: '||i_table_name||'('||counter||'):'||TYPEPK_list(counter));
FETCH c INTO vTYPEPKSTRING;
END LOOP;
CLOSE c;
EXCEPTION
WHEN OTHERS THEN
IF c%ISOPEN THEN
CLOSE c;
END IF;
END SIEROT;
Best of luck.
You'll have to use dynamic SQL (i.e. execute immediate) here, as table name (passed as a parameter) can't be used in a query. The way you put it, it seems that the whole code in SIEROT procedure should be dynamic.
Here's an example based on Scott's schema (as I don't have your tables):
SQL> set serveroutput on
SQL> create or replace procedure sierot(i_table_name in varchar2)
2 is
3
4 l_str varchar2(2000);
5 l_str_2 varchar2(2000);
6 counter integer := 0;
7 begin
8 l_str := 'select distinct i.empno typepkstring from ' || i_table_name || ' i join dept d on d.deptno = i.deptno
9 where d.deptno = 10';
10
11 l_str_2 := 'declare
12 counter integer := 0;
13 type c_list is table of emp.empno%type index by binary_integer;
14 typepk_list c_list;
15 begin
16 for n in (' || l_str ||') loop
17 counter := counter + 1;
18 typepk_list(counter) := n.typepkstring;
19 dbms_output.put_line(TYPEPK_list(counter));
20 end loop;
21 end;';
22
23 execute immediate l_str_2;
24
25 end;
26 /
Procedure created.
SQL> exec sierot('emp');
7782
7839
7934
PL/SQL procedure successfully completed.
SQL>
I have a procedure which receive as input parameter a record with 170 columns (it is based on the structure of a table).
In the procedure I want to call a debugging procedure one of whose parameters is a text string containing all the field names and values of this record.
For example:
CREATE OR REPLACE PROCEDURE xxx (pi_record IN table_name%ROWTYPE) as
text VARCHAR2(10000) := NULL;
BEGIN
...
text := 'pi_record.column1 = ' || pi_record.column1 || CHR(13) ||
'pi_record.column2 = ' || pi_record.column2 || CHR(13) ||
...
'pi_record.column170 = ' || pi_record.column170;
logging_procedure (text);
...
END;
Is there any simple way to achieve this in a dynamic way (looping through record fields names and values) without enumerating all of them?
Maybe something like this:
CREATE OR REPLACE PROCEDURE xxx (pi_record IN table_name%ROWTYPE) as
text VARCHAR2(10000) := NULL;
BEGIN
...
LOOP in pi_record.columns
text := text || CHR(13) || pi_record.column.name || ' : ' || pi_record.column.value
END LOOP
logging_procedure (text);
...
END;
Many thanks,
Here's one way to do that. A package spec contains a variable whose type matches the one we'll use in a procedure.
SQL> set serveroutput on
SQL> create or replace package pkg_xxx
2 as
3 dept_rec dept%rowtype;
4 end;
5 /
Package created.
SQL> create or replace procedure xxx (pi_record in dept%rowtype)
2 as
3 text varchar2 (10000) := null;
4 l_str varchar2 (200);
5 l_var varchar2 (200);
6 begin
7 pkg_xxx.dept_rec := pi_record;
8
9 for cur_r in ( select column_name
10 from user_tab_columns
11 where table_name = 'DEPT'
12 order by column_id)
13 loop
14 l_str :=
15 'begin '
16 || ':x := to_char(pkg_xxx.dept_rec.'
17 || cur_r.column_name
18 || '); '
19 || 'end; ';
20
21 execute immediate l_str using out l_var;
22
23 text := text || chr (10) || cur_r.column_name || ' = ' || l_var;
24 end loop;
25
26 dbms_output.put_line (text);
27 end;
28 /
Procedure created.
Now, let's pass something to the procedure and see what happens:
SQL> declare
2 cursor c1
3 is
4 select *
5 from dept
6 where deptno = 10;
7
8 c1r c1%rowtype;
9 begin
10 open c1;
11 fetch c1 into c1r;
12 close c1;
13
14 xxx (c1r);
15 end;
16 /
DEPTNO = 10
DNAME = ACCOUNTING
LOC = NEW YORK
PL/SQL procedure successfully completed.
SQL>
Huh, kind of works (if that's what you asked). Of course, it is just an example, you'll have to modify it if you want to get something really smart (hint: DATE columns).
The only idea I have is to insert the record into a TEMP table:
CREATE OR REPLACE PROCEDURE xxx (pi_record IN TABLE_NAME%ROWTYPE) AS
TEXT VARCHAR2(10000) := NULL;
item VARCHAR2(1000);
TABLE_DOES_NOT_EXIST EXCEPTION;
PRAGMA EXCEPTION_INIT(TABLE_DOES_NOT_EXIST, -942);
BEGIN
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE TABLE_NAME_TMP';
EXCEPTION
WHEN TABLE_DOES_NOT_EXIST then null;
END;
EXECUTE IMMEDIATE 'CREATE GLOBAL TEMPORARY TABLE TABLE_NAME_TMP AS SELECT * FROM TABLE_NAME WHERE ROWNUM = 0';
DELETE FROM TABLE_NAME_TMP;
INSERT INTO TABLE_NAME_TMP VALUES pi_record;
FOR aCol IN (SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS WHERE table_name = 'TABLE_NAME' ORDER BY COLUMN_ID) LOOP
EXECUTE IMMEDIATE 'SELECT '||aCol.COLUMN_NAME||' FROM TABLE_NAME_TMP' INTO item;
TEXT := TEXT || CHR(13) || aCol.COLUMN_NAME || ' : ' || item;
END LOOP;
DBMS_OUTPUT.PUT_LINE ( TEXT );
END;
In case table TABLE_NAME has static attributes then you should skip dynamic DROP TABLE ... and CREATE GLOBAL TEMPORARY TABLE ... and create the TEMP table only once.
everyone!
I got a different approach to get the difference between records dynamically:
You just have to create the global variables on the package header as bellow:
v_NAME_OF_TABLE_new NAME_OF_TABLE%rowtype;
v_NAME_OF_TABLE_old NAME_OF_TABLE%rowtype;
then create the function on your pkg body that return a boolean even if a field is different:
function is_different(p_old NAME_OF_TABLE%rowtype, p_new NAME_OF_TABLE%rowtype)
return boolean
is
cursor cols is
select tb.COLUMN_NAME
from all_tab_columns tb
where tb.OWNER = 'DW'
and tb.TABLE_NAME = 'NAME_OF_TABLE'
order by tb.COLUMN_ID;
l_sql varchar2(4000);
l_new varchar2(4000);
l_old varchar2(4000);
begin
pkg_NAME.v_NAME_OF_TABLE_new := p_new;
pkg_NAME.v_NAME_OF_TABLE_old := p_old;
for reg in cols loop
l_sql := '
begin
:x := pkg_NAME.v_NAME_OF_TABLE_new.'||reg.COLUMN_NAME||';'||'
end;';
execute immediate l_sql using out l_new;
l_sql := '
begin
:x := pkg_NAME.v_NAME_OF_TABLE_old.'||reg.COLUMN_NAME||';'||'
end;';
execute immediate l_sql using out l_old;
--- dbms_output.put_line(l_new||' - '||l_old);
if nvl(l_new,'NULO') <> nvl(l_old,'NULO') then
return true;
end if;
end loop;
return false;
end;
Atention: This can turn your process heavier and slower.
That's all!
Hope this can be helpful!
Could you help me to pass the input values (at execution time: i mean to enter multiple values for single variable at once).
Here is my code for which i am giving one input at a time either hard coded input or single input at time.
declare
type TEmpRec is record (
EmployeeID EMPLOYEES.EMPLOYEE_ID%TYPE,
LastName EMPLOYEES.LAST_NAME%TYPE
);
type TEmpList is table of TEmpRec;
vEmpList TEmpList;
---------
function EmpRec(pEmployeeID EMPLOYEES.EMPLOYEE_ID%TYPE,
pLastName EMPLOYEES.LAST_NAME%TYPE default null) return TEmpRec is
-- Effective "Record constructor"
vResult TEmpRec;
begin
vResult.EmployeeID := pEmployeeID;
vResult.LastName := pLastName;
return vResult;
end;
---------
procedure SearchRecs(pEmpList in out nocopy TEmpList) is -- Nocopy is a hint to pass by reference (pointer, so small) rather than value (actual contents, so big)
vIndex PLS_integer;
begin
if pEmpList is not null then
vIndex := pEmpList.First;
while vIndex is not null -- The "while" approach can be used on sparse collections (where items have been deleted)
loop
begin
select LAST_NAME
into pEmpList(vIndex).LastName
from EMPLOYEES
where EMPLOYEE_ID = pEmpList(vIndex).EmployeeID;
exception
when NO_DATA_FOUND then
pEmpList(vIndex).LastName := 'F'||pEmpList(vIndex).EmployeeID;
end;
vIndex := pEmpList.Next(vIndex);
end loop;
end if;
end;
---------
procedure OutputRecs(pEmpList TEmpList) is
vIndex PLS_integer;
begin
if pEmpList is not null then
vIndex := pEmpList.First;
while vIndex is not null
loop
DBMS_OUTPUT.PUT_LINE ( 'pEmpList(' || vIndex ||') = '|| pEmpList(vIndex).EmployeeID||', '|| pEmpList(vIndex).LastName);
vIndex := pEmpList.Next(vIndex);
end loop;
end if;
end;
begin
vEmpList := TEmpList(EmpRec(100),
EmpRec( 34),
EmpRec(104),
EmpRec(110));
SearchRecs(vEmpList);
OutputRecs(vEmpList);
end;
/
Above program takes input value one at time.
However, i tried as below but unable to succeed.
i tried to give input from console at once like (100,34,104,100) in place of either hard coding the input (or) giving one input at time.
Snippet in DECLARE section:
declare
type TEmpRec is record (
EmployeeID EMPLOYEES.EMPLOYEE_ID%TYPE,
LastName EMPLOYEES.LAST_NAME%TYPE
);
type TEmpList is table of TEmpRec;
v_input TEmpList := TEmpList(&v_input); -- to read multiple input at once
vEmpList TEmpList;
In the final BEGIN section:
BEGIN
FOR j IN v_input.FIRST .. v_input.LAST LOOP
vEmpList := TEmpList(EmpRec(v_input(j).EmployeeID)); --to assign input values to vEmptList
SearchRecs(vEmpList);
OutputRecs(vEmpList);
end loop;
end;
/
Error in DECLARE section:
PLS-00306: wrong number or types of arguments in call to 'TEMPLIST'
Error in LAST BEGIN section:
PLS-00320: the declaration of the type of this expression is incomplete or malformed
As an example: at time, i am able to read multiple input values for same variable but i am unable to pass this as an input but unable to figure out how can make this as an input my main program.
DECLARE
TYPE t IS TABLE OF VARCHAR2(100);
ORDERS t := t(&ORDERS);
BEGIN
FOR j IN ORDERS.FIRST .. ORDERS.LAST LOOP
dbms_output.put_line(ORDERS(j));
END LOOP;
END;
/
Output:
PL/SQL procedure successfully completed.
Enter value for orders: 321,153,678
321
153
678
Thank You.
Since You have a collection of record variable, you need to pass employee_ids and employee last_names separately. How are you planning to pass them in a single shot?.
Here is a sample script which accomplishes something you want with 2 inputs for 3 collection elements.
First, create a collection TYPE and a PIPELINED function to convert comma separated values into Collections - f_convert2.
CREATE TYPE test_type AS TABLE OF VARCHAR2(100);
CREATE OR REPLACE FUNCTION f_convert2(p_list IN VARCHAR2)
RETURN test_type
PIPELINED
AS
l_string LONG := p_list || ',';
l_comma_index PLS_INTEGER;
l_index PLS_INTEGER := 1;
BEGIN
LOOP
l_comma_index := INSTR(l_string, ',', l_index);
EXIT WHEN l_comma_index = 0;
PIPE ROW ( SUBSTR(l_string, l_index, l_comma_index - l_index) );
l_index := l_comma_index + 1;
END LOOP;
RETURN;
END f_convert2;
/
Then in your anonymous blocks pass values for employee_ids and last_name separately.
SET SERVEROUTPUT ON
DECLARE
TYPE temprec IS RECORD ( employeeid employees.employee_id%TYPE,
lastname employees.last_name%TYPE );
TYPE templist IS
TABLE OF temprec;
vemplist templist;
v_no_of_rec NUMBER := 10;
v_empl_ids VARCHAR2(100) := '&empl_ids';
v_empl_lnames VARCHAR2(100) := '&empl_lnames';
BEGIN
SELECT employee_id,last_name
BULK COLLECT
INTO
vemplist
FROM
(
SELECT
ROWNUM rn,
column_value employee_id
FROM
TABLE ( f_convert2(v_empl_ids) )
) a
JOIN (
SELECT
ROWNUM rn,
column_value last_name
FROM
TABLE ( f_convert2(v_empl_lnames) )
) b ON a.rn = b.rn;
FOR i in 1..vemplist.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(vemplist(i).employeeid || ' ' ||vemplist(i).lastname);
END LOOP;
END;
/
Instead of simple JOIN above if you use OUTER JOIN ( FULL or LEFT ), you can handle missing values without writing logic to check each value.
The requirement is to check data consistency of a view. It's a bit complicated, so let's move step by step.
A table check_data_column has basically 5 imp. columns: ViewName, ColumnName, Mandatory, MaxLength, DataType.
It will contain Information about each field of a particular view. For example:
ViewName: Employee_V
ColumnName: EmployeeNo
Mandatory: 1 (True)
MaxLength: 10
DataType: Number
ViewName: Employee_V
ColumnName: EmployeeName
Mandatory: 1 (True)
MaxLength: 20
DataType: String
Now I have to write a function which takes all the entries from this check_data_column and check the data in the appropriate view for each mentioned column.
From the above example, it will check the data in the Employee_V.
Each entry in EmployeNo column should not be null, max length should be 10 and it should be a numeric value.
Similarly, each entry in EmployeeName column should not be null, max length should be 20 and it should be a string.
Number of views is unknown and no. of columns in each view is unknown.
To solve the above problem, I wrote the following code:
FUNCTION CheckData(viewname VARCHAR2)
RETURN VARCHAR2
Is
return_v VARCHAR2(1000);
query_v VARCHAR2(200);
column_c SYS_REFCURSOR;
column_v column_c%ROWTYPE;
CURSOR ddc_c IS
SELECT *
FROM check_data_column;
BEGIN
return_v := null;
FOR ddc_v IN ddc_c LOOP
query_v := 'SELECT' || ddc_v.column_name || 'FROM anc_sap.' || ddc_v.viewname;
OPEN column_c FOR query_v;
LOOP
FETCH column_c INTO column_v;
EXIT WHEN column_c%NOTFOUND;
IF LENGTH(column_v) > ddc_v.max_length THEN
return_v := 'Max. length exceeded';
END IF;
----Other validations (on mandatory and data type)
END LOOP;
CLOSE column_c;
END LOOP;
RETURN return_v;
END CheckData;
Problem:
The problem I am facing here is in declaring column_v variable for sys_refcursor column_c. Since at this point of time I am not able to think replacement for sys_refcursor, is there anything else I can do?
This function worked in simple tests. It checks nulls and length, you have to add rest of validations.
create or replace function CheckData(i_viewname VARCHAR2)
RETURN VARCHAR2
Is
query_v VARCHAR2(2000);
v_cnt number := 0;
CURSOR ddc_c IS
SELECT * FROM check_data_column where viewname = i_viewname;
BEGIN
FOR ddc_v IN ddc_c LOOP
-- check nulls
if ddc_v.mandatory = 1 then
query_v := 'select count(1) from '|| ddc_v.viewname
||' where '||ddc_v.columnname||' is null';
execute immediate query_v into v_cnt;
if v_cnt > 0 then
return 'null values for mandatory column '
||ddc_v.viewname ||'.'||ddc_v.columnname||' exists';
end if;
end if;
-- check column length
query_v := 'select count(1) from '|| ddc_v.viewname
||' where length('||ddc_v.columnname||') > '||ddc_v.maxlength;
execute immediate query_v into v_cnt;
if v_cnt > 0 then
return 'max length in column '||ddc_v.viewname
||'.'||ddc_v.columnname||' exceeded';
end if;
-- other validations
END LOOP;
RETURN 'OK';
END CheckData;