I am familiar with MSSQL and using a parameter within the query, but I am not sure how I would do this within PL/SQL.
DECLARE
LSITEID NUMBER := 100001;
BEGIN
SELECT * from invoicehead ih
JOIN sitemaster sm on sm.SITEIID = ih.SITEIID
JOIN invoiceline il on il.invoiceIID = ih.invoiceIID
WHERE
ih.StartDate BETWEEN '2015-12-01' AND '2016-03-07'
AND SITEIID IN ( LSITEID)
END;
Right now I am testing this within Pl/SQL. But essentially I would be passing in the query with the parameter from MSSQL Linked Server OPENQuery.
How I can run the above query in PL/SQL with the parameter?
There is plenty of other resource for finding an answer, e.g. here (Tutorialspoint) or specifically here (plsql-tutorial). But perhaps I have missed your point.
To not remain on merely citing links, your query could look like this:
DECLARE
LSITEID integer;
BEGIN
LSITEID := 100001;
-- dostuff
END;
Two things to note: First, in a declare part (as I have learnt it) you should avoid assigning values. Second, if you intend to pass in different parameters you could/should use a procedure.
In PL/SQL you just use the name of the argument. In the following example, the argument is P_VALUE, note select statement says where dummy = p_value.
DECLARE
FUNCTION dummycount (p_value IN DUAL.dummy%TYPE)
RETURN INTEGER
AS
l_ret INTEGER;
BEGIN
SELECT COUNT (*) c
INTO l_ret
FROM DUAL
WHERE dummy = p_value;
RETURN l_ret;
END dummycount;
BEGIN
DBMS_OUTPUT.put_line ('A: ' || dummycount (p_value => 'A'));
DBMS_OUTPUT.put_line ('X: ' || dummycount (p_value => 'X'));
END;
This results in the following output:
A: 0
X: 1
Related
printf('local_time_greeting');
Having some trouble getting a stored procedure to run with SQL Developer. Below is an example patterned exactly like the first few lines, with variable names changed for security reasons.
CREATE OR REPLACE PROCEDURE redacted ( an_in_variable IN VARCHAR ) AS
these VARCHAR;
variables VARCHAR;
don_apostrophe_t INT;
matter INT;
BEGIN
DECLARE cursor_giving_me_trouble CURSOR FOR
SELECT something FROM db.table WHERE condition_1 = condition_2;
...
In the editor the SELECT word is red-wavy-lined, and when I try to run the code the output returns
PLS-00103: Encountered symbol "FOR" when expecting one of the following := . ( # % ; not null range default character
Any ideas?
You need to use IS rather than FOR, but you have the terms of the definition in the wrong order as well:
DECLARE
CURSOR cursor_giving_me_trouble IS
SELECT something FROM db.table WHERE condition_1 = condition_2;
db<>fiddle which still errors because of the illegal object names in your obfuscated code, but not from the syntax; and a more complete example.
It's also possible you're trying to use the construct:
FOR cursor_giving_me_trouble IN (
SELECT something FROM db.table WHERE condition_1 = condition_2
) LOOP
...
rather than a sub-block (a new declare, followed by begin/end) with references only to cursor within that. db<>fiddle.
To get you started:
create or replace procedure redacted
( an_in_variable in varchar2 )
as
these varchar2(123);
variables varchar2(456);
don_apostrophe_t integer;
matter integer;
cursor cursor_giving_me_trouble is
select 'Welcome to PL/SQL' as whatever from dual where 1=1;
begin
for r in cursor_giving_me_trouble loop
dbms_output.put_line(r.whatever);
end loop;
end;
However, you often don't need a separate cursor definition as there is this compact syntax:
create or replace procedure redacted
( an_in_variable in varchar2 )
as
these varchar2(123);
variables varchar2(456);
don_apostrophe_t integer;
matter integer;
begin
for r in (
select 'Welcome to PL/SQL' as whatever from dual where 1=1
)
loop
dbms_output.put_line(r.whatever);
end loop;
end;
What would be the PL/SQL equivalent of this SQL query:
SELECT * FROM table(OWNER.PACKAGE.get_exam('123456789'));
This is the Function that I am trying to call:
FUNCTION get_exam(id IN VARCHAR2)
RETURN ab_assign_v1
IS
CURSOR c_exams(cid VARCHAR2) IS
SELECT t_api_exam_v1(
sei.person_id, --unique id
l.description --loc description
)
FROM my_view sei
JOIN loc l
ON sei.loc_code = l.loc_code
v_collection ab_assign_v1;
BEGIN
OPEN c_exams(id);
FETCH c_exams BULK COLLECT INTO v_collection;
CLOSE c_exams;
RETURN v_collection;
EXCEPTION
WHEN OTHERS THEN
error_a1.raise_error(SQLCODE, SQLERRM);
END get_exam;
Hope this helps.
DECLARE
lv <COLLECTION_NAME>;
BEGIN
lv:= OWNER.PACKAGE.get_exam('123456789');
dbms_output.put_line(lv.COUNT);
END;
/
Assuming that you want to return the result of a function :
select owner.package.get_exam('123456789') from table
Your function returns a nested table type. You simply need to declare a variable of that type, and assign to it as you would if it were a scalar:
declare
l_coll your_collection_type;
begin
l_coll := OWNER.PACKAGE.get_exam('123456789');
end;
/
In this example your_collection_type is a placeholder for whatever object your function actually returns.
" I am getting this error: PLS-00201: identifier 'ab_assign_v1' must be declared "
ab_assign_v1 is the type used by your function. From the code posted in your revised question it seems that type is in the same schema which owns the package with the function. However your original pseudo-code prefixes the call with the schema name. So, putting two and two together, you need to revise the variable declaration to include the schema too. (You may need to grant EXECUTE on it too, if you haven't done this already).
declare
l_coll OWNER.ab_assign_v1;
begin
l_coll := OWNER.PACKAGE.get_exam('123456789');
end;
/
Lets suppose I have some SQL script in a scripted calculation view that takes a single value input parameter and generates a string of multiple inputs for an input parameter in another calculation view.
BEGIN
declare paramStr clob;
params = select foo
from bar
where bar.id = :IP_ID;
select '''' || string_agg(foo, ''', ''') || ''''
into paramStr
from :params;
var_out = select *
from "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW"(PLACEHOLDER."$$IP_IDS$$" => :paramStr);
END
This works as expected. However, if I change the var_out query and try to use the variable in a where clause
BEGIN
...
var_out = select *
from "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW"
where "IP_IDS" in(:paramStr);
END
the view will activate, but I get no results from the query. No runtime errors, just an empty result set. When I manually pass in the values to the WHERE IN() clause, everything works fine. It seems like an elementary problem to have, but I can't seem to get it to work. I have even tried using char(39) rather than '''' in my concatenation expression, but no banana :(
Ok, so what you are doing here is trying to make the statement dynamic.
For the IN condition, you seem to hope that once you have filled paramStr it would be handled as a set of parameters.
That's not the case at all.
Let's go with your example from the comment: paramStr = ' 'ip1','ip2' '
What happens, when the paramStr gets filled into your code is this:
var_out = select *
from "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW"
where "IP_IDS" in(' ''ip1'',''ip2'' ');
So, instead of looking for records that match IP_DS = 'ip1' or IP_DS = 'ip2' you are literally looking for records that match IP_DS = ' 'ip1','ip2' '.
One way to work around this is to use the APPLY_FILTER() function.
var_out = select *
from "_SYS_BIC"."somepackage/MULTIPLE_IP_VIEW";
filterStr = ' "IP_IDS" in (''ip1'',''ip2'') ';
var_out_filt = APPLY_FILTER(:var_out, :filterStr) ;
I've written about that some time ago: "On multiple mistakes with IN conditions".
Also, have a look at the documentation for APPLY_FILTER.
The SAP note “2315085 – Query with Multi-Value Parameter on Scripted Calculation View Fails with Incorrect Syntax Error“ actually showcases the APPLY_FILTER() approach that failed when I first tried it.
It also presents an UDF_IN_LIST function to convert a long varchar string with array of items from input parameter to a usable in-list predicate.
Unfortunately, couldn't make it work in sps11 rev 111.03 despite some parameter available (SAP Note 2457876 :to convert error into warning for string length overflow) - (range 3) string is too long exception
then ALTER SYSTEM ALTER CONFIGURATION ('indexserver.ini', 'System') set ('sqlscript', 'typecheck_procedure_input_param') = 'false' WITH RECONFIGURE;
-recompile the Calc View
then
select * from :var_tempout where OBJECT_ID in (select I_LIST from "BWOBJDES"."CROSS_AREA::UDF_INLIST_P"(:in_objectids,','));
invalid number exception - invalid number
But taking the scripting out of the function makes it work...
For this SPS 11 level APPLY_FILTER seems to be the only workaround. And it is really hard to say what would be the rev on SPS 12 to go in order to have it.
FUNCTION "BWOBJDES"."CROSS_AREA::UDF_INLIST_P"(str_input nvarchar(5000),
delimiter nvarchar(10))
RETURNS table ( I_LIST INTEGER ) LANGUAGE SQLSCRIPT SQL SECURITY INVOKER AS
/********* Begin Function Script ************/
BEGIN
DECLARE cnt int;
DECLARE temp_input nvarchar(128);
DECLARE slice NVARCHAR(10) ARRAY;
temp_input := :str_input;
cnt := 1;
WHILE length(temp_input) > 0 DO
if instr(temp_input, delimiter) > 0 then
slice[:cnt] := substr_before(temp_input,delimiter);
temp_input := substr_after(temp_input,delimiter);
cnt := :cnt + 1;
else
slice[:cnt] := temp_input;
break;
end if;
END WHILE;
tab2 = UNNEST(:slice) AS (I_LIST);
return select I_LIST from :tab2;
END;
CREATE PROCEDURE "MY_SCRIPTED_CV/proc"( IN numbers NVARCHAR(5000), OUT
var_out
MY_TABLE_TYPE ) language sqlscript sql security definer reads sql data with
result view
"MY_SCRIPTED_CV" as
/********* Begin Procedure Script ************/
BEGIN
-- not working
--var_out = select * from MY_TABLE where NUMBER in (select I_LIST from
--UDF_INLIST_P(:numbers,','));
-- working
DECLARE cnt int;
DECLARE temp_input nvarchar(128);
DECLARE slice NVARCHAR(13) ARRAY;
DECLARE delimiter VARCHAR := ',';
temp_input := replace(:numbers, char(39), '');
cnt := 1;
WHILE length(temp_input) > 0 DO
if instr(temp_input, delimiter) > 0 then
slice[:cnt] := substr_before(temp_input,delimiter);
temp_input := substr_after(temp_input,delimiter);
cnt := :cnt + 1;
else
slice[:cnt] := temp_input;
break;
end if;
END WHILE;
l_numbers = UNNEST(:slice) AS (NUMBER);
var_out=
SELECT *
FROM MAIN AS MA
INNER JOIN l_numbers as LN
ON MAIN.NUMBER = LN.NUMBER
END;
I know, this is a quite old thread but nevertheless my findings based what I read in here from Jenova might be interesting for others. Thatswhy I am writing them down.
I was faced with the same problem. Users can sent multiple entrie in an input parameter in a calc view I have to optimize. Unluckily I run into the same problem like Jenova and others before. And, no, imho this is not about dynamic view execution or dynamic sql, but about processing in lists in a clean way in a scripted calc view or table function if they are send in an input parameter.
Lars' proposal to use APPLY_FILTER is not applicable in my case, since I cannot use a complex statement in the APPLY_FILTER function itself. I have to materialize the whole amount of data in the select, which result I can assign to APPLY_FILTER and then execute the filtering. I want to see the filtering applied in the first step not after materializing data which is not appearing in the result of the filter application.
So I used a hdbtablefunction to create a function performing the parameter string cleanup and the transformation into an array and afterwards it is returning the result of the UNNEST function.
Since the result of the function is a table it can be used as a table inside the scripted view or tablefunction - in joins, subselects and so on.
This way I am able to process user input lists as expected, fast and with much less resource consumption.
FUNCTION "_SYS_BIC"."package1::transparam" (ip_string NVARCHAR(500) )
RETURNS table ( "PARAMETER" nvarchar(100))
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER AS
v_test varchar(1000);
IP_DELIMITER VARCHAR(1) := ',';
v_out VARCHAR(100):='';
v_count INTEGER:=1;
v_substr VARCHAR(1000):='';
v_substr2 VARCHAR(1000):='';
id INTEGER array;
val VARCHAR(100) array;
BEGIN
--
v_substr:=:ip_string;
v_substr := REPLACE(:v_substr, '''', '');
v_substr := REPLACE(:v_substr, ' ', '');
while(LOCATE (:v_substr, :ip_delimiter) > 0 ) do
-- find value
v_out := SUBSTR(v_substr, 0, LOCATE (:v_substr, :ip_delimiter) - 1 );
-- out to output
val[v_count]:=v_out;
-- increment counter
v_count:=:v_count+1;
-- new substring for search
v_substr2 := SUBSTR(:v_substr, LOCATE (:v_substr, :ip_delimiter) + 1, LENGTH(:v_substr));
v_substr := v_substr2;
END while;
IF(LOCATE (:v_substr, :ip_delimiter) = 0 AND LENGTH(:v_substr) > 0) THEN
-- no delimiter in string
val[v_count]:=v_substr;
END IF;
-- format output as tables
rst = unnest(:VAL) AS ("PARAMETER");
RETURN SELECT * FROM :rst;
END;
can be called like
select * from "package1.transparam"('''BLU'',''BLA''')
returning table with two lines
PARAMETER
---------
BLU
BLA
The most comprehensive explanation is here:
https://blogs.sap.com/2019/01/17/passing-multi-value-input-parameter-from-calculation-view-to-table-function-in-sap-hana-step-by-step-guide/
Creating custom function splitting string into multiple values
Then inner/left outer join can be used to filter.
I am working on PL/SQL code where I need to perform a select query using variable as column name in where clause. Column names are stored in a table as varchar and I am using a loop to pass those column names to my select statement.
Please find sample code segment I am trying to run:
set serveroutput on;
declare
var varchar2(100);
counter number;
begin
var:='description';
select count(*)
into counter
from nodetable
where var like '%Ship%';
dbms_output.put_line(counter);
end;
Output:
anonymous block completed
0
However the result should be 86.
Oracle is comparing last condition as two string and not column=string.
Please let me know if this is even feasible in oracle or if there is a workaround for it.
Regards
Ankit
You have to use dynamic SQL, preferrably with bind-variables:
EXECUTE IMMEDIATE
'select count(*) from nodetable where '||var||' like :p1'
INTO counter
USING '%Ship%';
Try this
declare
var varchar2(100);
counter number;
begin
var:='description';
EXECUTE IMMEDIATE
'select count(*)
into counter
from nodetable
where '||var||' like ''%Ship%'' ';
dbms_output.put_line(counter);
end;
You need to be carefull with the colon's (').
I agreed with previous answer in implementation, but i strictly recommend you to change your technical requirements, because you can't use bind variables for this, and it's potential place for injection. For example, if someone will edit value in your table which stores column names, to something like that: "description = inject_function or description". Then your dynamic sql block will execute this statement:
select count(*) from nodetable where description = inject_function or description like '%Ship%
and example implementation of function
create function inject_function
return varchar2
is pragma autonomous_transaction;
begin
delete * from most_important_table;
commit;
return to_char(null);
exception when others then
rollback;
return to_char(null);
end;
I would like to extract a function from a piece of SQL-code which is used multiple times in one query. I'm looking for a functionality which is similar to the following (invented by me) syntax:
with f(x) as (return x+1)
select f(thing1), f(thing2), f(thing3) from things
thing1, thing2, thing3 are integer columns in the table "things" in the example. Also, imagine that f is more complicated than an add-one function.
How do I define a function inside a query?
Declaration of a function in the WITH clause of a query is not possible but according to the information presented at OOW it will be in 12c version. So for now you need to create a function as a schema object whether it would be a stand-alone function or part of a package. For example:
create or replace function F(p_p in number)
return number
is
begin
return p_p + 1;
end;
And then call it in a query, ensuring that the data type of a column you are passing in to the function as a parameter is of the same data type as the parameter of the function:
select f(col1)
, f(col2)
, ...
, f(coln)
from your_table
Are you trying to build a function for dynamic table and table's columns, I would do some code like this? And you cannot declare a function in the WITH clause of a query.
SELECT f ( tablename,columnname1 ),
f ( tablename,columnname2 ),
........
FROM tablename;
Create or replace function f (tableName varchar2,ColumnName varchar2)
Return somethingHere
Is
varTableName varchar2(200);
varColumnName varchar2(200);
varValue integer;
t_cid INTEGER;
t_command VARCHAR2(200);
Begin
--Get tableName
varTableName := tableName ;
--Get columnName
varColumnName := ColumnName ;
t_command := 'SELECT ' || varColumnName ||' FROM ' || varTableName;
--Here execute dynamic sql statement
DBMS_SQL.PARSE
DBMS_SQL.DEFINE_COLUMN
DBMS_SQL.EXECUTE
--fatch row values into varValue
DBMS_SQL.COLUMN_VALUE (..,..,varValue);
--then do your x+1 magic here
varValue := varValue+1
--then output your value.
End;