Dynamic PL SQL in where condition for different operands - sql

I have three tables, viz:
Mains_Control
Control_Mapping
Control_Details
along with several conditions and columns. But I am performing dynamic operations on the tables columns given below.
I am unable to write a dynamic SQL block in PL-SQL procedure for the below conditions. Could anyone please assist me on this particular scenario.
SELECT *
FROM CUR_ALL_CONTENTS
WHERE MAINS_CONTROL SWITCH = $SWITCH
AND ($ATTRIB_COLUMNS = $ATTRIB_VAL
OR $ATTRIB_COLUMNS = $ATTRIB_VAL)
The above business condition is this
SELECT *
FROM CUR_ALL_CONTENTS
WHERE MAINS_CONTROL_SWITCH = 'TYPE'
AND (SWITCH_MODE = 'THRUST' OR SWITCH_MODE_GEAR = 'SEC_GEAR')
Here SWITCH attribute value can change, ATTRIB_COLUMNS and ATTRIB_VAL can change.
Mains_Control table has the following columns
SWITCH ACTION_CODE RULE_MAP_1
TYPE ON R1
TYPE OFF R2
METHOD HOLD R3
METHOD TERM_IN R4
Control_Mapping table has the following columns
RULE_MAP_1 RULE_MAP_2
R1 M11
R2 M22
R3 M33
R4 M44
Control_Details table has the following columns
RULE_MAP_2 ATTRIB_COLUMNS OPERAND ATTRIB_VAL
M11 SWTICH_MODE = THRUST
M22 SWITCH_MODE_GEAR = SEC_GEAR
M33 HOLD_RELEASE <> END

Still not clear for me how you determine which condition shall be used and when. Let's give this example with hard-coded rule set:
DECLARE
sqlstr VARCHAR2(30000);
cur INTEGER;
res INTEGER;
refCur SYS_REFCURSOR;
val Mains_Control.SWITCH%TYPE;
CURSOR Conditions IS
SELECT *
FROM RULE_MAP_2 IN ('M11', 'M22');
BEGIN
cur := DBMS_SQL.OPEN_CURSOR;
sqlstr := 'SELECT * '||CHR(13);
sqlstr := sqlstr || 'FROM CUR_ALL_CONTENTS '||CHR(13);
sqlstr := sqlstr || 'WHERE MAINS_CONTROL_SWITCH = :switch AND (';
FOR aCond IN Conditions LOOP
sqlstr := sqlstr || aCond.ATTRIB_COLUMNS ||aCond.OPERAND||' :'||RULE_MAP_2 ||' OR '
END LOOP;
sqlstr := REGEXP_REPLACE(sqlstr, ' OR ', ')');
DBMS_OUTPUT.PUT_LINE(sqlStr); --> verify generated statement
DBMS_SQL.PARSE(cur, sqlStr, DBMS_SQL.NATIVE);
SELECT SWITCH
INTO val
FROM Mains_Control
WHERE RULE_MAP_1 = 'R1';
DBMS_SQL.BIND_VARIABLE(cur, ':switch', sw);
FOR aCond IN Conditions LOOP
DBMS_SQL.BIND_VARIABLE(cur, ':'||aCond.RULE_MAP_2, aCond.ATTRIB_VAL);
END LOOP;
res := DBMS_SQL.EXECUTE(cur);
refCur := DBMS_SQL.TO_REFCURSOR(cur);
FETCH refCur BULK COLLECT INTO ...;
END;
The code would be simpler without DBMS_SQL.BIND_VARIABLE, however using bind variables is the proper way of doing it.

Related

PLS-00103: Encountered the symbol "=" when expecting one of the following.... in PL/SQL script

create or replace function lstnation (listdisplay in varchar2)
return varchar2 is
nName varchar2 (1000) default null;
listD varchar2(1000) default null;
cursor display_nation
is
select nation.n_name
from nation
inner join region
on region.r_regionkey = nation.n_nationkey
where region.r_regionname = listdisplay;
BEGIN
open display_nation;
loop
fetch display_nation into nName;
exit when display_nation%notfound;
IF
listD := listD || RTRIM(nName)||' , ';
end loop;
close display_nation;
return listD;
end lstnation;
/
DECLARE
rKey region.r_regionkey%type;
rName region.r_name%type;
nList varchar2(1000);
cursor outer_block is
select region.r_regionkey, region.r_name, lstnation(region.r_name)
from region;
BEGIN
open outer_block;
loop
fetch outer_block into rKey, rName, nList;
exit when outer_block%notfound;
dbms.output.put_line(rkey || ' ' || RTRIM(rName) || ': '|| nList);
end loop;
close outer_block;
end;
/
I get two errors, how can I fix it
LINE/COL ERROR
19/12 PLS-00103: Encountered the symbol "=" when expecting one of the
following:
. ( * # % & = - + < / > at in is mod remainder not rem then
<an exponent (**)> <> or != or ~= >= <= <> and or like like2
like4 likec between || multiset member submultiset
20/2 PLS-00103: Encountered the symbol "END" when expecting one of the
following:
begin function pragma procedure subtype type
current cursor delete
exists prior
You can save some coding and efficiency by replacing the cursor loop with the listagg function
select listagg(rtrim(nation.n_name),',')
from nation
inner join region
on region.r_regionkey = nation.n_nationkey
where region.r_regionname = listdisplay;
So that will collate all the matching rows, and use whatever delimiter is passed in. One thing to be aware of, you have listD varchar2(1000) so as long as the results from the query are less than 1000, you are OK. If you expect a larger result set, you may need to increase or use a clob.
If for some reason, you still want to use the loop method, then you need to fix your IF statement:
loop
fetch display_nation into nName;
exit when display_nation%notfound;
IF <condition> THEN
listD := listD || RTRIM(nName)||' , ';
END IF;
end loop;

pl sql query takes more time to execute

I found this PL/SQL at my workplace and I couldn't find the reason why this script takes so much time to execute:
DECLARE
query VARCHAR(500);
ref_cur REFCURSOR;
product_listH VARCHAR(1000):='';
product_listA VARCHAR(1000):='';
product_listP VARCHAR(1000):='';
product VARCHAR(100):='';
begin
query := ' select hotelname
from sch1.resconfirmsv rr,
sch1.reshoteldetailssv hd,sch2.respkgconfirmsv r '||
' where rr.id = hd.resconfirmid and
hd.resconfirmid = r.hotelconfirmid and
r.id = ' || m_resconfirmid || '';
OPEN ref_cur FOR EXECUTE query;
LOOP
FETCH ref_cur INTO product;
IF NOT FOUND THEN
EXIT; -- exit loop
END IF;
product_listH := product_listH||''||trim(COALESCE(product,'-'))||',<br>';
END LOOP;
product_listH := rtrim(trim(product_listH),',<br>');
CLOSE ref_cur;
query := ' select distinct programname
from sch1.resconfirmsv rr,
sch3.resactivitysv a,
sch3.resprogramsv hx,
sch2.respkgconfirmsv r '||
' where rr.id = hx.resconfirmid and
hx.id=a.resprogramid and
hx.resconfirmid = r.activitiesconfirmid and
r.id = ' || m_resconfirmid || '';
OPEN ref_cur FOR EXECUTE query;
LOOP
FETCH ref_cur INTO product;
IF NOT FOUND THEN
EXIT; -- exit loop
END IF;
product_listA := product_listA||''||trim(COALESCE(product,'-'))||',<br>';
END LOOP;
product_listA := rtrim(trim(product_listA),',<br>');
CLOSE ref_cur;
product_listP := product_listH || ',<br>' || product_listA;
product_listP := rtrim(trim(product_listP),',<br>');
product_listP = ltrim(rtrim(product_listP,',<br>'),',<br>');
RETURN product_listP;
end;
without this script total run-time is 12.176 sec and with this script it takes up to 18.802 sec.means this gets at least 6 seconds to execute. All the needed columns are indexed. Anybody can tell me where the places need to be more optimize in this query?
Why declaring the cursor as a seperate varchar?
Instead i'd use the normal declaration of a cursor, it takes time to analyze the query to be executed by Oracle, so (between the declare- and Begin-labels of your current code:
cursor ref_cur as
select distinct programname
from sch1.resconfirmsv rr,
sch3.resactivitysv a,
sch3.resprogramsv hx,
sch2.respkgconfirmsv r '||
where rr.id = hx.resconfirmid and
hx.id=a.resprogramid and
hx.resconfirmid = r.activitiesconfirmid and
r.id = m_resconfirmid;
Now you can use
For x in ref_cur loop
The same thing for query#2.
Cheers

Oracle : how to fetch data from dynamic query?

I have a program to generate dynamic query string based on input. This query may select from any tables or joined tables in my DB, and the column names and number of columns are unknown.
Now with this query string as the only input, I want to fetch all data from the result and output them line by line, is there any way to do this ?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Thank Thinkjet for the reference. I have solved the problem, to help the others, here is the piece of code I used:
DECLARE
v_curid NUMBER;
v_desctab DBMS_SQL.DESC_TAB;
v_colcnt NUMBER;
v_name_var VARCHAR2(10000);
v_num_var NUMBER;
v_date_var DATE;
v_row_num NUMBER;
p_sql_stmt VARCHAR2(1000);
BEGIN
v_curid := DBMS_SQL.OPEN_CURSOR;
p_sql_stmt :='SELECT * FROM emp';
DBMS_SQL.PARSE(v_curid, p_sql_stmt, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS(v_curid, v_colcnt, v_desctab);
-- Define columns:
FOR i IN 1 .. v_colcnt LOOP
IF v_desctab(i).col_type = 2 THEN
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_num_var);
ELSIF v_desctab(i).col_type = 12 THEN
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_date_var);
ELSE
DBMS_SQL.DEFINE_COLUMN(v_curid, i, v_name_var, 50);
END IF;
END LOOP;
v_row_num := dbms_sql.execute(v_curid);
-- Fetch rows with DBMS_SQL package:
WHILE DBMS_SQL.FETCH_ROWS(v_curid) > 0 LOOP
FOR i IN 1 .. v_colcnt LOOP
IF (v_desctab(i).col_type = 1) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_name_var);
ELSIF (v_desctab(i).col_type = 2) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_num_var);
ELSIF (v_desctab(i).col_type = 12) THEN
DBMS_SQL.COLUMN_VALUE(v_curid, i, v_date_var);
END IF;
END LOOP;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(v_curid);
END;
/
You can do that with DBMS_SQL package.
Update
To get more detailed reference about DBMS_SQL go here.
If you are building your string within PL/SQL, you can run it with EXECUTE IMMEDIATE. <- link. Use the BULK COLLECT INTO and output the collection.
<PRE>
DECLARE
RUN_S CLOB;
IGNORE NUMBER;
SOURCE_CURSOR NUMBER;
PWFIELD_COUNT NUMBER DEFAULT 0;
L_DESCTBL DBMS_SQL.DESC_TAB2;
Z_NUMBER NUMBER;
BEGIN
RUN_S := ' SELECT 1 AS VAL1,
2 AS VAL2,
CURSOR (SELECT 11 AS VAL11,
12 AS VAL12
FROM DUAL) AS CUR1,
CURSOR (SELECT 11 AS VAL11,
12 AS VAL12
FROM DUAL) AS CUR2
FROM DUAL';
SOURCE_CURSOR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(SOURCE_CURSOR, RUN_S, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS2(SOURCE_CURSOR, PWFIELD_COUNT, L_DESCTBL); -- get record structure
FOR I IN 1 .. PWFIELD_COUNT LOOP
DBMS_OUTPUT.PUT_LINE('Col ' || I || ' Type:' || L_DESCTBL(I).COL_TYPE);
IF L_DESCTBL(I).COL_TYPE = 2 THEN
DBMS_SQL.DEFINE_COLUMN(SOURCE_CURSOR, I, Z_NUMBER);
END IF;
NULL;
END LOOP;
IGNORE := DBMS_SQL.EXECUTE(SOURCE_CURSOR);
LOOP
IF DBMS_SQL.FETCH_ROWS(SOURCE_CURSOR) > 0 THEN
FOR I IN 1 .. PWFIELD_COUNT LOOP
IF L_DESCTBL(I).COL_TYPE IN (2) THEN
DBMS_SQL.COLUMN_VALUE(SOURCE_CURSOR, I, Z_NUMBER);
DBMS_OUTPUT.PUT_LINE('Col ' || I || ' Value:' || Z_NUMBER);
END IF;
END LOOP;
ELSE
EXIT;
END IF;
END LOOP;
END;
</PRE>

Oracle RLS/VPD with for loop in policy function

This is a followup to an older question about Oracle's row level security feature originally discussed here
I needed to modify the because the original code would return more than one results if the user was attached to multiple projects. So now I need to have multiple where conditions (ie where project = project_1 or project = project_2) passed to the security policy for this to work. To do this I tried modifying the code by using a for loop and it is not working...
--create function
create or replace function
table_access_policy
(obj_schema varchar2, obj_name varchar2) return varchar2
is
v_project_temp varchar2(9000);
begin
v_project_temp:= 'declare v_project varchar2(9000);
begin
v_project:= ''project = '';
for c in (select admin.access_list.project from admin.access_list where upper(admin.access_list.user_id) = SYS_CONTEXT (''USERENV'', ''SESSION_USER''))
loop
v_project := v_project || c.project_sn || '' or project = '' ;
end loop;
v_project := rtrim(v_project, '' or project = '');
end;';
return v_project_temp;
end;
The function saves/runs without any errors, but the policy itself throws an error when it's called. Is there a better way to do this?
Instead of putting the PL/SQL in a string you should run it and build up the v_project string to return. Such as:
--create function
create or replace function
table_access_policy
(obj_schema varchar2, obj_name varchar2) return varchar2
is
v_project varchar2(9000);
begin
v_project:= 'project = ';
for c in (select admin.access_list.project from admin.access_list where upper(admin.access_list.user_id) = SYS_CONTEXT ('USERENV', 'SESSION_USER'))
loop
v_project := v_project ||''''|| c.project_sn ||''''|||| ' or project = ' ;
end loop;
v_project := rtrim(v_project, ' or project = ');
return v_project;
end;
Ultimately the value that appears in v_project will go straight after a where in an SQL statement such as:
select * from data;
will become
select * from date where <v_project>;
So only something that follows a where should go in v_project.

SQL stmt concatenate(sp*)

I’m dong some SQL work this week and I normally write Java.
I need to add some if's into my SQL like so, but don’t want to do any string concatenation.
This should be an easy one: I just don’t know the SQL syntax
I want to make all those "AND" statements "if" conditions for params I’m passing in
Would I have to do something like this:
IF p_sac IS NOT NULL
THEN
stmt := stmt || ' AND nsns.sac = ''' || p_sac || '''';
END IF;
IF p_value1 IS NOT NULL
THEN
stmt := stmt || ' AND UPPER(value1s.value1) LIKE ''' || UPPER(p_value1) || ''' ';
END IF;
Or is there an alternative to this above?
Basically I've got this:
FUNCTION summarize_item_search_data (p_obj_code IN VARCHAR2, p_value1 IN VARCHAR2,
p_sac IN NUMBER, p_job_type_id IN NUMBER,
p_value4 IN NUMBER)
RETURN sys_refcurvalue2
IS
result_cur sys_refcurvalue2;
BEGIN
OPEN result_cur FOR
SELECT DISTINCT jp.id, jp.row_top.mwslin AS mwslin, jp.obj_code, jp.jobload_year, jp.row_top.fiscal_year AS fiscal_year,
nsns.sac, value1s.value1, nsns.nsn,
DECODE( jp.row_top.nsn_id, NULL, jp.row_top.nomenclature ,nsns.nomenclature) AS nomenclature, jp.row_top.value4 AS value4
FROM scabs sch, jobs JP, master_nsn nsns, master_value1 value1s, TABLE(value1s.group_id) (+) ntab,
groups pgds
WHERE jp.row_top.nsn_id = nsns.id(+) AND nsns.value1_id = value1s.id(+)
-- stmt := stmt || ' AND ''' || p_year || ''' = ntab.fiscal_year(+)';
AND ntab.group_id = pgds.id(+)
AND nsns.sac = p_sac
AND UPPER(value1s.value1) LIKE UPPER(p_value1)
AND UPPER(jp.obj_code) = UPPER(p_obj_code)
AND jp.row_top.value4 <= p_value4
AND jp.row_top.job_type_id =p_job_type_id
RETURN result_cur;
END summarize_item_search_data;
Just put it all into your WHERE clause as conditions:
WHERE
(p_sac IS NULL OR nsns.sac = p_sac) AND
....
If performance suffers greatly then you might want to look into using dynamic SQL, but I would start with the approach above.