I tried to create an Oracle function where table name, column name and where string are dynamic parameters:
CREATE OR REPLACE FUNCTION MYSCHEMA.myFunctionName(
tableName in nvarchar2,
columnName in nvarchar2,
whereStr in nvarchar2)
RETURN nvarchar2
IS nActive nvarchar2(2000);
BEGIN
declare
querystr nvarchar2(2000) ;
result nvarchar2(2000);
begin
querystr :='
select listagg('+columnName+','+','+') within group (order by '+columnName+')
from '+tableName+' where 1 = 1 '+whereStr+';';
EXECUTE IMMEDIATE querystr
INTO result;
nActive := result;
RETURN ( nActive );
end;
END ;
/
But it gives me error "Warning: compiled but with compilation errors".
What am I doing wrong?
For concatenate strings in Oracle use || not +
You don't need ; at the end of execute immediate query string
You need to escape ' using ''.
... as #Aleksej said
Execute immediate need query string in CHAR or VARCHAR2;
listagg return either raw or VARCHAR2
CREATE OR REPLACE FUNCTION MYSCHEMA.myFunctionName(
tableName in varchar2,
columnName in varchar2,
whereStr in varchar2)
RETURN varchar2
BEGIN
declare
querystr varchar2(2000) ;
result varchar2(2000);
begin
querystr :='
select listagg('|| columnName || ', '','') within group (order by ' ||columnName ||')
from ' || tableName || ' where 1 = 1 ' || whereStr;
EXECUTE IMMEDIATE querystr INTO result;
return result;
end;
END ;
/
You need to change the type of your variables, considering that
"The return data type is RAW if the measure column is RAW; otherwise the return value is VARCHAR2" (documentation)
EXECUTE needs a VARCHAR2: "It must be of type CHAR or VARCHAR2, not NCHAR or NVARCHAR2"
So:
declare
querystr varchar2(2000) ;
result VARCHAR2(32767);
begin
...
Related
I am building a function on PL/SQL using Oracle 11g.
I am trying to use a table variable within an EXECUTE IMMEDIATE statement, but it is not working, as you can see:
ERROR at line 1:
ORA-00904: "CENTER_OBJECTS": invalid identifier
ORA-06512: at "HIGIIA.KNN_JOIN", line 18
The code I am using is...
First, the type definitions
CREATE TYPE join_t IS OBJECT (
inn char(40),
out char(40)
);
/
CREATE TYPE join_jt IS TABLE OF join_t;
/
CREATE TYPE blob_t IS OBJECT (
id CHAR(40),
fv BLOB
);
/
CREATE TYPE blob_tt IS TABLE OF blob_t;
/
The function is:
create or replace FUNCTION knn_join (tab_inn IN varchar2, tab_out IN varchar2, blob_col1 IN varchar2, blob_col2 IN varchar2, dist_alg in VARCHAR2, kv in NUMBER ) RETURN join_jt
IS
var_fv BLOB;
var_id CHAR(40);
center_objects blob_tt := blob_tt();
retval join_jt := join_jt ();
join_table join_jt := join_jt();
sql_stmt1 varchar2(400);
sql_stmt2 varchar2(400);
BEGIN
sql_stmt1 := 'SELECT blob_t(ROWIDTOCHAR(rowid),' || blob_col1 || ') FROM ' || tab_out;
sql_stmt2 := 'SELECT join_t(ROWIDTOCHAR(r.rowid), center_objects(idx).id) FROM ' || tab_inn || ' r WHERE ' || dist_alg || '_knn(r.' || blob_col2 || ', center_objects(idx).' || blob_col1 || ')<=' || kv;
dbms_output.put_line(sql_stmt2);
EXECUTE IMMEDIATE sql_stmt1 BULK COLLECT INTO center_objects;
for idx in center_objects.first()..center_objects.last()
loop
--SELECT join_t(ROWIDTOCHAR(r.rowid), center_objects(idx).id) BULK COLLECT INTO join_table FROM londonfv r WHERE manhattan_knn(r.fv, center_objects(idx).fv) <=5;
EXECUTE IMMEDIATE sql_stmt2 BULK COLLECT INTO join_table;
for idx2 in join_table.first()..join_table.last()
loop
retval.extend();
retval(retval.count()) := join_table(idx2);
end loop;
end loop;
RETURN retval;
END;
/
To run the function:
select * from TABLE(knn_join('london','cophirfv','fv','fv','manhattan',5));
I am trying to use run the statement 'SELECT join_t(ROWIDTOCHAR(r.rowid), center_objects(idx).id) BULK COLLECT INTO join_table FROM london r WHERE manhattan_knn(r.fv, center_objects(idx).fv) <=5' using the EXECUTE IMMEDIATE, but it does not work because I am using a variable in it.
Can someone give me a hand on it?
Thanks in advance!
You can't refer to a local PL/SQL variable inside a dynamic SQL statement, because it is out of scope within the SQL context used by the dynamic call. You could replace your first call:
SELECT join_t(ROWIDTOCHAR(r.rowid), center_objects(idx).id) FROM ' ...
with a bind variable:
SELECT join_t(ROWIDTOCHAR(r.rowid), :id FROM ' ...
EXECUTE IMMEDIATE ... USING center_objects(idx).id ...
but you can't do what when the object attribute is variable too:
... ', center_objects(idx).' || blob_col1 || ')<='...
although - at least in the example you've shown - the only object attribute name available is fv, regardless of the table column names passed in to the function - so that could be hard-coded; and thus a bind variable could be used:
... ', :fv)<='...
EXECUTE IMMEDIATE ... USING center_objects(idx).id, center_objects(idx).fv ...
and the kv value should also be a bind variable, so you'd end up with:
create or replace FUNCTION knn_join (tab_inn IN varchar2, tab_out IN varchar2,
blob_col1 IN varchar2, blob_col2 IN varchar2, dist_alg in VARCHAR2, kv in NUMBER )
RETURN join_jt
IS
center_objects blob_tt := blob_tt();
retval join_jt := join_jt ();
join_table join_jt := join_jt();
sql_stmt1 varchar2(400);
sql_stmt2 varchar2(400);
BEGIN
sql_stmt1 := 'SELECT blob_t(ROWIDTOCHAR(rowid),' || blob_col1 || ') FROM ' || tab_out;
sql_stmt2 := 'SELECT join_t(ROWIDTOCHAR(r.rowid), :id) FROM ' || tab_inn || ' r WHERE '
|| dist_alg || '_knn(r.' || blob_col2 || ', :fv)<= :kv';
dbms_output.put_line(sql_stmt1);
dbms_output.put_line(sql_stmt2);
EXECUTE IMMEDIATE sql_stmt1 BULK COLLECT INTO center_objects;
for idx in center_objects.first()..center_objects.last()
loop
EXECUTE IMMEDIATE sql_stmt2 BULK COLLECT INTO join_table
USING center_objects(idx).id, center_objects(idx).fv, kv;
for idx2 in join_table.first()..join_table.last()
loop
retval.extend();
retval(retval.count()) := join_table(idx2);
end loop;
end loop;
RETURN retval;
END;
/
As far as I can tell you could still do the join within the dynamic SQL statement, and eliminate the loops and the need for the intermediate center_objects and join_table collections:
create or replace FUNCTION knn_join (tab_inn IN varchar2, tab_out IN varchar2,
blob_col1 IN varchar2, blob_col2 IN varchar2, dist_alg in VARCHAR2, kv in NUMBER )
RETURN join_jt
IS
retval join_jt;
sql_stmt varchar2(400);
BEGIN
sql_stmt := 'SELECT join_t(ROWIDTOCHAR(tinn.rowid), ROWIDTOCHAR(tout.rowid))'
|| ' FROM ' || tab_inn || ' tinn JOIN ' || tab_out || ' tout'
|| ' ON ' || dist_alg || '_knn(tinn.fv, tout.fv) <= :kv';
dbms_output.put_line(sql_stmt);
EXECUTE IMMEDIATE sql_stmt BULK COLLECT INTO retval USING kv;
RETURN retval;
END;
/
When you call it as you've shown:
select * from TABLE(knn_join('london','cophirfv','fv','fv','manhattan',5));
that's the equivalent of the hard-coded:
SELECT join_t(ROWIDTOCHAR(tinn.rowid), ROWIDTOCHAR(tout.rowid))
FROM london tinn
JOIN cophirfv tout
ON manhattan_knn(tinn.fv, tout.fv) <= 5
... so I guess you can verify whether that hard-coded version gives you the results you expect first. (Adding sample data and expected results to the question would have helped, of course).
That join condition may be expensive, depending on what the function is doing, how may rows are in each table (as every row in each table has to be compared with every row in the other), whether you actually have other filters, etc. The loop version would be even worse though. Without more information there isn't much to be done about that anyway.
As an aside, using varchar2 instead of char for the object attributes would be more normal; that's also the data type returned by the rowidtochar() function.
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’m write one function funGetAccount’ inOracle Pl-Sql` passing parameter ProdCd but query has consider its statement how it solved I’m not understand my req is same query pls don’t change the query design.
Function funGetAccount (ProdCd in Number)
Return Varchar2 Is
varResult Varchar2(2000);
Begin
varResult := ' Select AC_NO, NAME From ('
|| ' Select AC_NO, NAME'
|| ' From Tab1 Where PRODUCT_CD = ProdCd)';
varResult := varResult;
Return varResult;
End funGetAccount;
This will return AC_NO and NAME concatenated by space. You cannot return 2 values from it.
Also make sure that your query is returning only 1 row for the given prodcd else it return only first row.
create or replace function funGetAccount (ProdCd in Number)
Return Varchar2 Is
varResult Varchar2(2000);
Begin
Select AC_NO||' '||NAME
into varResult
From Tab1 Where PRODUCT_CD = ProdCd and rownum <2;
Return varResult;
End funGetAccount;
I don't understand what definitely you want to archive with this function: return select statement or retrieve data.
In the first case you should do:
Function funGetAccount (ProdCd in Number)
Return Varchar2
Is
varResult Varchar2(2000);
Begin
varResult := ' Select AC_NO, NAME From ('
|| ' Select AC_NO, NAME'
|| ' From Tab1 '
|| ' Where PRODUCT_CD = ' || ProdCd || ')';
Return varResult;
End funGetAccount;
In the second (retrieve data)
create or replace function funGetAccount (ProdCd in Number)
Return Varchar2
Is
varResult Varchar2(2000);
Begin
Select AC_NO||' '||NAME
into varResult
From Tab1
Where PRODUCT_CD = ProdCd;
Return varResult;
End funGetAccount;
As per the question i can see there are two requirements which can come into picture.
1) OP wants to return the raw SQL statement through FUNCTION.
2) OP wants SQL output in CONCATENATED WAY.
I have focused on 2 point where i have incorporated some of the good coding practices like EXCEPTION Handling. Hope it helps.
FUNCTION funGetAccount(
ProdCd IN NUMBER)
RETURN VARCHAR2
IS
p_result_out VARCHAR2(100);
varResult VARCHAR2(2000);
BEGIN
varResult := 'Select AC_NO||'',''||NAME From
( Select AC_NO, NAME From Tab1 Where PRODUCT_CD = '''||ProdCd||''')';
BEGIN --> Exception handling missed
EXECUTE IMMEDIATE varResult INTO p_result_out;
EXCEPTION
WHEN OTHERS THEN --> Others is broad term you can catch specific exceptions like no data found too many rows etc
NULL; -- Your action logic
END;
RETURN p_result_out;
END funGetAccount;
Function funGetAccount (ProdCd in Number)
Return Varchar2 Is
varResult Varchar2(2000);
Begin
varResult := ' Select AC_NO, NAME From ('
|| ' Select AC_NO, NAME'
|| ' From Tab1 Where PRODUCT_CD = '''|| ProdCd ||''')';
varResult := varResult;
Return varResult;
End funGetAccount;
Hi I have the following procedure:
create or replace procedure
SP_DELETE_FROM_TABLE(pTableName in VARCHAR2, pFieldName in VARCHAR2,
pFieldValue in VARCHAR2,pFieldType in VARCHAR2) is
querystring VARCHAR2(500);
begin
queryString := 'DELETE FROM ' ||pTableName||
' WHERE '||pFieldName ||' = DECODE(:pFieldType,integer,:pFieldValue)' ;
EXECUTE IMMEDIATE queryString USING pFieldType,pFieldValue;
end SP_DELETE_FROM_TABLE;
all my Parameters are of Type VARCHAR2, What I am trying to do is: When I call the procedure with the following values ('users_table','users_id','11','integer')
so by using DECODE I would like to check if pFieldValue is of type pFieldTypeand if yes return pFieldValue
so if pFieldValue is:11 and pfieldType is:integer it should delete users_id 11 if fieldType is string do nothing..
I'd create a function that checks parameter has correct type
and then use it in main procedure
--main procedure
create or replace procedure SP_DELETE_FROM_TABLE(pTableName in VARCHAR2, pFieldName in VARCHAR2,pFieldValue in VARCHAR2,pFieldType in VARCHAR2) is
querystring VARCHAR2(500);
begin
if 'Y' = is_type_correct(pFieldValue, pFieldType ) then
queryString := 'DELETE FROM ' ||pTableName|| ' WHERE '
||pFieldName ||' = :pFieldValue';
EXECUTE IMMEDIATE queryString USING pFieldValue;
end
else
null; --incorrect type and parameter, do nothing
end;
end SP_DELETE_FROM_TABLE;
--check function
CREATE OR REPLACE FUNCTION is_type_correct( p_val IN VARCHAR2, p_type varchar2 )
RETURN VARCHAR2 DETERMINISTIC PARALLEL_ENABLE
IS
l_num NUMBER;
l_date date;
BEGIN
if 'integer' = p_type
then
l_num := to_number( p_val );
elsif 'date' = p_type then
l_date := to_date(p_val, 'YYYY.MM.DD');
elsif 'varchar2' then
null;//do nothing
else
return 'N'; //uknown type
end if;
RETURN 'Y';
EXCEPTION
WHEN value_error THEN
RETURN 'N';
END is_number;
Just try to convert a string to a number, and when an exception occurs, do nothing:
querystring VARCHAR2(500);
some_number number;
begin
some_number := to_number( pFieldValue ); /* trying to convert to a number */
queryString := 'DELETE FROM ' ||pTableName||
' WHERE '||pFieldName ||' = :x' ;
EXECUTE IMMEDIATE queryString USING some_number;
EXCEPTION
WHEN VALUE_ERROR THEN null; /* do nothing when pFieldValue is not a number*/
end SP_DELETE_FROM_TABLE;
In a Pl sql function in need to pass in varchar parameter for a select statement alias. For example something like below,
create or replace
FUNCTION XX_FUNC_TEST (p_id in varchar2 , p_app_type in varchar2) RETURN varchar2 IS
appl_type_r varchar2(20);
BEGIN
SELECT p_app_type
INTO appl_type_r
FROM YY_TABLE
WHERE trans_id = p_id;
dbms_output.put_line(appl_type_r);
RETURN appl_type_r;
END XX_FUNC_TEST;
So its like the real column name is applicant_type which i pass as a parameter. But some how the out put i get is applicant_type which i pass as the parameter not the column value.
This is because you need to create your query dynamically:
CREATE OR REPLACE FUNCTION XX_FUNC_TEST (p_id in varchar2 , p_app_type in varchar2) RETURN VARCHAR2 IS
appl_type_r VARCHAR2(20);
sql VARCHAR2(2000);
BEGIN
sql :=
'SELECT ' || p_app_type || ' ' ||
'FROM YY_TABLE ' ||
'WHERE trans_id = p_id';
EXECUTE IMMEDIATE sql INTO appl_type_r;
DBMS_OUTPUT.PUT_LINE(appl_type_r);
RETURN appl_type_r;
END XX_FUNC_TEST;
Otherwise, Oracle thinks you just want to select the value itself.