Looping through string splitted in Oracle - sql

I have a query which returns a data from a column[datatype varchar2] in string format.
select AuditEvents.BeforeValue from AuditEvents where condition......
Now , AuditEvents.BeforeValue has a mix type of data like null or "1,2,3,4,5" or "2,3,1,4" or "3,14" or "11:10" or "Security" --without quotes. These "1,2,3,4,5" are eventId in another table 'EventDesc'.
AuditEvents.BeforeValue column data depends on another column, AuditEvents.EventyType which is varchar. In EventyType column, if data is "ExcludedEvents" then BeforeValue should fetch data from another table EventDesc which has eventId description. Like 1=Event1 2=Event2 etc.So in case of "1,2,3" it should fetch data as "Event1,Event2,Event3"
My query is is it possible to fetch data based on above scenario directly in oracle query rather than doing in c# side(as I'm having alot of trouble when Ui asks for searching or sorting)

Is it possible? Yes, with some trouble because not all values exist in your events table, nor are all of them valid.
See this example, read comments within code.
SQL> with
2 -- sample data
3 events (id, name) as
4 (select 1, 'Event 1' from dual union all
5 select 2, 'Event 2' from dual union all
6 select 3, 'Event 3' from dual
7 ),
8 auditevents (beforevalue) as
9 (select '2,3,1' from dual union all
10 select 'security' from dual union all
11 select '11:10' from dual
12 ),
13 -- query you might need begins here
14 -- split AUDITEVENTS to rows
15 ae_split as
16 (select beforevalue,
17 regexp_substr(beforevalue, '[^,]+', 1, column_value) id
18 from auditevents cross join
19 table(cast(multiset(select level from dual
20 connect by level <= regexp_count(beforevalue, ',') + 1
21 ) as sys.odcinumberlist))
22 )
23 -- join split AUDITEVENTS with the EVENTS table
24 select s.beforevalue, listagg(e.name, ', ') within group (order by e.id) events
25 from ae_split s join events e on to_char(e.id) = s.id
26 where exists (select null from events a where to_char(a.id) = s.id)
27 group by s.beforevalue;
BEFOREVALUE EVENTS
----------- ------------------------------
2,3,1 Event 1, Event 2, Event 3
SQL>

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', ':')
)

Oracle - Split the parameter by comma and check if the parameter exist in Column

I am new to Oracle and not sure if there are any inbuilt functions to do this task.
I have a column that contains Product_ID's separated by comma.
Product_ID
123,234,546,789,487
I am passing a list of Product_ID's separated by a comma as varchar2.
so, I am passing "234,789" as varchar2.
I want to find if 234 and 789 exist in that column and if exists get that row.
How can I do this?
If you want to check that all the values in your input list are included in the column then you can use:
SELECT *
FROM table_name t
WHERE EXISTS (
WITH input ( value ) AS (
SELECT '123,789' FROM DUAL -- Your input value
)
SELECT 1
FROM input
WHERE ','||t.product_id||',' LIKE '%,' || REGEXP_SUBSTR( value, '[^,]+', 1, LEVEL ) || ',%'
CONNECT BY LEVEL <= REGEXP_COUNT( value, '[^,]+' )
HAVING COUNT(*) = REGEXP_COUNT( value, '[^,]+' )
)
Which, for the sample data:
CREATE TABLE table_name ( Product_ID ) AS
SELECT '123,234,546,789,487' FROM DUAL
Outputs:
| PRODUCT_ID |
| :------------------ |
| 123,234,546,789,487 |
If you want to check that at least one value in your input list is in the column then you can use the same query without the line containing the HAVING clause.
db<>fiddle here
Here's one way - making the comma-separated number lists into JSON arrays so that we can split them using json_table, then re-aggregating as nested tables so that we can compare with the submultiset operator:
create type table_of_pid as table of number;
/
with
sample_data (product_id) as (
select '123,234,546,789,487' from dual union all
select '333,444,555,666,888' from dual
)
, user_input (product_list) as (
select '234,789' from dual
)
select *
from sample_data
where ( select cast(collect(pid) as table_of_pid) as input_pid
from user_input cross apply
json_table('[' || product_list || ']', '$[*]'
columns pid number path '$')
)
submultiset
( select cast(collect(pid) as table_of_pid) as input_pid
from json_table('[' || product_id || ']', '$[*]'
columns pid number path '$')
)
;
PRODUCT_ID
-------------------
123,234,546,789,487
Your inputs violate First Normal Form, the most basic sanity requirement in a relational database. If the data was in normal form, you wouldn't need any of the JSON trickery. Still, the aggregation into collection and the submultiset comparison would be the correct approach even if the data was already in normal form.
It is a bad idea to store comma-separated values into a single column.
One option to do what you asked for is in the following example; read comments within code. Note that - for large tables - performance WILL suffer.
SQL> set ver off
SQL>
SQL> with
2 test (product_id) as
3 -- your sample table
4 (select '123,234,546,789,487' from dual union all
5 select '111,222,333' from dual
6 ),
7 test_split as
8 -- you have to split it into rows
9 (select product_id,
10 regexp_substr(product_id, '[^,]+', 1, column_value) val
11 from test
12 cross join table(cast(multiset(select level from dual
13 connect by level <= regexp_count(product_id, ',') + 1
14 ) as sys.odcinumberlist))
15 ),
16 parameter_split as
17 -- split input parameter into rows as well
18 (select regexp_substr('&&par_id', '[^,]+', 1, level) val
19 from dual
20 connect by level <= regexp_count('&&par_id', ',') + 1
21 )
22 -- join split values, return the result
23 select distinct t.product_id
24 from test_split t join parameter_split p on p.val = t.val;
Enter value for par_id: 123,546
PRODUCT_ID
-------------------
123,234,546,789,487
SQL> undefine par_id
SQL> /
Enter value for par_id: 333
PRODUCT_ID
-------------------
111,222,333
SQL>

How to check if the length of a string is more than one word and keep only the first word else keep the entire string in SQL?

I have the following table in sql.
I want to keep only the first word in the Name column. I have written the code below however when I run it it extracts the first word for strings longer that one word but returns empty cell for strings which consist of one word only. Could you please advise me how should I modify it to achieve the desired result of keeping only the first word of all strings.
SELECT ID,substr(Name, 1, instr ( Name, ' ' ) -1 ) AS Name FROM names_list
DBMS Toad for Oracle
How about regexp_substr()?
select regexp_substr(name, '^[^ ]+')
from names_list;
This is more flexible than instr(), because you have more control over the separators. For instance, if a comma is sometimes used as well:
select regexp_substr(name, '^[^ ,]+')
from names_list;
This would select the first word out of the name column:
SQL> with names_list (id, name) as
2 (select 1, 'John Smith' from dual union all
3 select 2, 'One' from dual union all
4 select 3, 'Nikola O''Neil' from dual union all
5 select 4, 'Rose Ann Lee' from dual union all
6 select 5, 'Neil' from dual union all
7 select 6, 'William Hugh Forest' from dual union all
8 select 7, 'Andrew' from dual
9 )
10 select id,
11 regexp_substr(name, '^\w+') name
12 from names_list;
ID NAME
---------- --------------------
1 John
2 One
3 Nikola
4 Rose
5 Neil
6 William
7 Andrew
7 rows selected.
SQL>

Oracle db query for regex is not giving correct result

I am using Oracle database and table
-----------------------------
ID : NAME
------------------------------
Now I need to know know how many name follow folling condition
Can contain A to Z
Can contain a to z
Can contain _
I have written query
SELECT *
FROM REGEX_TEST
WHERE REGEXP_LIKE (name,'[A-Za-z0-9_]')
But this is not giving me correct result
Sample result which I want
ID Text Expected result
1 PLAN_20001 PASS
2 A937AH PASS
3 556679815 PASS
4 A93_7AH PASS
5 PLANavd20001 PASS
6 A93*7AH FAIL
7 A93%7AH FAIL
8 A93^7AH FAIL
9 A93$7AH FAIL
10 A93#7AH FAIL
11 A93!7AH FAIL
12 A93~7AH FAIL
13 A93+7AH FAIL
//------------ RESULT -----------
1 PLAN20001
2 A937AH
3 556679815
4 A93 7AH
5 PLANavd20001
6 A93*7AH
7 A93%7AH
8 A93^7AH
9 A93$7AH
10 A93#7AH
11 A93!7AH
12 A93~7AH
13 A93+7AH
At the moment your pattern is matching any character in the name; it will only exclude values that do not contain any of those ranges at all, rather than what you seem to want which is to exclude values that contain anything else. So you need to anchor the pattern with ^ and $, and add a quantifier to allow the pattern to be repeated, with either * or ?. So the pattern becomes '^[A-Za-z0-9_]*$'.
Demo with a CTE to represent your sample data:
WITH REGEX_TEST (id, name) AS (
select 1, 'PLAN20001' from dual
union all select 2,'A937AH' from dual
union all select 3, '556679815' from dual
union all select 4, 'A93 7AH' from dual
union all select 5, 'PLANavd20001' from dual
union all select 6, 'A93*7AH' from dual
union all select 7, 'A93%7AH' from dual
union all select 8, 'A93^7AH' from dual
union all select 9, 'A93$7AH' from dual
union all select 10, 'A93#7AH' from dual
union all select 11, 'A93!7AH' from dual
union all select 12, 'A93~7AH' from dual
union all select 13, 'A93+7AH' from dual
)
SELECT *
FROM REGEX_TEST
WHERE REGEXP_LIKE (name, '^[A-Za-z0-9_]*$');
ID NAME
---------- ------------
1 PLAN20001
2 A937AH
3 556679815
5 PLANavd20001
I've left the numeric range 0-9 in despite you saying twice that you only want letters and underscores, as your reply to David Aldridge then said you did want numbers too.
You could also use the alphanumeric class instead of your ranges, which handles other languages more safely (depending on what you want to match, of course):
WHERE REGEXP_LIKE (name, '^[[:alnum:]_]*$');
Or the Perl-influenced \w operator:
WHERE REGEXP_LIKE (name, '^\w*$');
Or the not-word operator, which doesn't need to be anchored:
WHERE NOT REGEXP_LIKE (name, '\W');

Split one row into multiple rows (fixed width)

One row of incoming record has multiple sub-records concatenated. Each sub record is 9 characters long. I have 8 such sub-records in each row. So each row is (8x9=72 char). Is there a way i can split 1 record into 8 records here?
Input
123456789123456789123456789123456789123456789123456789123456789123456789
Output
123456789
123456789
123456789
123456789
123456789
123456789
123456789
123456789
I know i can do this with 8 sub queries and union them .. is there a better way?
or how about this:
SELECT substr(t1.astr,n*9+1,9)
FROM t1,
(SELECT ROWNUM-1 n FROM dual CONNECT BY LEVEL <= 8) t2
table t1 contains your strings, table t2 is a generated list of sequential numbers from 0 to 7 by cross joining it to the t1 table we can use it to cut up the string column.
I've seen people use a secondary table containing only the values 1 through 8, then joining with that table. You can use the value to substring your record.
Something like
SELECT SUBSTRING(long_val, eight.value * 9, 9)
FROM mytable, eight
Note: Query untested, but I hope the idea gets across.
If it is predictable that each line will always split into a fixed number of lines, something like the following should be possible:
select
b.iterator,
substr(a.mystring,b.iterator*9-8,9) as split
from
(select '123456789123456789123456789123456789123456789123456789123456789123456789' as mystring from dual) a,
(select 1 as iterator from dual union
select 2 as iterator from dual union
select 3 as iterator from dual union
select 4 as iterator from dual union
select 5 as iterator from dual union
select 6 as iterator from dual union
select 7 as iterator from dual union
select 8 as iterator from dual) b
EDIT: Kevin's iterator was simpler and better than my brute force version. The b subquery shoud be (SELECT ROWNUM as iterator FROM dual CONNECT BY LEVEL <= 8)