PL/SQL - comma separated list within IN CLAUSE - plsql

I am having trouble getting a block of pl/sql code to work. In the top of my procedure I get some data from my oracle apex application on what checkboxes are checked. Because the report that contains the checkboxes is generated dynamically I have to loop through the
APEX_APPLICATION.G_F01
list and generate a comma separated string which looks like this
v_list VARCHAR2(255) := (1,3,5,9,10);
I want to then query on that list later and place the v_list on an IN clause like so
SELECT * FROM users
WHERE user_id IN (v_list);
This of course throws an error. My question is what can I convert the v_list to in order to be able to insert it into a IN clause in a query within a pl/sql procedure?

If users is small and user_id doesn't contain commas, you could use:
SELECT * FROM users WHERE ',' || v_list || ',' LIKE '%,'||user_id||',%'
This query is not optimal though because it can't use indexes on user_id.
I advise you to use a pipelined function that returns a table of NUMBER that you can query directly. For example:
CREATE TYPE tab_number IS TABLE OF NUMBER;
/
CREATE OR REPLACE FUNCTION string_to_table_num(p VARCHAR2)
RETURN tab_number
PIPELINED IS
BEGIN
FOR cc IN (SELECT rtrim(regexp_substr(str, '[^,]*,', 1, level), ',') res
FROM (SELECT p || ',' str FROM dual)
CONNECT BY level <= length(str)
- length(replace(str, ',', ''))) LOOP
PIPE ROW(cc.res);
END LOOP;
END;
/
You would then be able to build queries such as:
SELECT *
FROM users
WHERE user_id IN (SELECT *
FROM TABLE(string_to_table_num('1,2,3,4,5'));

You can use XMLTABLE as follows
SELECT * FROM users
WHERE user_id IN (SELECT to_number(column_value) FROM XMLTABLE(v_list));

I have tried to find a solution for that too but never succeeded. You can build the query as a string and then run EXECUTE IMMEDIATE, see http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/dynamic.htm#i14500.
That said, it just occurred to me that the argument of an IN clause can be a sub-select:
SELECT * FROM users
WHERE user_id IN (SELECT something FROM somewhere)
so, is it possible to expose the checkbox values as a stored function? Then you might be able to do something like
SELECT * FROM users
WHERE user_id IN (SELECT my_package.checkbox_func FROM dual)

Personally, i like this approach:
with t as (select 'a,b,c,d,e' str from dual)
--
select val
from t, xmltable('/root/e/text()'
passing xmltype('<root><e>' || replace(t.str,',','</e><e>')|| '</e></root>')
columns val varchar2(10) path '/'
)
Which can be found among other examples in Thread: Split Comma Delimited String Oracle
If you feel like swamping in even more options, visit the OTN plsql forums.

Related

How to concatenate a Select query inside a INSTR() in SQLite?

I was trying to order a result set by the order of the values in an IN() clause.
SELECT * FROM CrossReference WHERE cross_reference_id IN (SELECT Id FROM FilteredIds)
So I tried to find a function such as MySql FIELD(). Then I found these answers (answer1, answer2) which explain how to do the exact thing on SQLite using the INSTR().
SELECT *, INSTR(',GDBR10,GDBR5,GDBR30,', ',' || ticker || ',') POS
FROM tbl
WHERE POS>0
ORDER BY POS;
So it's working as expected, but I want to populate the ids dynamically using a select query. I tried many approaches, but nothing seemed to work. Here is the last one I tried. It gave me just one result row (a result related to the first filterId).
SELECT *, INSTR (','||(SELECT id FROM FilteredIds)||',', ',' || cross_reference_id || ',') POS FROM CrossReference WHERE POS>0 ORDER BY POS;
So I guess I'm making some kind of mistake when concatenating the SELECT query with the rest of the code. Because when I manually enter the filtered Ids it works and returns results according to the entered filter ids.

Give alias to anonymous column of global collection type in SELECT FROM TABLE(collection)

I'm trying to use values from an apex_t_numbers collection in a SELECT query as WITH subquery:
atn_cur_ids := apex_string.split_numbers(arg_v_ids, ':');
-- So if arg_v_ids := '1:2:3', then atn_cur_ids := apex_t_numbers(1, 2, 3)
with t_cur_ids as (
select * as id from table(atn_cur_ids);
)
select text from t_texts
join t_cur_ids on t_texts.id = t_cur_ids.id
And here's the problem - apex_t_numbers is a table of number, not of record type with named fields. Oracle SQL doesn't allow to give aliases to a * even if it has only one single "anonymous" column.
A possible solution could be a function that both
receives * and
returns value per row
But I am aware of only one that can get a * and return something - count(*), and it doesn't meet the second requirement.
Alright, found a solution. It could be done with column_value as a name of column:
with t_cur_ids as (
select column_value as id from table(atn_cur_ids);
)
select text from t_texts
join t_cur_ids on t_texts.id = t_cur_ids.id

Create a table as, where 'date condition' in dynamic PL/SQL

I got assigned the following task.
Assume we have a table A structured with an id column and a date column.
Write a procedure in PL/SQL that: takes as parameters the table name (in our case A) and a date D, creates a backup table named A_bck containing only the records of A with dates < D and removes from the table A all the records inserted in A_bck.
Here there is my code.
Unluckily I get this error:
Error report -
ORA-00904: "MAY": invalid identifier
ORA-06512: at line 41
ORA-06512: at line 80
00904. 00000 - "%s: invalid identifier"
If I try to achieve the same result using a where condition on the id column instead that on the date one, I have no problems.
Where is the mistake? Am I implementing it completely in the wrong way?
The problem you have is that as you're executing dynamic sql you're query is built up as a string. Oracle does not know that the date you've given is actually a date, it is simply being treated as part of the string. To solve this you should be able to do the following:
my_query := 'CREATE TABLE ' || table_name_backup || ' AS (SELECT * FROM ' || table_name || ' WHERE table_date < to_date(''' || backup_date || '''))';
This should sort out your issue for you. As a side note, you will probably want to change your "table_exists" query, as table names are all stored in upper case, e.g.
SELECT COUNT(*) INTO table_exists FROM USER_TABLES WHERE TABLE_NAME = upper(my_table);
Edit: Further explanation following comment
To explain why you don't have the above problem when using integers, it is important to remember that using execute immediate simply executes the given string as an SQL query.
For example:
declare
x INTEGER := 1;
i integer;
my_query VARCHAR2(256);
begin
my_query := 'select 1 from dual where 1 = ' || x;
EXECUTE IMMEDIATE my_query INTO i;
end;
my_query in the above example would be executed as:
select 1 from dual where 1 = 1
which is perfectly valid sql. In your example however, you were ending up with something like this:
CREATE TABLE abaco_bck AS (SELECT * FROM abaco WHERE table_date < 27-MAY-17)
As it isn't wrapped in quotes, or explicitly converted to a date, the SQL engine is trying to subtract "MAY" from 27, but it doesn't know what "MAY" is.
One other thing to mention, is that for some operations you could use bind variables instead of quotes (although you can't for DDL) e.g.
declare
lToday DATE := SYSDATE;
i INTEGER;
my_query VARCHAR2(256);
begin
my_query := 'select 1 from dual where sysdate = :1';
EXECUTE IMMEDIATE my_query INTO i USING lToday;
end;

PLS-00103: Encountered the symbol ","

This procedure is getting following error.
CREATE OR REPLACE PROCEDURE SAMPLE
IS
BEGIN
EXECUTE IMMEDIATE
'CREATE TABLE COLUMN_NAMES AS (
SELECT LISTAGG(COLUMN_NAME, ',') WITHIN GROUP (ORDER BY COLUMN_NAME) AS STUDENTS
FROM
(SELECT DISTINCT COLUMN_NAME
FROM BW_COLUMN_ROW_CELL_JOIN)
)';
END;
/
gives:
PLS-00103: Encountered the symbol "," when expecting one of the following:
* & = - + ; < / > at in is mod remainder not rem return
returning <an exponent (**)> <> or != or ~= >= <= <> and or
like like2 like4 likec between into using || multiset bulk member submultiset
Can any one say what is wrong in this?
Thanks.
Another way (in Oracle 10g and later) is to use the alternative string literal notation - this means you don't need to worry about correctly escaping all the single quotes in the string, e.g. q'{my string's got embedded quotes}':
CREATE OR REPLACE PROCEDURE SAMPLE
IS
BEGIN
EXECUTE IMMEDIATE q'[
CREATE TABLE COLUMN_NAMES AS (
SELECT LISTAGG(COLUMN_NAME, ',') WITHIN GROUP (ORDER BY COLUMN_NAME) AS STUDENTS
FROM
(SELECT DISTINCT COLUMN_NAME
FROM BW_COLUMN_ROW_CELL_JOIN)
)]';
END;
/
The problem I think is you have single quotes within single quotes. I cant test this at the moment, but I'd suggest you try the following (note the inner quotes are double quotes '', which escapes them:
CREATE OR REPLACE PROCEDURE SAMPLE
IS
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE COLUMN_NAMES AS ( SELECT LISTAGG(COLUMN_NAME, '','') WITHIN GROUP (ORDER BY COLUMN_NAME) AS STUDENTS FROM (SELECT DISTINCT COLUMN_NAME FROM BW_COLUMN_ROW_CELL_JOIN) )';
END;
/
I'd also try the create table part of the code standalone first just to make sure its valid before wrapping it in a proc.
You can't use single quotes directly in select statement of Execute Immediate it need to be coded using CHR(39)
CREATE OR REPLACE PROCEDURE SAMPLE
IS
BEGIN
EXECUTE IMMEDIATE
'CREATE TABLE COLUMN_NAMES AS (
SELECT LISTAGG(COLUMN_NAME,'||chr(39)||','||chr(39)||') WITHIN GROUP (ORDER BY COLUMN_NAME) AS STUDENTS
FROM
(SELECT DISTINCT COLUMN_NAME FROM BW_COLUMN_ROW_CELL_JOIN))';
END;

Select from PLSQL Associative array?

Is it possible to use SELECT FROM when using an associative array? I'm passing an array to a stored procedure through a .NET application, and I wanna be able to use that array as a condition when selecting from another table. Lets say I'm passing an array of IDs to the procedure, I wanna be able to do this:
select * from table1 where userID in (select column_value from array)
The type for the array is defined in the package:
type id_array is type of number index by pls_integer
Yes, it is possible, by wrapping the array with a pipelined function. Here's a good primer on pipelined functions:
http://www.oracle-developer.net/display.php?id=429
UPDATE: Oracle 12c now supports querying associative arrays using the TABLE operator, as long as the type is declared in a package spec: https://galobalda.wordpress.com/2014/08/02/new-in-oracle-12c-querying-an-associative-array-in-plsql-programs/
e.g.
select * from table1
where userID in (select column_value from table(array));
No, you can't select from PL/SQL arrays, since you use SQL in select from statements, though you can use DB defined Nested Tables types in SQL. This short article can help you get started.
Take a look a this simple synthetic exmple:
> create type temp_t as table of int;/
Type created.
> select 'test' from dual where 1 in (select * from table(temp_t(1,2,3)));
'TES
----
test
An example using PLSQL (to select from a nested table):
create type temp_r as OBJECT(
temp_varchar2 varchar2(100),
temp_number number(20)
);
/
create type temp_t as TABLE of temp_r;
/
set serveroutput on size 1000000
/
-- PLSQL starts here
declare
temp_rec temp_r := temp_r(null, null); -- empty constructor to initialize object
temp_table temp_t := temp_t(); -- empty constructor to initialize object
lv_ref_cursor SYS_REFCURSOR;
lv_temp_varchar2 varchar(100);
lv_temp_number number(20);
begin
temp_rec.temp_varchar2 := 'first';
temp_rec.temp_number := 1;
temp_table.extend;
temp_table(1) := temp_rec;
temp_table.extend;
temp_table(2) := temp_r('second', 2);
OPEN lv_ref_cursor FOR
SELECT temp_varchar2, temp_number
FROM table(temp_table)
where temp_number = 1;
fetch lv_ref_cursor into lv_temp_varchar2, lv_temp_number;
close lv_ref_cursor;
dbms_output.put_line('returns: ' || lv_temp_varchar2 || ', ' || lv_temp_number);
end;
/

Resources