oracle 10g IN clause query - oracle

Please ignore obvious syntax flaws in the below:
I have an sql like this as a named query:
select saalry from emp where emp_id in (:id)
id is of type number
I wanted to pass in a comma separated list like this:
String id = 121,123,456
But I am getting ORA-01722: invalid number
How can I pass a comma separated list of ids to my IN clause?

Assuming :id is a string containing a relatively short comma-delimited list of numbers (e.g. '123,456,789'), this may be sufficient for you:
select saalry from emp
where INSTR( ',' || :id || ','
, ',' || TRIM(TO_CHAR(emp_id)) || ','
) > 0;
It won't perform as well, however, since it is unlikely to use an index on emp_id.

There is another way that is from http://blogs.oracle.com/aramamoo/entry/how_to_split_comma_separated_string_and_pass_to_in_clause_of_select_statement
Their example is
select regexp_substr('SMITH,ALLEN,WARD,JONES','[^,]+', 1, level) from dual
connect by regexp_substr('SMITH,ALLEN,WARD,JONES', '[^,]+', 1, level) is not null;
Which can be put into an in clause
select * from emp where ename in (
select regexp_substr('SMITH,ALLEN,WARD,JONES','[^,]+', 1, level) from dual
connect by regexp_substr('SMITH,ALLEN,WARD,JONES', '[^,]+', 1, level) is not null );

Related

How to convert delimited string to a PL/SQL table for JOINing?

I have the following table:
CREATE TABLE T_DATA
(
id VARCHAR2(20),
value VARCHAR2(30),
index NUMBER,
valid_from DATE,
entry_state VARCHAR2(1),
CONSTRAINT PK_T_DATA PRIMARY KEY(id, value)
);
and I have the following string:
id1:value1,id2:value2,id3:value3...
where id and value are actually corresponding values on T_DATA. I'm expected to use that string and return a resultset from T_DATA usind the ids and values provided as filters (basically, a select). I was told I can convert the string into a PL/SQL table with the two columns and with that, a simple SELECT * FROM T_DATA INNER JOIN [PL/SQL table] ON [fields] will retrieve the rows required, but I can't find out how to convert the string to a PL/SQL table with multiple columns. How can I do it?
The simplest solution I can think of (although it may not be the most efficient) is to just use a simple INSTR
WITH
t_data
AS
( SELECT 'id' || ROWNUM AS id,
'value' || ROWNUM AS VALUE,
ROWNUM AS index_num,
SYSDATE - ROWNUM AS valid_from,
'A' AS entry_state
FROM DUAL
CONNECT BY LEVEL <= 10)
SELECT *
FROM t_data
WHERE INSTR ('id1:value1,id3:value3', id || ':' || VALUE) > 0;
If you want to split the search string, you can try a query like this one
WITH
t_data
AS
( SELECT 'id' || ROWNUM AS id,
'value' || ROWNUM AS VALUE,
ROWNUM AS index_num,
SYSDATE - ROWNUM AS valid_from,
'A' AS entry_state
FROM DUAL
CONNECT BY LEVEL <= 10),
split_string AS (SELECT 'id1:value1,id3:value3' AS str FROM DUAL),
split_data as (
SELECT substr(regexp_substr(str, '[^,]+', 1, LEVEL),1,instr(regexp_substr(str, '[^,]+', 1, LEVEL), ':') - 1) as id,
substr(regexp_substr(str, '[^,]+', 1, LEVEL),instr(regexp_substr(str, '[^,]+', 1, LEVEL), ':') + 1) as value
FROM split_string
CONNECT BY INSTR (str, ',', 1, LEVEL - 1) > 0)
SELECT t.*
FROM t_data t
join split_data s
on( t.id = s.id and t.value = s.value);
You can use the query using LIKE as follows:
SELECT *
FROM T_DATA
WHERE ',' || YOUR_STRING || ',' LIKE '%,' || ID || ':' || VALUE || ',%'

How to get character or string after nth occurrence of pipeline '|' symbol in ORACLE using REGULAR_EXPRESSION?

What is the regular expression query to get character or string after nth occurrence of pipeline | symbol in ORACLE? For example I have two strings as follows,
Jack|Sparrow|17-09-16|DY7009|Address at some where|details
|Jack|Sparrow|17-09-16||Address at some where|details
I want 'DY7009' which is after 3rd pipeline symbol starting from 1st position, So what will be regular expression query for this? And in second string suppose that 1st position having | symbol, then I want 4th string if there is no value then it should give NULL or BLANK value.
select regexp_substr('Jack|Sparrow|17-09-16|DY7009|Address at some where|details'
,' ?? --REX Exp-- ?? ') as col
from dual;
Result - DY7009
select regexp_substr('Jack|Sparrow|17-09-16|DY7009|Address at some where|details'
,' ?? --REX Exp-- ?? ') as col
from dual;
Result - '' or (i.e. NULL)
So what should be the regexp? Please help. Thank you in Advance
NEW UPDATE Edit ---
Thank you all guys!!, I appreciate your answer!!. I think, I didn't ask question right. I just want a regular expression to get 'string/character string' after nth occurrence of pipeline symbol. I don't want to replace any string so only regexp_substr will do the job.
----> If 'Jack|Sparrow|SQY778|17JULY17||00J1' is a string
I want to find string value after 2nd pipe line symbol here the answer will be SQY778. If i want to find string after 3rd pipeline symbol then answer will be 17JULY17. And if I want to find value after 4th pipeline symbol then it should give BLANK or NULL value because there is nothing after 4th pipeline symbol. If I want to find string 5th symbol then I will only replace one digit in Regular expression i.e. 5 and I will get 00J1 as a result.
Here ya go. Replace the 4th argument to regexp_substr() with the number of the field you want.
with tbl(str) as (
select 'Jack|Sparrow|17-09-16|DY7009|Address at some where|details ' from dual
)
select regexp_substr(str, '(.*?)(\||$)', 1, 4, NULL, 1) field_4
from tbl;
FIELD_4
--------
DY7009
SQL>
To list all the fields:
with tbl(str) as (
select 'Jack|Sparrow|17-09-16|DY7009|Address at some where|details ' from dual
)
select regexp_substr(str, '(.*?)(\||$)', 1, level, NULL, 1) split
from tbl
connect by level <= regexp_count(str, '\|')+1;
SPLIT
-------------------------
Jack
Sparrow
17-09-16
DY7009
Address at some where
details
6 rows selected.
SQL>
So if you want select fields you could use:
with tbl(str) as (
select 'Jack|Sparrow|17-09-16|DY7009|Address at some where|details ' from dual
)
select
regexp_substr(str, '(.*?)(\||$)', 1, 1, NULL, 1) first,
regexp_substr(str, '(.*?)(\||$)', 1, 2, NULL, 1) second,
regexp_substr(str, '(.*?)(\||$)', 1, 3, NULL, 1) third,
regexp_substr(str, '(.*?)(\||$)', 1, 4, NULL, 1) fourth
from tbl;
Note this regex handles NULL elements and will still return the correct value. Some of the other answers use the form '[^|]+' for parsing the string but this fails when there is a NULL element and should be avoided. See here for proof: https://stackoverflow.com/a/31464699/2543416
Don't have enough reputation to comment on Chris Johnson's answer so adding my own. Chris has the correct approach of using back-references but forgot to escape the Pipe character.
The regex will look like this.
WITH dat
AS (SELECT 'Jack|Sparrow|17-09-16|DY7009|Address at some where|details' AS str,
3 AS pos
FROM DUAL
UNION
SELECT ' |Jack|Sparrow|17-09-16||Address at some where|details' AS str,
4 AS pos
FROM DUAL)
SELECT str,
pos,
REGEXP_REPLACE (str, '^([^\|]*\|){' || pos || '}([^\|]*)\|.*$', '\2')
AS regex_result
FROM dat;
I'm creating the regex dynamically by adding the position of the Pipe character dynamically.
The result looks like this.
|Jack|Sparrow|17-09-16||Address at some where|details (4):
Jack|Sparrow|17-09-16|DY7009|Address at some where|details (3): DY7009
You can use regex_replace to get the nth matching group. In your example, the fourth match could be retrieved like this:
select regexp_replace(
'Jack|Sparrow|17-09-16|DY7009|Address at some where|details',
'^([^\|]*\|){3}([^\|]*)\|.*$',
'\4'
) as col
from dual;
Edit: Thanks Arijit Kanrar for pointing out the missing escape characters.
To OP: regex_replace doesn't replace anything in the database, only in the returned string.
You can use this query to get the value at the specific column ( nth occurrence ) as follows
SELECT nth_string
FROM
(SELECT TRIM (REGEXP_SUBSTR (long_string, '[^|]+', 1, ROWNUM) ) nth_string ,
level AS lvl
FROM
(SELECT REPLACE('Jack|Sparrow|17-09-16|DY7009|Address at some where|details','||','| |') long_string
FROM DUAL
)
CONNECT BY LEVEL <= REGEXP_COUNT ( long_string, '[^|]+')
)
WHERE lvl = 4;
Note that i am using the standard query in oracle to split a delimited string into records. To handle blank between delimiters as in your second case, i am replacing it with a space ' ' . The space gets converted to NULL after applying TRIM() function.
You can get any nth record by replacing the number in lvl = at the end of the query.
Let me know your feedback. Thanks.
EDIT:
It seems to not work with purely regexp_substr() as there is no way to convert blank between '||' to Oracle NULL .So intermediate TRIM() was required and i am adding a replace to make it easier. There will be patterns to directly match this scenario, but could not find them.
Here are all scenarios for 4th occurence .
WITH t
AS (SELECT '|Jack|Sparrow|SQY778|17JULY17||00J1' long_string
FROM dual
UNION ALL
SELECT 'Jack|Sparrow|SQY778|17JULY17||00J1' long_string
FROM dual
UNION ALL
SELECT '||Jack|Sparrow|SQY778|17JULY17|00J1' long_string
FROM dual)
SELECT long_string,
Trim (Regexp_substr (mod_string, '\|([^|]+)', 1, 4, NULL, 1)) nth_string
FROM (SELECT long_string,
Replace(long_string, '||', '| |') mod_string
FROM t) ;
LONG_STRING NTH_STRING
------------------------ -----------
|Jack|Sparrow|SQY778|17JULY17||00J1 17JULY17
Jack|Sparrow|SQY778|17JULY17||00J1 NULL
||Jack|Sparrow|SQY778|17JULY17|00J1 SQY778
EDIT2: Finally a pattern that gives the solution.Thanks to Gary_W
To get the nth occurence from the string , use:
WITH t
AS (SELECT '|Jack|Sparrow|SQY778|17JULY17||00J1' long_string
FROM dual
UNION ALL
SELECT 'Jack|Sparrow|SQY778|17JULY17||00J1' long_string
FROM dual
UNION ALL
SELECT '||Jack|Sparrow|SQY778|17JULY17|00J1' long_string
FROM dual)
SELECT long_string,
Trim (regexp_substr (long_string, '(.*?)(\||$)', 1, :n + 1, NULL, 1)) nth_string
FROM t;

ORA-01489: Oracle - ORA-01489: result of string concatenation is too long

I work on this query and get this error:
Oracle - ORA-01489: result of string concatenation is too long
Some one please help to solve this issue
SELECT LISTAGG(RCRDNUM) WITHIN GROUP (ORDER BY RCRDNUM)
FROM (SELECT (ERR.RCRDNUM || ',') AS RCRDNUM
FROM TABLENAME ERR
INNER JOIN (SELECT UPPER(REGEXP_SUBSTR('No value present for CNTRY_CD column for the record',
'[^,]+', 1, LEVEL)) ERR_MSG
FROM DUAL
CONNECT BY REGEXP_SUBSTR('No value present for CNTRY_CD column for the record',
'[^,]+', 1, LEVEL)
IS NOT NULL) ERRMSG_P
ON (UPPER(ERR.ERRMSG) = ERRMSG_P.ERR_MSG
OR 'No value present for CNTRY_CD column for the record' IS NULL))
If the aggregate list is a string longer than 4000 characters, the string needs to be a CLOB, and you can't use listagg(). However, you can use xmlagg(), which does not have the 4000 character limit. The result must be a CLOB though - and it is cast as CLOB in the solution.
. Here is a proof-of-concept; I will let you adapt it to your situation.
with a (id,val) as (select 10, 'x' from dual union all select 20, 'abc' from dual)
select listagg(val, ',') within group (order by id) as l_agg,
rtrim( xmlcast( xmlagg( xmlelement(e, val || ',') order by id) as clob), ',')
as clob_agg
from a
;
Output
L_AGG CLOB_AGG
---------- ----------
x,abc x,abc
In Oracle's SQL queries, strings (columns of type VARCHAR) are limited to 4000 characters. Obviously, your query creates longer strings and therefore fails. This can easily happen with LISTAGG.
Should your query really return such long strings? If not, you need to work on your query.
If you really need values longer than 4000 characters, you can try to use CLOB instead of VARCHAR by using a custom user-defined aggregation function. Tom Kyte has an example in one of his questions.

Oracle Loop delimited list to check if record exist

I'm new in PL/SQL, and Got a code from an old program that check if a record exist in a table something like :
oRetValue := ' ';
SELECT f1
INTO oRetValue
FROM t1
WHERE w1='w1'
AND code = iCode;
it was ok before, but now the application has more than 500 rows to verify. I'd like to pass a string with all the code separated by comma, and the procedure will loop and return all icode not found. Any help would be greatly appreciated.
Try this:
select f1
into oretvalue
from t1
where w1 = 'w1'
and code in
(select p_code
from (select level as id,
regexp_substr(icode, '[^,]+', 1, level) as p_code
from dual
connect by regexp_substr(icode, '[^,]+', 1, level) is not null));

Query Error - Single row query returns more than one row

I have a query where the input value is as: "Amar, Akbar, Anthony"
I want the query to treat input value as: 'Amar', 'Akbar', 'Anthony'. I have a regexp_substr which works in normal query and serves the purpose but when I put it with Case, it gives below error.
Single row query returns more than one row
The CASE is written so that if user doesn't enter anything in textbox, then query should return all rows, if user inputs something, result should show the matching values.
Select * from test_tbl a where
(
CASE
WHEN TRIM (:username) IS NULL
THEN NVL (a.user_name, 'NOTHING')
WHEN TRIM (UPPER (:username)) = 'NOTHING'
THEN NVL (a.user_name, :username)
ELSE UPPER (a.user_name)
END) LIKE (
CASE
WHEN TRIM (:username) IS NULL
THEN NVL (a.user_name, 'NOTHING')
WHEN TRIM (UPPER (:username)) = 'NOTHING'
THEN :username
ELSE ((select regexp_substr(:username,'[^,]+', 1, level) from dual connect by regexp_substr(:username, '[^,]+', 1, level) is not null))
END)
Is there a way to achieve required functionality? That is not change query much and include CASE with the regexp_substr.
I'm not entirely sure I follow your logic, but I think this is what you're looking for:
select * from test_tbl a
where :username is null
or a.user_name in (
select regexp_substr(:username,'[^,]+', 1, level) from dual
connect by regexp_substr(:username, '[^,]+', 1, level) is not null
)
With some dummy data: and a bind variable:
create table test_tbl (user_name varchar2(10));
insert into test_tbl values ('Amar');
insert into test_tbl values ('Akbar');
insert into test_tbl values ('Joe');
var username varchar2(80);
When username is set
exec :username := 'Amar,Akbar,Anthony';
.. that query gets:
USER_NAME
----------
Amar
Akbar
When it is null:
exec :username := null;
... that query gets all rows:
USER_NAME
----------
Amar
Akbar
Joe
I took the spaces out of your string of names so it would match properly. If your actual string from the textbox has spaces you'll need to handle them in the sub query.

Resources