Error(30,11): PLS-00103: Encountered the symbol UPDATE error - oracle

While creating below SP I am getting the error as
Error(30,11): PLS-00103: Encountered the symbol "UPDATE" when expecting one of the following: ( - + case mod new not null select with continue avg count current exists max min prior sql stddev sum variance execute forall merge time timestamp interval date pipe
Here is my SP
Procedure Update_Link_Details (
P_UDLINKID NVARCHAR2,
P_FOLDERSTRUCTURE NVARCHAR2,
TBL_UPD OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN TBL_UPD FOR
Update Tb_Link_Print
set status='Start'
where LINKPRINTID= P_UDLINKID
and upper(FOLDERSTRUCTURE) LIKE '%' || upper(P_FOLDERSTRUCTURE) || %';
NULL;
END Update_Link_Details;

"I just want to update the table with that status ...Also I want to return the updated record"
That's two things, so it's two statements:
Procedure Update_Link_Details (
P_UDLINKID NVARCHAR2,
P_FOLDERSTRUCTURE NVARCHAR2,
TBL_UPD OUT SYS_REFCURSOR
)
AS
BEGIN
Update Tb_Link_Print
set status='Start'
where LINKPRINTID= P_UDLINKID
and upper(FOLDERSTRUCTURE) LIKE '%' || upper(P_FOLDERSTRUCTURE) || '%';
OPEN TBL_UPD FOR
select * from Tb_Link_Print
where LINKPRINTID= P_UDLINKID
and upper(FOLDERSTRUCTURE) LIKE '%' || upper(P_FOLDERSTRUCTURE) || '%';
END Update_Link_Details;
This is a trifle clunky. Here is a version which engineers away the duplication:
Procedure Update_Link_Details (
P_UDLINKID NVARCHAR2,
P_FOLDERSTRUCTURE NVARCHAR2,
TBL_UPD OUT SYS_REFCURSOR
)
AS
upd_rowids sys.dbms_debug.vc2coll;
BEGIN
Update Tb_Link_Print
set status='Start'
where LINKPRINTID= P_UDLINKID
and upper(FOLDERSTRUCTURE) LIKE '%' || upper(P_FOLDERSTRUCTURE) || '%'
returning rowidtochar(rowid) bulk collect into upd_rowids;
OPEN TBL_UPD FOR
select p.*
from Tb_Link_Print p
join table(upd_rowids) u
on p.rowid = chartorowid(u.column_value)
;
END Update_Link_Details;
It uses the RETURNING clause to capture the ROWIDs of the affected rows, then opens the Ref Cursor using those ROWIDs to restrict the result set.

Related

Executing function embedded SELECT sql query to increase performance

I have the following SQL query that will return the name of a column in a specific table. Let's say it return 'USER_PK' as column name when it runs.
the query:
SELECT max(COLUMN_NAME)
FROM ALL_TAB_COLUMNS
WHERE OWNER= 'DW_01'
AND table_name='D_O_USERS'
AND COLUMN_NAME<>'USER_PK';
Now I would like to run the above query as part of a function but instead of running it and storing the value it returns in a variable (using INTO or attribution like initial_sql: = '...', followed by exec ) I would need to have it run inside one line of code as below (see part in bold)... So far I have been unsuccessful as it is interpreted as a string when using quotes ...
CREATE OR REPLACE function DW_01.EXECUTE_AUTO (db_schema IN VARCHAR2, db_table IN VARCHAR2, pk_name IN VARCHAR2, id_pk IN INTEGER) RETURN VARCHAR2
IS
result VARCHAR2(4000);
begin
EXECUTE IMMEDIATE 'select STANDARD_HASH( '|| **SELECT max( COLUMN_NAME) FROM ALL_TAB_COLUMNS WHERE OWNER='' || db_schema || '' AND table_name=''||db_table ||'' AND COLUMN_NAME<>'' ||pk_name ||'** ,''SHA512'' ) from '||db_table||' where '|| pk_name ||'='||id_pk into RESULT ;
return result;
end;
Many thanks in advance for your thoughts!
You need to amend you r code like below -
CREATE OR REPLACE function DW_01.EXECUTE_AUTO (db_schema IN VARCHAR2,
db_table IN VARCHAR2,
pk_name IN VARCHAR2,
id_pk IN INTEGER) RETURN VARCHAR2
IS
result VARCHAR2(4000);
begin
EXECUTE IMMEDIATE 'select STANDARD_HASH( ' || pk_name || ',256 )
from '||db_table||' where '|| pk_name ||'='||id_pk into RESULT;
return result;
end;
/
There are only a few ways to run dynamic SQL in SQL, and they're neither pretty nor fast. The function below uses DBMS_XMLGEN.GETXML to dynamically run a SQL statement.
create or replace function execute_auto(db_schema in varchar2, db_table in varchar2, pk_name in varchar2, id_pk in integer) return varchar2
is
v_column_name varchar2(128);
v_result varchar2(4000);
begin
select standard_hash(to_number(extractValue(xml_results, '/ROWSET/ROW/' || max_column)), 'SHA512') hash_value
into v_result
from
(
--Create a single XML file with the ROWIDs that match the condition.
select max(column_name) max_column, xmltype(dbms_xmlgen.getxml
('
select '||max(column_name)||'
from '||db_schema||'.'||db_table||'
where id = '||id_pk
)) xml_results
from all_tab_columns
where owner = db_schema
and table_name = db_table
and column_name <> pk_name
);
return v_result;
end;
/
For example, let's create this sample table with 100,000 rows:
--drop table test1;
create table test1(id number, a number, b number, constraint pk_test1 primary key(id));
insert into test1
select level, level, level from dual connect by level <= 100000;
commit;
This shows how to use the function;
select execute_auto(user, 'TEST1', 'ID', id) hash
from test1 where id = 1;
HASH
----
A36753F534728ED84A463ECB13750B8E920A7E4D90244258DE77D9800A0F3DAF8CBAD49602E960A2355933C689A23C30377CE10FC4B8E1F197739FF86C791022
In addition to problems with type conversion and SQL injection, the performance is terrible. Selecting all 100,000 rows this way takes 200 seconds on my machine.
select sum(length(execute_auto(user, 'TEST1', 'ID', id))) from test1;
Normally, running everything in a single SELECT statement is a good way to improve performance. But this extreme type of dynamic SQL will never run fast. You probably want to rethink your approach. Instead of trying to optimize the SQL inside a function that is run one-row-at-a-time, try to change the process to process once-per-table

The declaration of the type of this expression is incomplete or malformed

I am working on the following stored procedure.
My_Procedure
create or replace PROCEDURE My_Procedure(
v_ID IN NUMBER DEFAULT 0 ,
v_DETAIL_ID IN NUMBER DEFAULT 0 ,
v_HEADER_ID IN NUMBER DEFAULT 0 ,
v_JOB_DETAIL_ID IN NUMBER DEFAULT 0 ,
v_TABLE_NAME IN VARCHAR2,
v_ESCALATION_TYPE IN VARCHAR2 DEFAULT NULL ,
v_COLUMN_LIST IN NVARCHAR2 DEFAULT NULL ,
cv_1 OUT SYS_REFCURSOR,
cv_2 OUT SYS_REFCURSOR ) AS BEGIN NUll; END;
Stored Procedure in which i am executing
DECLARE
v_ident VARCHAR(30000) := NULL;
v_columns
varchar(30000) := NULL;
v_job_header_id number := 0;
BEGIN
SELECT
(
SELECT
rtrim(XMLAGG(xmlelement(e, c.column_name, ',').extract('//text()')
ORDER BY
c.column_id
).getclobval(), ',')
FROM
table1 c
WHERE
c.owner = t.owner
AND c.table_name = t.table_name
)
INTO v_columns
FROM
all_tables t
WHERE
t.table_name = 'TABLE_NAME';
v_ident := 'INSERT INTO TABLE_NAME ('
|| v_columns
|| ') EXEC My_Procedure(0,0,0,''tableName'',null,v_COLUMNS)';
dbms_output.put_line(v_ident);
EXECUTE IMMEDIATE ( v_ident, 'v_JOB_HEADER_ID INT OUT,v_COLUMNS VARCHAR(30000) OUT', v_job_header_id, v_columns );
END;
In this stored procedure i am taking the table columns name and inserting the values according to the columns.
I am getting the error on the execute immediate line. as the declaration of the type of this expression is incomplete or malformed.

Cursor Select statement Where condition changes based on condition

I am using a Cursor in a procedure, here I am not using Bulk collect since I have lot of calculations on the records fetched from Cursor.
In Cursor's Select statement Where clause changes based on condition , I am trying to use the code as below but it is giving me error:
Error(19,12): PLS-00103: Encountered the symbol "c_recs" when expecting one of the following: := . ( # % ;
create or replace PROCEDURE "test"
(fromdate_in IN varchar2,
todate_in IN varchar2,
atype_in IN number
)
is
begin
if atype_in = 01 then
cursor c_recs IS SELECT cname FROM A_AUD AA WHERE AA.atime BETWEEN to_date( '' || fromdate_in || '' ,'DD/MM/RRRR')
AND to_date('' || todate_in || '','DD/MM/RRRR') AND AA.CTYPE IN ('RAlert');
elsif atype_in = 02
cursor c_auditrecs IS SELECT cname FROM A_AUD AA WHERE AA.atime BETWEEN to_date( '' || fromdate_in || '' ,'DD/MM/RRRR')
AND to_date('' || todate_in || '','DD/MM/RRRR') AND AA.CTYPE IN ('DAlert');
end if;
end
begin
--more logic
FOR rec IN c_recs LOOP
---calculations
END LOOP;
END test;
I do not want to use SYS_REFCURSOR as from net , i read cursors are slight better than ref cursors.
A better and efficient option would be to do it in a single statement without any CURSORs. But, it does depend on what you want to do.If you have to do dmls based on the cursor records, preferably do it in a single statement.
If indeed you want to process something in a loop, use an implicit cursor loop which is equivalent ( and sometimes better performant ) to explicit cursors.
The select query can also be simplified with conditional logic rather than IF/ELSE.
CREATE OR REPLACE PROCEDURE "test" (
fromdate_in IN VARCHAR2,
todate_in IN VARCHAR2,
atype_in
in NUMBER
) is begin
for cur in (
SELECT cname
FROM a_aud aa
WHERE aa.time BETWEEN TO_DATE(fromdate_in,'dd/mm/rrrr')
AND TO_DATE(todate_in,'dd/mm/rrrr') AND (
(
atype_in = '01' AND aa.ctype = 'RAlert'
) OR (
atype_in = '02' AND aa.ctype = 'DAlert'
)
)
) loop
---calculations
do_something_with(cur.cname)
end loop;
end;
/
I would also suggest you to have the type of arguments as dates and pass the variables directly from the calling block rather than converting them inside the sql/cursor.This will avoid TO_DATE conversion.
CASE in cursor declaration solves that problem:
DECLARE
CURSOR c_recs
IS
SELECT cname
FROM a_aud aa
WHERE aa.time BETWEEN TO_DATE (fromdate_in, 'dd/mm/rrrr')
AND TO_DATE (todate_in, 'dd/mm/rrrr')
AND aa.ctype =
CASE
WHEN atype_in = '01' THEN 'RAlert'
WHEN atype_in = '02' THEN 'DAlert'
END;
BEGIN
FOR rec IN c_recs
LOOP
NULL;
END LOOP;
END;
I can't comment your refcursor statement.

Create dynamic query for search using Oracle

I need to search in my Oracle Database using filters based on the dropdown's. So there are total 3 dropdowns and one textbox for searching parameters. Now what I want is.
If User Selects APPLICATION from the first dropdown and Project Name from second dropdown and LIKE condition from third dropdown. A dynamic query should be created and based on that it should filter the record from the database. For that What I tried is below
PROCEDURE FILTER_SEARCH_DATA
(
P_SEARCH_TYPE IN NVARCHAR2,
P_PARAM_TYPE IN NVARCHAR2,
P_OPERATOR IN NVARCHAR2,
P_TEXTVAL IN NVARCHAR2,
P_RETURN OUT SYS_REFCURSOR
)
AS
STR NVARCHAR2(400):='';
STROP NVARCHAR2(400):='';
STREX NVARCHAR2(4000):='';
BEGIN
IF(P_OPERATOR ='LIKE') THEN
BEGIN
STR:=STR || ' WHERE AM.APPLICATIONNAME ' || P_OPERATOR || '''%' || P_TEXTVAL ||'%''';
END;
ELSE
BEGIN
STR:=STR || 'WHERE AM.APPLICATIONNAME ' || P_OPERATOR || P_TEXTVAL ;
END;
END IF;
DBMS_OUTPUT.PUT_LINE('STR'|| STR);
IF P_SEARCH_TYPE = 'APPLICATION' THEN
DBMS_OUTPUT.PUT_LINE('START APPLICATION');
STREX:='OPEN P_RETURN FOR SELECT AM.APPLICATIONNAME, AM.PROJECTNO, AM.VSS_FOLDER_LOC FROM APPLICATION_MASTER AM
INNER JOIN APPLICATION_DETAILS AD
ON AM.APP_MST_ID = AD.APP_MST_ID' || str ||';';
DBMS_OUTPUT.PUT_LINE('STREX '|| STREX);
END IF;
END FILTER_SEARCH_DATA;
But it is not working accordingly for what I want.
Let me know if you have any issues related to this.
Your problem is here:
STREX:='OPEN P_RETURN FOR SELECT AM.APPLICATIONNAME, AM.PROJECTNO, AM.VSS_FOLDER_LOC FROM APPLICATION_MASTER AM
INNER JOIN APPLICATION_DETAILS AD
ON AM.APP_MST_ID = AD.APP_MST_ID' || str ||';';
DBMS_OUTPUT.PUT_LINE('STREX '|| STREX);
This code just outputs a string into the standard output. You need to use this:
PROCEDURE FILTER_SEARCH_DATA
(P_SEARCH_TYPE IN NVARCHAR2,
P_PARAM_TYPE IN NVARCHAR2,
P_OPERATOR IN NVARCHAR2,
P_TEXTVAL IN NVARCHAR2,
P_RETURN OUT SYS_REFCURSOR ) AS
STR NVARCHAR2(400):='';
STROP NVARCHAR2(400):='';
STREX VARCHAR2(4000):='';
val NVARCHAR2(4000);
BEGIN
IF (P_OPERATOR ='LIKE') THEN
val := '%' || P_TEXTVAL ||'%';
ELSE
val := P_TEXTVAL;
END IF;
DBMS_OUTPUT.PUT_LINE('STR'|| STR);
IF P_SEARCH_TYPE = 'APPLICATION' THEN
DBMS_OUTPUT.PUT_LINE('START APPLICATION');
STREX:='SELECT AM.APPLICATIONNAME, AM.PROJECTNO, AM.VSS_FOLDER_LOC
FROM APPLICATION_MASTER AM
INNER JOIN APPLICATION_DETAILS AD
ON AM.APP_MST_ID = AD.APP_MST_ID
WHERE AM.APPLICATIONNAME ' || P_OPERATOR || ' :PARAM';
DBMS_OUTPUT.PUT_LINE('STREX '|| STREX);
open P_RETURN for STREX using val;
END IF;
END FILTER_SEARCH_DATA;
Also, I added using of parameters (:PARAM in the SQL code), it helps with performance and allows to avoid SQL injections.
Another one important thing: in your code you open the cursor only if P_SEARCH_TYPE equals to 'APPLICATION'. You need to do something in other cases, or your procedure will return closed cursor.
UPD
STREX should be VARCHAR2, not NVARCHAR2.

How to use variables in Oracle PL/SQL Function

I'm sorry upfront because this question seems to easy.
I have this function:
CREATE OR REPLACE FUNCTION Costs_MK (VIEWNAME IN VARCHAR2 , WHERE_CLAUSE IN VARCHAR2)
RETURN VARCHAR2
IS
v_Costs VARCHAR2 (500);
BEGIN
Select Listagg(Costs, ';' ) WITHIN GROUP (ORDER BY Costs)
into v_Costs
from (select distinct (Costs)
from VIEWNAME
where WHERE_CLAUSE);
RETURN v_Costs;
END Costs_MK;
However I get the Error-Message:
Error(13,30): PL/SQL: ORA-00920: invalid relational operator
I even can't compile it. If I use the exact values for Viewname and Where_clause I get the desired result.
What am I doing wrong?
/edit: Line 13 is
from VIEWNAME
/edit #2:
Thanks guys. You helped me a lot. I didn't thought about dynamic sql in the first step, so thanks for the refresher ;).
I suggest you to add EXCEPTION BLOCK along with EXECUTE IMMEDIATE
I have created a PROCEDURE you can similary create FUNCTION
CREATE OR REPLACE procedure Costs_PK(VIEWNAME IN VARCHAR2 , WHERE_CLAUSE IN VARCHAR2 )
AS
v_Costs VARCHAR2 (500);
sql_stmnt varchar2(2000);
BEGIN
sql_stmnt := 'Select Listagg(Cost, '';'' ) WITHIN GROUP (ORDER BY Cost) from (select distinct (Cost) from ' || VIEWNAME || ' where ' || WHERE_CLAUSE || ' ) ';
--sql_stmnt := 'Select Listagg(Cost, '';'' ) WITHIN GROUP (ORDER BY Cost) from (select distinct (Cost) from cost_tab where cost >=123 ) ';
EXECUTE IMMEDIATE sql_stmnt INTO v_Costs ;
dbms_output.put_line ('query works -- ' || v_costs);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('input :' || VIEWNAME || ' and ' || WHERE_CLAUSE );
dbms_output.put_line (sql_stmnt );
DBMS_OUTPUT.PUT_LINE ('ERROR MESSAGE : ' || sqlCODE || ' ' || SQLERRM );
END;
begin
Costs_PK('cost_tab','cost >= 123');
end;
NOTE: code has been Tested
output:
query works -- 123;456
This is one of the areas in PL/SQL where the most straightforward static SQL solution requires code duplication as there is no way to parametrize the table name in a query. Personally I usually favor duplicate code of static SQL over the increased complexity of dynamic SQL as I like PL/SQL compiler to check my SQL compile time. YMMV.
You don't tell us what kind of where statements the different views are having. In the example below I assume there is 1:1 relation between the view and where parameter(s) so I can easily build static SQL.
create or replace view foo_v (foo_id, cost) as
select level, level*10 from dual connect by level < 10
;
create or replace view bar_v (bar_id, cost) as
select level, level*100 from dual connect by level < 10
;
create or replace function cost_mk(
p_view in varchar2
,p_foo_id in number default null
,p_bar_id in number default null
) return varchar2 is
v_cost varchar2(32767);
begin
case lower(p_view)
when 'foo_v' then
select listagg(cost, ';' ) within group (order by cost)
into v_cost
from (select distinct cost
from foo_v
where foo_id < p_foo_id);
when 'bar_v' then
select listagg(cost, ';' ) within group (order by cost)
into v_cost
from (select distinct cost
from bar_v
where bar_id < p_bar_id);
end case;
return v_cost;
end;
/
show errors
Usage example
select cost_mk(p_view => 'foo_v', p_foo_id => 5) from dual;
select cost_mk(p_view => 'bar_v', p_bar_id => 5) from dual;
You would want to use EXECUTE IMMEDIATE as was hinted by the comments to your question, define a new variable sqlQuery VARCHAR2(200); or similar and rework your sql similar to the following:
sqlQuery := 'Select Listagg(Costs, '';'' ) WITHIN GROUP (ORDER BY Costs) ';
sqlQuery := sqlQuery || 'from (select distinct (Costs) from :1 where :2)';
EXECUTE IMMEDIATE sqlQuery INTO v_Costs USING VIEWNAME, WHERE_CLAUSE;

Resources