convert varchar value to array to varchar sql oracle [duplicate] - sql

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

Create UNION ALL statements via a loop

Manually, I can select partitions in an inner query with the first code block below. Is there a way to do this in a more elegant way via a loop? I'm showing 3 partitions here, but I have about 200 and the partitions are based on a date column and therefore the partition names will need to change when I run this query again at a future date.
SELECT *
FROM (
SELECT * FROM RSS_ACQ.TRX_ARQ PARTITION("SYS_P211048") UNION ALL
SELECT * FROM RSS_ACQ.TRX_ARQ PARTITION("SYS_P210329") UNION ALL
SELECT * FROM RSS_ACQ.TRX_ARQ PARTITION("SYS_P176323")
) TRX_ARQ
;
With this statement, I've created a loop that outputs the UNION ALL statements.
BEGIN
FOR ALL_TAB_PARTITIONS IN
(
SELECT PARTITION_NAME
FROM ALL_TAB_PARTITIONS
where TABLE_OWNER = 'TABLEOWNER'
AND TABLE_NAME = 'TABLENAME'
AND PARTITION_POSITION > 123
ORDER BY partition_position DESC
)
LOOP
DBMS_OUTPUT.PUT_LINE( 'SELECT * FROM RSS_ACQ.TRX_ARQ PARTITION(\"'
|| ALL_TAB_PARTITIONS.PARTITION_NAME || '\") UNION ALL');
END LOOP;
END;
And in this block, I've attempted to use the loop inside the inner query. It's not yet formatted correctly and I'll need to avoid having UNION ALL for the very last partition.
SELECT *
FROM (
BEGIN
FOR ALL_TAB_PARTITIONS IN
(
SELECT PARTITION_NAME
FROM ALL_TAB_PARTITIONS
where TABLE_OWNER = 'TABLEOWNER'
AND TABLE_NAME = 'TABLENAME'
AND PARTITION_POSITION > 123
ORDER BY partition_position DESC
)
LOOP
DBMS_OUTPUT.PUT_LINE( 'SELECT * FROM RSS_ACQ.TRX_ARQ PARTITION(\"'
|| ALL_TAB_PARTITIONS.PARTITION_NAME || '\") UNION ALL');
END LOOP;
END;
) TRX_ARQ
;
Here are some of the errors, but there were also many more. They are syntax errors pointing to other parts of the query so I would expect that I have an issue with escaping the quotes.
Error starting at line : 99 in command -
END LOOP
Error report -
Unknown Command
Error starting at line : 100 in command -
END
Error report -
Unknown Command
Error starting at line : 101 in command -
)
Error report -
Unknown Command
Error starting at line : 102 in command -
) TABLENAME
Error report -
Unknown Command
This is a bit of a guess, but it's too long for a comment.
I am assuming your table is interval partitioned. In that case, getting all the data from partition positions > 123 is the same as getting all the rows with a higher date than the highest date in partition 123.
You can obtain that date from ALL_TAB_PARTITIONS and then use it to query the table. Like this:
WITH FUNCTION get_high_value RETURN DATE IS
l_high_val_expr ALL_TAB_PARTITIONS.HIGH_VALUE%TYPE;
l_high_value DATE;
BEGIN
SELECT high_value
INTO l_high_val_expr
FROM all_tab_partitions
WHERE table_owner = 'RSS_ACQ'
AND table_Name = 'TRX_ARQ'
and partition_position = 123;
EXECUTE IMMEDIATE 'SELECT ' || l_high_val_expr || ' FROM DUAL' INTO l_high_value;
RETURN l_high_value;
END;
SELECT * FROM rss_acq.trx_arq
-- Replace "partitioned_date_column" with the name of the column on which the
-- table is interval partitioned.
WHERE partitioned_date_column > get_high_value;
We can't execute an anonymous PL/SQL block in a SELECT statement.
What you need to do is spool the output of the ALL_TAB_PARTITIONS loop to a file (or a SQL worksheet if you're using an IDE like SQL Developer). This will give you a script you can run separately after editing it (you need to trim UNION ALL from the final generated SELECT.
Probably there are more elegant ways of achieving the same thing, but the task seems sufficiently wrong that it doesn't strike me as being worth the effort. You want to query 200 partitions in a single statement. That is a brute force operation and there isn't mush to be gained from querying named blocks. In fact, producing a union of 200 separate queries may be more expensive than a single query. So why not try something like this?
select * from RSS_ACQ.TRX_ARQ
where partition_key_col >= date '2018-08-01' -- or whatever
"I think you are overlooking the 12c feature of using PL/SQL in the WITH clause"
That 12c feature is for functions not procedures, so it won't help the OP run their code. It would be possible to use a WITH clause function but that would require:
creating a type with the same projection as the target table
and a nested table type based on that type
a WITH clause function which assembles and executes a dynamic SQL statement
we can't use REF CURSORs in SQL so ...
the function has to execute the dynamic select INTO a local collection variable ...
then loop over the collection and PIPE ROW to output those rows ...
so the main query can call the function with a table() call
Can a WITH clause function be pipelined? I can't find anything in the documentation to say we can't (don't have access to 12c right now to test).

How can I loop to get counts from multiple tables?

I am trying to derive a table with counts from multiple tables. The tables are not on my schema. The table names on the schema that I am interested in all start with 'STAF_' and end with '_TS'. The criteria i am looking for is where SEP = 'MO'. So for example, the query in its base form is:
select area, count(SEP) areacount
from mous.STAF_0001_TS
where SEP = 'MO'
group by area;
I have about 1000 tables that i'd like to do this for.
Ultimatly, I'd like the output to be a table on my schema that looks like the following:
area| areacount
0001| 3
0002| 7
0003| 438
Thank you.
As a first step I'd write an SQL query that generates an SQL query:
SELECT 'SELECT area, count(*) FROM '||c.table_name||'UNION ALL' as run_me
FROM all_tables c
WHERE c.table_name LIKE 'STAF\_%\_MS' escape '\'
Running this will produce an output that is another SQL query. Copy the result text out of your results grid and paste it back into your query pane. Delete the final UNION ALL and run it
Once you dig how to write an SQL query that generate an SQL query, you can look at turning it into a view, or creating a dynamic query in a string.
Gotta say, this is a horrible way to store data; you'd be better off using ONE table with an extra column containing whatever is in xxx of STAF_xxx_MS right now
In Oracle 12c, you can embed a FUNCTION that will query the number of rows in any given table. Then you can use that function in your main query. Here is an example:
WITH FUNCTION cnt ( p_owner VARCHAR2, p_table_name VARCHAR2 ) RETURN NUMBER IS
l_cnt NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT count(*) INTO :cnt FROM ' || p_owner || '.' || p_table_name INTO l_cnt;
RETURN l_cnt;
EXCEPTION WHEN OTHERS THEN
RETURN NULL; -- This will happen for entries in ALL_TABLES that are not directly accessible (e.g., IOT overflow tables)
END cnt;
SELECT t.owner, t.table_name, cnt(t.owner, t.table_name)
FROM all_tables t
where t.table_Name like 'STAF\_%\_MS' escape '\';

Oracle function with select all from tables

SELECT DISTINCT L.* FROM LABALES L , MATCHES M
WHERE M.LIST LIKE '%ENG'
ORDER BY L.ID
I need to create function with this select, I tried this but it doesn't work.
CREATE OR REPLACE FUNCTION getSoccerLists
RETURN varchar2 IS
list varchar2(2000);
BEGIN
SELECT DISTINCT L.* FROM LABALES L , MATCHES M
WHERE M.LIST LIKE '%ENG'
ORDER BY L.ID
return list;
END;
How will I create function that returns all from table L.
Thanks
You may use implicit result using DBMS_SQL.RETURN_RESULT(Oracle12c and above) in a procedure using a cursor to your query.
CREATE OR REPLACE PROCEDURE getSoccerLists
AS
x SYS_REFCURSOR;
BEGIN
OPEN x FOR SELECT DISTINCT L.* FROM LABALES L
JOIN MATCHES M ON ( 1=1 ) -- join condition
WHERE M.LIST LIKE '%ENG'
ORDER BY L.ID;
DBMS_SQL.RETURN_RESULT(x);
END;
/
then simply call the procedure
EXEC getSoccerLists;
For lower versions(Oracle 11g) , you may use a print command to display the cursor's o/p passing ref cursor as out parameter.
CREATE OR REPLACE PROCEDURE getSoccerLists (x OUT SYS_REFCURSOR)
AS
BEGIN
OPEN x FOR SELECT DISTINCT L.* FROM LABALES L
JOIN MATCHES M ON ( 1=1 ) -- join condition
WHERE M.LIST LIKE '%ENG'
ORDER BY L.ID;
END;
/
Then, in SQL* Plus or running as script in SQL developer and Toad, you may get the results using this.
VARIABLE r REFCURSOR;
EXEC getSoccerLists (:r);
PRINT r;
Another option is to use TABLE function by defining a collection of the record type of the result within a package.
Refer Create an Oracle function that returns a table
I guess this questions is a repetition of the your previously asked question, where you wanted to get all the columns of tables but into separate column. I already answered in stating this you cannot do if you call your function via a SELECT statement. If you call your function in a Anoymous block you can display it in separate columns.
Here Oracle function returning all columns from tables
Alternatively, you can get the results separated by a comma(,) or pipe (|) as below:
CREATE OR REPLACE
FUNCTION getSoccerLists
RETURN VARCHAR2
IS
list VARCHAR2(2000);
BEGIN
SELECT col1
||','
||col2
||','
||col2
INTO LIST
FROM SOCCER_PREMATCH_LISTS L ,
SOCCER_PREMATCH_MATCHES M
WHERE M.LIST LIKE '%' || (L.SUB_LIST) || '%'
AND (TO_TIMESTAMP((M.M_DATE || ' ' || M.M_TIME), 'DD.MM.YYYY HH24:MI') >
(SELECT SYSTIMESTAMP AT TIME ZONE 'CET' FROM DUAL
))
ORDER BY L.ID");
Return list;
End;
Note here if the column size increased 2000 chars then again you will lose the data.
Edit:
From your comments
I want it to return a table set of results.
You then need to create a table of varchar and then return it from the function. See below:
CREATE TYPE var IS TABLE OF VARCHAR2(2000);
/
CREATE OR REPLACE
FUNCTION getSoccerLists
RETURN var
IS
--Initialization
list VAR :=var();
BEGIN
SELECT NSO ||',' ||NAME BULK COLLECT INTO LIST FROM TEST;
RETURN list;
END;
Execution:
select * from table(getSoccerLists);
Note: Here in the function i have used a table called test and its column. You replace your table with its columnname.
Edit 2:
--Create a object with columns same as your select statement
CREATE TYPE v_var IS OBJECT
(
col1 NUMBER,
col2 VARCHAR2(10)
)
/
--Create a table of your object
CREATE OR REPLACE TYPE var IS TABLE OF v_var;
/
CREATE OR REPLACE FUNCTION getSoccerLists
RETURN var
IS
--Initialization
list VAR :=var();
BEGIN
--You above object should have same columns with same data type as you are selecting here
SELECT v_var( NSO ,NAME) BULK COLLECT INTO LIST FROM TEST;
RETURN list;
END;
Execution:
select * from table(getSoccerLists);
This is not an answer on how to build a function for this, as I'd recommend to make this a view instead:
CREATE OR REPLACE VIEW view_soccer_list AS
SELECT *
FROM soccer_prematch_lists l
WHERE EXISTS
(
SELECT *
FROM soccer_prematch_matches m
WHERE m.list LIKE '%' || (l.sub_list) || '%'
AND TO_TIMESTAMP((m.m_date || ' ' || m.m_time), 'DD.MM.YYYY HH24:MI') >
(SELECT SYSTIMESTAMP AT TIME ZONE 'CET' FROM DUAL)
);
Then call it in a query:
SELECT * FROM view_soccer_list ORDER BY id;
(It makes no sense to put an ORDER BY clause in a view, because you access the view like a table, and table data is considered unordered, so you could not rely on that order. The same is true for a pipelined function youd access with FROM TABLE (getSoccerLists). Always put the ORDER BY clause in your final queries instead.)

Calling Stored procedure with 'IS IN' statement in Oracle

I have following issue. I need to filter fetching data in stored procedure:
SELECT * FROM tab WHERE post_code IS IN ('pc1', 'pc2', 'pc3');
My question is: how to pass arguments pc1, pc2, pc3... into stored procedure?
As an array or as string?
When I try pass as string I have problem with apostrophes.
Passing array in my opinion is not good because of performance...I will need to create for loop and create string which will be passed like this:
SELECT * FROM tab WHERE post_code IS IN (post_codes);
How to do it right?
Not sure why you think passing an array would lead to a performance issue.
This will work:
declare
post_codes sys.dbms_debug_vc2coll := new sys.dbms_debug_vc2coll ('pc1', 'pc2', 'pc3');
lrec tab%rowtype;
begin
select * into lrec
where post_code in ( select * from table(post_codes));
end;
/
This is proof of concept only. It will hurl TOO_MANY_ROWS exception if the query returns more than one row. As you haven't provided any context for what you are trying to achieve I haven't bothered to invent anything exra.
The easiest way, in my opinion is to use SQL to split up your string:
with id_generator
as
(
SELECT regexp_substr(:txt, '[^,]+', 1, LEVEL) token
FROM dual
CONNECT BY LEVEL <= length(:txt) - length(REPLACE(:txt, ',', '')) + 1
)
select u.id, u.username
from users u, id_generator g
where u.id = g.token;
This query looks a little scary, but you can take the top part and run it in isolation to see what it does:
SELECT regexp_substr(:txt, '[^,]+', 1, LEVEL) token
FROM dual
CONNECT BY LEVEL <= length(:txt) - length(REPLACE(:txt, ',', '')) + 1
This generates a row per item in your comma separated list, which you can then join to the other table to get your results.
More discussion on this problem on my blog - http://betteratoracle.com/posts/20-how-do-i-bind-a-variable-in-list

Oracle comma separated parameter to row and join with other tables

I am using Oracle 10g.
My Scenario:
I am getting some more than 4000 records in a comma separated string('ord0000,ord0001,ord0002,......') as a parameter. I need to compare these values against a table1 and find out the matching recordset.
For that purpose I created a function as below:
function get_split_values (
csv_string varchar2
) return split_table pipelined
as
Delimit_String varchar2(32767) := csv_string;
Delimit_index integer;
begin
loop
Delimit_index := instr(delimit_string,',');
if Delimit_index > 0 then
pipe row(substr(delimit_string,1,delimit_index-1));
delimit_string := substr(delimit_string,delimit_index+1);
else
pipe row(delimit_string);
exit;
end if;
end loop;
return;
end get_split_values;
Now when I used this function to join with my table1 in a procedure as below:
create procedure abc (parameter_csv varchar2,...)
as
begin
open cursor for
select t.col1 from table1 t join table(get_split_values(parameter_csv)) x
on x.column_value = t.col1;
...
end abc;
It works fine when the parameter_csv have some around 300 or 400 IDs like('ord0000,ord0001,ord0002,......') but when it contains more that records I got the error
"ORA 01460 : unimplemented or unreasonable conversion requested."
I don't understand what raise this error. Any ideas?
OR is there any best way to accomplish this task.
Initially I thought you were overflowing your varchar2(32767) but a quick look at your sample IDs indicates that you shouldn't be maxing out that early(400 ids).
A quick google of the error led me to this forum in OTN: http://forums.oracle.com/forums/thread.jspa?threadID=507725&start=15&tstart=0
And to this blog post: http://oraclequirks.blogspot.com/2008/10/ora-01460-unimplemented-or-unreasonable.html
which indicates that this might be an oracle bug
If its a bug with using the PL/SQL procedure, you can just split the string as part of an inline view. Something like.
SELECT T.col1
FROM table1 T
JOIN ( SELECT REGEXP_SUBSTR( parameter_csv, '[^,]+', 1, LEVEL ) AS id
FROM DUAL
CONNECT BY LEVEL <=
LENGTH( REGEXP_REPLACE( parameter_csv, '[^,]+', '' ) ) + 1
) X
ON X.id = T.col1;
NOTE: Doesn't handle things like duplicate IDs in the the csv, empty values in the csv ,, etc