Oracle Apex 5.0: populate a IR with WHERE IN clause based on page item - sql

I am try to set up a page that contains:
a page item (P16_INPUT) where users paste a list of IDs (on separate lines)
an interactive report (output) that should be populated based on the IDs
to achieve this I have added an intermediate hidden page item (P16_INPUT_INTERMEDIATE) where I convert the input text (on different lines) into a comma-separated text strings with single quotes.
Example:
User enters in P16_INPUT:
RTR123
RTR456
A Dynamic action calls an (overly complicated) PL/SQL expression that populates
P16_INPUT_INTERMEDIATE:
declare
l_in varchar(4000);
l_int varchar(4000);
l_out varchar(4000);
begin
l_in := :P16_INPUT;
l_int := replace(l_in,Chr(13),''',''');
l_out := '''' || l_int || '''';
l_out := replace(l_out,Chr(10));
l_out := replace(l_out,Chr(9));
:P16_INPUT_INTERMEDIATE := l_out;
end;
which produces:
'RTR123','RTR456'
The dynamic action refreshes also the output IR, which is based on the following SQL statement:
select t1.id ,t1.b_seq , t1.s_seq, t1.back_seq
from v_export t1
where t1.id in (:P16_INPUT_INTERMEDIATE);
But I always get back an empty table.
If I only enter 1 ID in P16_INPUT and skip the intermediate P16_INPUT_INTERMEDIATE, the IR is correctly populated with 1 row of results.

Im not sure how exactly to do this as I havent worked with it much, but what you are trying to do is dynamic SQL, an SQL query that is compiled on runtime.
Gl with it

Related

How to send cursor response from Oracle stored procedure

I have a stored procedure which I want to return cursor as the OUT parameter. I see the output in
dbms_output.put_line(as_return_val); like
Loan Purpose can not be Limited || /loanPurpose
THE PURPOSE OF PURCHASE IS NOT VALID || /loanPurpose
I need to read this response as a array of Objects(each ro one Object) in my service layer. Sorry I am not sure I need to send it as a cursor or like a varchar from here. I added as_return_val this variable to see whether I am getting correct response .
Procedure
create or replace PROCEDURE PR_LOGIC_CHECK_TEST
(in_loan_id IN NUMBER,
in_trans_id IN NUMBER)AS
BEGIN
DECLARE
as_errm VARCHAR2(2000);
curr_cursor_out SYS_REFCURSOR;
temp_cursor_row temp_cor_ll_cursor%rowtype;
as_return_val VARCHAR2(500);
BEGIN
pr_loan_logic_check(in_loan_id, in_trans_id, as_errm, curr_cursor_out);
LOOP
FETCH curr_cursor_out INTO temp_cursor_row;
EXIT WHEN curr_cursor_out%notfound;
as_return_val := temp_cursor_row.ret_value
|| ' || '
|| '/'||temp_cursor_row.xpath_name;
--dbms_output.put_line(as_return_val);
END LOOP;
close curr_cursor_out;
ROLLBACK;
END;
END PR_LOGIC_CHECK_TEST;
I'm still guessing at what you really want to happen since you haven't actually given us a reproducible test case (i.e. something that we can run locally or on dbfiddle or liveSQL that produces a particular output given a particular input).
Architecturally, it seems problematic to have a stored procedure whose only purpose is to format the output of a different procedure for human consumption. Formatting results would more properly be done in the display layer (the view of an MVC application but view means something else when we're talking about databases so I'm using "display layer") of the application not in a stored procedure. It would make more sense for your application to call pr_loan_logic_check directly and to let your display layer decide to concatenate the values from multiple columns together. If someone wants to change how the output is formatted later, you'd then just be changing code in the display layer not in your backend database.
My guess is that you want to return a collection like this. Note that I'm creating the collection type at the SQL level. You could create it in a PL/SQL package as well.
create or replace type varchar2_tbl
is table of varchar2(500);
create or replace PROCEDURE PR_LOGIC_CHECK_TEST (
in_loan_id IN NUMBER,
in_trans_id IN NUMBER,
out_strings OUT varchar2_tbl
)
AS
as_errm VARCHAR2(2000);
curr_cursor_out SYS_REFCURSOR;
temp_cursor_row temp_cor_ll_cursor%rowtype;
BEGIN
pr_loan_logic_check(in_loan_id, in_trans_id, as_errm, curr_cursor_out);
out_strings := out_strings();
LOOP
FETCH curr_cursor_out INTO temp_cursor_row;
EXIT WHEN curr_cursor_out%notfound;
out_strings.extend();
out_strings( out_strings.count ) :=
temp_cursor_row.ret_value
|| ' || '
|| '/'||temp_cursor_row.xpath_name;
END LOOP;
close curr_cursor_out;
-- I have trouble imagining why you'd put a `rollback` here
ROLLBACK;
END PR_LOGIC_CHECK_TEST;

How to make a function that takes code like "SELECT * FROM SOME_TABLE" as an input and returns a table as an output?

I want to create a function that takes some code as an input (e.g. Select * FROM SOME_TABLE) and returns the result of a query as an output.
I want to use it in procedures in order to return tables as a result.
It should look like this:
BEGIN
--some procedure code
CREATE TABLE SOME_TABLE as Select * FROM ...;
Select * FROM table(my_function('Select * FROM SOME_TABLE'));
END;
Important tips:
The resulting table can have multiple columns, from 1 to +inft
The resulting table can have multiple rows, from 1 to +inft
So the size of a table can be both very small or very large.
The input query can have several where, having, partition, and other Oracle constructions.
I want to have a table as an output, not DBMS_OUTPUT.
I can't install any modules/applications, or use other languages hints. However, I can manually create types, functions, procedures.
I tried to search in the net but could not find a solution that meets all my expectations. The best link I've found was this:
https://sqljana.wordpress.com/2017/01/22/oracle-return-select-statement-results-like-sql-server-sps-using-pipelined-functions/
DBMS_SQL.RETURN_RESULT works if your "code" is a select query
DECLARE
l_cur SYS_REFCURSOR;
l_query VARCHAR2(4000) := 'select * from SOME_TABLE';
BEGIN
OPEN l_cur for l_query;
DBMS_SQL.RETURN_RESULT(l_cur);
END;
/
you can create a function that has a string as parameter and return a cursor.
select statement you should pass as a string. in a function you can open a Cursor.
declare
v_sql varchar2(100) := 'select 1,2,3,4,5 from dual';
cur_ref SYS_REFCURSOR;
function get_data(in_sql in varchar2) return SYS_REFCURSOR
as
cur_ret SYS_REFCURSOR;
begin
OPEN cur_ret FOR in_sql;
return cur_ret;
end;
begin
:cur_ref := get_data(v_sql);
end ;
if your select statement is longer than 32K than you maybe should use a clob instead of varchar2 for your Parameter type. But you have to try that

Select statement in PL/SQL

Does anyone know how to use their tables and columns from pre-existing databases in a pl/sql command. I am using something from Oracles webpage to use as an example.
SQL> declare
2 v_line varchar2(40):= '&v_string';
3 begin
4 v_line := 'Hello '||v_line;
5 dbms_output.put_line (v_line);
6 end;
7 /
old 2: v_line varchar2(40):= '&v_string';
New 2: v_line varchar2(40):= 'Thomas';
Hello Thomas
PL/SQL procedure successfully completed.
I am wanting to be able to manipulate the data from above. Lets say I have a table called name_s and I have columns called n_first and n_last and I want to use those columns to output 'Hello Thomas' but using those tables.
I am new to pl.sql and trying to learn the syntax.
I wasn't sure if you could,
DECLARE
n_first
n_last
BEGIN
SELECT * FROM name_s
n_fisrt := 'Thomas'||n_first;
dbms_output.put_line(n_first)
END;
/
Desired Output: 'Thomas'
I understand that this is not syntactically correct but I am just trying to pick up on how you can use data from your existing tables to get this sort of desired output
EDIT: The above table is hypothetical and only contains two columns for this example. Now using real tables and data.
SET SERVEROUTPUT ON;
begin
for cur_r in (select loc_t from loc_t_matrix where store_s in(1,2,3,4,5); loop
dbms_output.put_line('Hello store' || cur_r.loc_traits );
end loop;
end;
/
The above does not compile
Do you need something like this
declare
v_authName author.author_last_name%type;
begin
select
author_last_name into v_authName
from
author
where
author_key = 'A103';
dbms_output.put_line('Name: '||v_authName);
end;
/
Name: weaton
http://www.dba-oracle.com/t_pl_sql_plsql_select_into_clause.htm
As tables usually contain more than one row, I presume that NAME_S also contains several rows. In that case, the simplest option to display those names is a cursor FOR loop (Google for it). For example:
begin
for cur_r in (select n_first, n_last from name_s) loop
dbms_output.put_line('Hello ' || cur_r.n_first ||' '||cur_r.n_last);
end loop;
end;
/
As you can see, you don't need local variables in that case.
If you used your SELECT ... INTO (as suggested by mCeviker), that would work unless it raises TOO-MANY-ROWS (as you can't put all rows' names into a single variable).

HANA - Passing string variable into WHERE IN() clause in SQL script

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.

Looping on values, creating dynamic query and adding to result set

I have the following problem. I am an experienced Java programmer but am a bit of a n00b at SQL and PL/SQL.
I need to do the following.
1 Pass in a few arrays and some other variables into a procedure
2 Loop on the values in the arrays (they all have the same number of items) and dynamically create an SQL statement
3 Run this statement and add it to the result set (which is an OUT parameter of the procedure)
I already have experience of creating an SQL query on the fly, running it and adding the result to a result set (which is a REF CURSOR) but I'm not sure how I'd loop and add the results of each call to the query to the same result set. I'm not even sure if this is possible.
Here's what I have so far (code edited for simplicity). I know it's wrong because I'm just replacing the contents of the RESULT_SET with the most recent query result (and this is being confirmed in the Java which is calling this procedure).
Any and all help would be greatly appreciated.
TYPE REF_CURSOR IS REF CURSOR;
PROCEDURE GET_DATA_FASTER(in_seq_numbers IN seq_numbers_array, in_values IN text_array, in_items IN text_array, list IN VARCHAR2, RESULT_SET OUT REF_CURSOR) AS
query_str VARCHAR2(4000);
seq_number NUMBER;
the_value VARCHAR2(10);
the_item VARCHAR2(10);
BEGIN
FOR i IN 1..in_seq_numbers.COUNT
LOOP
seq_number := in_seq_numbers(i);
the_value := trim(in_values(i));
the_item := trim(in_items(i));
query_str := 'SELECT distinct '||seq_number||' as seq, value, item
FROM my_table ai';
query_str := query_str || '
WHERE ai.value = '''||the_value||''' AND ai.item = '''||the_item||'''
AND ai.param = ''BOOK''
AND ai.prod in (' || list || ');
OPEN RESULT_SET FOR query_str;
END LOOP;
EXCEPTION WHEN OTHERS THEN
RAISE;
END GET_DATA_FASTER;
A pipelined table function seems a better fit for what you want, especially if all you're doing is retrieving data. See http://www.oracle-base.com/articles/misc/pipelined-table-functions.php
What you do is create a type for your output row. So in your case you would create an object such as
CREATE TYPE get_data_faster_row AS OBJECT(
seq NUMBER(15,2),
value VARCHAR2(10),
item VARCHAR2(10)
);
Then create a table type which is a table made up of your row type above
CREATE TYPE get_data_faster_data IS TABLE OF get_data_faster_row;
Then create your table function that returns the data in a pipelined manner. Pipelined in Oracle is a bit like a yield return in .net (not sure if you're familiar with that). You find all of the rows that you want and "pipe" them out one at a time in a loop. When your function completes the table that's returned consists of all the rows you piped out.
CREATE FUNCTION Get_Data_Faster(params) RETURN get_data_faster_data PIPELINED AS
BEGIN
-- Iterate through your parameters
--Iterate through the results of the select using
-- the current parameters. You'll probably need a
-- cursor for this
PIPE ROW(get_data_faster_row(seq, value, item));
LOOP;
LOOP;
END;
EDIT: Following Alex's comment below, you need something like this. I haven't been able to test this but it should get you started:
CREATE FUNCTION Get_Data_Faster(in_seq_numbers IN seq_numbers_array, in_values IN text_array, in_items IN text_array, list IN VARCHAR2) RETURN get_data_faster_data PIPELINED AS
TYPE r_cursor IS REF CURSOR;
query_results r_cursor;
results_out get_data_faster_row := get_data_faster_row(NULL, NULL, NULL);
query_str VARCHAR2(4000);
seq_number NUMBER;
the_value VARCHAR2(10);
the_item VARCHAR2(10);
BEGIN
FOR i IN 1..in_seq_number.COUNT
LOOP
seq_number := in_seq_numbers(i);
the_value := trim(in_values(i));
the_item := trim(in_items(i));
query_str := 'SELECT distinct '||seq_number||' as seq, value, item
FROM my_table ai';
query_str := query_str || '
WHERE ai.value = '''||the_value||''' AND ai.item = '''||the_item||'''
AND ai.param = ''BOOK''
AND ai.prod in (' || list || ');
OPEN query_results FOR query_str;
LOOP
FETCH query_results INTO
results_out.seq,
results_out.value,
results_out.item;
EXIT WHEN query_results%NOTFOUND;
PIPE ROW(results_out);
END LOOP;
CLOSE query_results;
END LOOP;
END;
Extra info from Alex's comment below useful for the answer:
you can have multiple loops from different sources, and as long as the
data from each be put into the same object type, you can just keep
pumping them out with pipe row statements anywhere in the function.
The caller sees them as a table with the rows in the order you pipe
them. Rather than call a procedure and get a result set as an output
parameter, you can query as select seq, value, item from
table(package.get_data_faster(a, b, c, d)), and of course you can
still have an order by clause if the order they're piped isn't what
you want.