Make in (SQL) dynamic for incoming values - sql

Is it possible for in statement to be dynamic? like dynamic comma separation
for example:
DATA=1
select * from dual
where
account_id in (*DATA);
DATA=2
select * from dual
where
account_id in (*DATA1,*DATA2);
FOR DATA=n
how will i make the in statement dynamic/flexible (comma) for unknown quantity.
select * from dual
where
account_id in (*DATAn,*DATAn+1,etc);

A hierarchical query might help.
acc CTE represents sample data
lines #9 - 11 are what you might be looking for; data is concatenated with level pseudocolumn's value returned by a hierarchical query
Here you go:
SQL> with acc (account_id) as
2 (select 'data1' from dual union all
3 select 'data2' from dual union all
4 select 'data3' from dual union all
5 select 'data4' from dual
6 )
7 select *
8 from acc
9 where account_id in (select 'data' || level
10 from dual
11 connect by level <= &n
12 );
Enter value for n: 1
ACCOU
-----
data1
SQL> /
Enter value for n: 3
ACCOU
-----
data1
data2
data3
SQL>

As I can see, You are using the number in Where clause, Substitution will be enough to solve your problem.
See the example below:
CREATE table t(col1 number);
insert into t values(1);
insert into t values(2);
insert into t values(3);
-- Substitution variable initialization
define data1=1;
define data2='1,2';
--
-- With data1
select * from t where col1 in (&data1);
Output:
-- With data2
select * from t where col1 in (&data2);
Output:
Hope, This will be helpful to you.
Cheers!!

The basic problem is not the listagg function but a major misconception that just because elements in a string list are comma separated that a string with commas in it is a list. Not So. Consider a table with the following rows.
Key
- Data1
- Data2
- Data1,Data2
And the query: Select * from table_name where key = 'wanted_key'; Now if all commas separate independent elements then what value in for "wanted_Key" is needed to return only the 3rd row above? Even with the IN predicate 'Data1,Data2' is still just 1 value, not 2. For 2 values it would have to be ('Data1','Data2').
The problem you're having with Listagg is not because of the comma but because it's not the appropriate function. Listagg takes values from multiple rows and combines then into a single comma separated string but not comma separated list. Example:
with elements as
( select 'A' code, 'Data1' item from dual union all
select 'A', 'Data2' from dual union all
select 'A', 'Data3' from dual
)
select listagg( item, ',') within group (order by item)
from elements group by code;
(You might also want to try 'Data1,Data2' as a single element. Watch out.
What you require is a query that breaks out each element separately. This can be done with
with element_list as
(select 'Data1,Data2,Data3' items from dual) -- get paraemter string
, parsed as
(select regexp_substr(items,'[^,]+',1,level) item
from element_list connect by regexp_substr(items,'[^,]+',1,level) is not null -- parse string to elements
)
The CTE "parsed" can now be used as table/view in your query.
This will not perform as well as querying directly with a parameter, but performance degradation is the cost of dynamic/flexible queries.
Also as set up this will NOT handle parameters which contain commas within an individual element. That would require much more code as you would have to determine/design how to keep the comma in those elements.

Related

Oracle Apex decoding checkbox created list of return values into display values for a report

Oracle Apex checkbox return values are saved in a table as colon delineated list:
01:02:03:04
And that is how they will appear in a report column, however I want to decode those values back into there display values:
Apple:Banana:Carrot:Durian
If this return values where from a select list I would use the following sql:
select display, return
from lov_list
where type = 'fruit'
However this obliviously returns an ORA-01722: invalid number error.
The first approach I tried was maybe nested replace functions:
replace(replace(replace(replace(fruit_column, '01', Apple), '02', 'Banana'), '03', 'Carrot'), '04', 'Durian')
This works, but is not "dynamic" as in the future I would like to be able to add new values to the lov_list table and not have to update or add more nested replace functions especially if I now have over 10 new values.
I theory what I would like to do is the following:
select replace(ft.fruit_column, ll.return, ll.display)
from fruit_table ft
left join lov_list ll
on ft.fruit_column like ('%'||ll.return||'%')
But this only works for same of the values:
Apple:02:03:04
02:Carrot
etc.
I've looked multiple replace functions that other users have created, but I don't think they'll work for particular problem, not "dynamically" anyways, unless I'm missing something in those functions and they in fact could work, I'm not sure.
Is they another approach I can take?
You should first split colon-delimited values into rows, then join those values with table that contains fruit names, and - finally - aggregate the result back into a colon-delimited list of NAMES.
SQL> with
2 chbox (val) as
3 (select '01:02:03' from dual),
4 fruits (id, name) as
5 (select '01', 'Apple' from dual union all
6 select '02', 'Banana' from dual union all
7 select '03', 'Carrot' from dual
8 ),
9 temp as
10 -- first split colon-delimited value into rows
11 (select level lvl,
12 regexp_substr(val, '[^:]+', 1, level) id
13 from chbox
14 connect by level <= regexp_count(val, ':') + 1
15 )
16 -- finally, join TEMP ID's with FRUITS; aggregate names
17 select listagg(f.name, ':') within group (order by t.lvl) result
18 from fruits f join temp t on t.id = f.id;
RESULT
------------------------------
Apple:Banana:Carrot
SQL>
Code you need begins at line #9 (just precede temp CTE name with the with keyword).
I don't quite get your data model, but I'm a fan of using the apex_string package to convert delimited strings into rows.
For example
select display, return
from lov_list
where type = 'fruit'
and return in (
select column_value
from apex_string.split('01:02:03:04', ':')
)

SQL Query to select a specific part of the values in a column

I have a table in a database and one of the columns of the table is of the format AAA-BBBBBBB-CCCCCCC(in the table below column Id) where A, B and C are all numbers (0-9). I want to write a SELECT query such that for this column I only want the values in the format BBBBBBB-CCCCCCC. I am new to SQL so not sure how to do this. I tried using SPLIT_PART on - but not sure how to join the second and third parts.
Table -
Id
Name
Age
123-4567890-1234567
First Name
199
456-7890123-4567890
Hulkamania
200
So when the query is written the output should be like
Output
4567890-1234567
7890123-4567890
As mentioned in the request comments, you should not store a combined number, when you are interested in its parts. Store the parts in separate columns instead.
However, as the format is fixed 'AAA-BBBBBBB-CCCCCCC', it is very easy to get the substring you are interested in. Just take the string from the fifth position on:
select substr(col, 5) from mytable;
You can select the right part of a column starting at the 4th character
SELECT RIGHT(Id, LEN(Id)-4) AS TrimmedId;
Another option using regexp_substr
with x ( c1,c2,c3 ) as
(
select '123-4567890-1234567', 'First Name' , 199 from dual union all
select '456-7890123-4567890', 'Hulkamania' , 200 from dual
)
select regexp_substr(c1,'[^-]+',1,2)||'-'||regexp_substr(c1,'[^-]+',1,3) as result from x ;
Demo
SQL> with x ( c1,c2,c3 ) as
(
select '123-4567890-1234567', 'First Name' , 199 from dual union all
select '456-7890123-4567890', 'Hulkamania' , 200 from dual
)
select regexp_substr(c1,'[^-]+',1,2)||'-'||regexp_substr(c1,'[^-]+',1,3) as result from x ; 2 3 4 5 6
RESULT
--------------------------------------------------------------------------------
4567890-1234567
7890123-4567890
SQL>

how to select a list of 10,000 unique ids from dual in oracle SQL

So I can't create or edit tables (I'm a user with read only permission) and I want to look up 10,000 unique id's. I can't put them inside of an IN() statement because oracle limits over 1000 items.
Is it possible to select this entire list from the DUAL table in oracle? Something like:
select
'id123,id8923,id32983,id032098,id308230,id32983289'
from DUAL
Use a collection (they are not limited to 1000 items like an IN clause is):
SELECT COLUMN_VALUE AS id
FROM TABLE(
SYS.ODCIVARCHAR2LIST(
'id123', 'id8923', 'id32983', 'id032098', 'id308230', 'id32983289'
)
)
SYS.ODCIVARCHAR2LIST and SYS.ODCINUMBERLIST are collection types that are supplied in the SYS schema.
You can join this directly to whichever table you are SELECTing from without needing to use the DUAL table:
SELECT y.*
FROM your_table y
INNER JOIN TABLE(
SYS.ODCIVARCHAR2LIST(
'id123', 'id8923', 'id32983', 'id032098', 'id308230', 'id32983289'
)
) i
ON (y.id = i.COLUMN_VALUE);
If you can get a collection type created then you do not even need the TABLE expression and can use it directly in the WHERE clause using the MEMBER OF operator:
CREATE OR REPLACE TYPE stringlist IS TABLE OF VARCHAR2(200);
/
SELECT *
FROM yourtable
WHERE id MEMBER OF stringlist(
'id123', 'id8923', 'id32983', 'id032098', 'id308230', 'id32983289'
);
You can even pass the values as a bind parameter - see my answer here
Oracle still doesn't support the VALUES row constructor, so there are only two ugly workarounds:
The 1000 item limit does not apply for multi-column IN conditions
Expression Lists
A comma-delimited list of expressions can contain no more than 1000
expressions. A comma-delimited list of sets of expressions can contain
any number of sets, but each set can contain no more than 1000
expressions.
so you can do:
where (1,id) in ( (1,'id123'),
(1,'id8923'),
(1,'id32983'),
(1,'id032098'), .... )
Or using a big ugly UNION ALL:
with idlist (xid) as (
select 'id123' from dual union all
select 'id8923' from dual union all
.....
select 'id32983' from dual
)
select ...
from some_table
where id in (select xid from idlist);
One solution is the WITH clause:
with ids as (
select 'id123' as uid from dual union all
select 'id8923' as uid from dual union all
select 'id32983' as uid from dual union all
select 'id032098' as uid from dual union all
select 'id308230' as uid from dual union all
select 'id32983289' as uid from dual
)
select *
from ids
join your_table yt
on yt.id = ids.uid
This may seem like a bit of a chore but presumably you have your list of UIDs in a spreadsheet or whatever. If so it's a cinch to generate those select statements using regular expressions. Just cut'n'paste the column into an editor which supports regex search and replace.
Yet another work-around
select *
from t
where id in ('id1','id2','id3',...,'id1000')
or id in ('id1001','id1002','id1003',...,'id2000')
or id in ('id2001','id2002','id2003',...,'id3000')
or ...

find all comma delimited values from a varchar2 datatype column

I am not sure whether this is possible or not to find all the comma delimited values from a varchar2 datatype column.
id test
----------------------
1 1
2 3,10,15,20
3 2
4 3,15
If my table is as above then i want to return only row 2 and 4 as below i.e. result of query should look like below:
rownum test
----------------------
1 3,10,15,20
2 3,15
find all the comma delimited values from a number datatype column.
It is impossible to store comma-delimited values in a NUMBER data type column. It has to be character type.
Simply use INSTR in the filter predicate:
SELECT ID, TEST FROM your_table WHERE instr(TEST, ',') > 0;
For example,
SQL> WITH DATA AS
2 ( SELECT 1 ID, '1' TEST FROM dual
3 UNION ALL
4 SELECT 2 , '3,10,15,20' FROM dual
5 UNION ALL
6 SELECT 3 , '2' FROM dual
7 UNION ALL
8 SELECT 4 , '3,15' FROM dual
9 )
10 SELECT ID, TEST FROM DATA WHERE instr(TEST, ',') > 0;
ID TEST
---------- ----------
2 3,10,15,20
4 3,15
INSTR(test, ',') looks for the occurrence of comma , in the delimited-string. > 0 will filter those rows.
NOTE : The WITH clause is only to build the sample data for demonstration.
You can find it by using REGEXP function. A regular expression is a powerful way of specifying a pattern for a complex search.
So query to get result specific to is as :
select id, test from Table_name where test REGEXP '[,]'
For more details regarding REGEX : Go to dev.Mysql

Determine which values are missing from an Oracle database column

I have a list of values and I want to query which values in that list DO NOT appear in a particular column in an Oracle database (not sure which version).
So for example if my list of values is A,B,C and I have a table as below :
--------
|COLUMN|
--------
| C|
| A|
--------
The result I would expect would be B.
So far my approach has been a SQL query similar to the below :
SELECT <<List of values SQL, not sure what goes here>>
EXCEPT
SELECT column FROM table
However I do not know what the SQL for the first statement looks like.
So far I came up with :
SELECT "A","B","C" FROM dual
But this doesn't have the desired effect as it creates 3 columns
Another point to mention is that in the actual problem there are around 100 entries in the list to search, not the three in the toy example above.
Maybe this helps:
WITH static_list AS (
SELECT 'A' AS v FROM dual UNION ALL
SELECT 'B' AS v FROM dual UNION ALL
SELECT 'C' AS v FROM dual
)
SELECT v FROM static_list
MINUS (SELECT column
FROM table);
Dirk's solution works fine. However, Regular Expressions can also be used for achieving this goal for Oracle 10g and above:
SELECT trim(regexp_substr(str, '[^,]+', 1, level)) str
FROM (SELECT 'A,B,C,D' str from dual)
CONNECT BY instr(str, ',', 1, level - 1) > 0
Minus
Select column from table