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

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.

Related

PL/SQL oracle procedure dose not returen any value

i Have this oracle procedure reading paramater with varchar value,and when i use this parameter value inside the procedure dose not work. Everything will be explained below
CREATE OR REPLACE procedure test_pro(read_batch in varchar2 )
as
v_read_batches varchar2(500);
begin
v_read_batches := '''' || replace(read_batch, ',', ''',''') || '''';
--v_read_batches VALUE IS '100','1000','11','9200'
SELECT CODE,BANK_NAME_ARABIC,BANK_CODE,to_number(BATCH_ID)BATCH_ID FROM (select 1 CODE,PB.BANK_NAME_ARABIC ,to_char(PB.BANK_CODE)BANK_CODE,
CASE PB.BANK_CODE
WHEN 1000
THEN 1000
WHEN 100
THEN 100
ELSE 9200
END batch_id
from BANKS PB
WHERE PB.BANK_CODE IN (1000,100,11200)
union
SELECT 2 CODE,'Other Banks' other_banks,listagg(PB.BANK_CODE , ', ')
within group(order by PB.BANK_CODE ) as BANK_CODE, 11 batch_id
FROM BANKS PB
WHERE PB.BANK_CODE NOT IN (1000,100,9200))
WHERE to_char(BATCH_ID) IN (v_read_batches)
end test_pro;
Problem is when i put v_read_batches inside the sql condition it did not returen any value, when i execute
the below sql alone with same value in v_read_batches variable it works and reture the values !!
SELECT CODE,BANK_NAME_ARABIC,BANK_CODE,to_number(BATCH_ID)BATCH_ID
FROM (select 1 CODE,PB.BANK_NAME_ARABIC
,to_char(PB.BANK_CODE)BANK_CODE, CASE PB.BANK_CODE
WHEN 1000
THEN 1000
WHEN 100
THEN 100
ELSE 9200 END batch_id from BANKS PB WHERE PB.BANK_CODE IN (1000,100,11200)
union SELECT 2 CODE,'Other Banks' other_banks,listagg(PB.BANK_CODE ,
', ') within group(order by PB.BANK_CODE ) as BANK_CODE, 11 batch_id
FROM BANKS PB WHERE PB.BANK_CODE NOT IN (1000,100,9200))
WHERE to_char(BATCH_ID) IN ('100','1000','11','9200')
You cannot build a string like this and hope to use it iin an IN statement. The elements in an IN clause are static, ie, if you code
col in ('123,456')
then we are looking for COL to match the string '123,456' not the elements 123 and 456.
You can convert your input string to rows via some SQL, eg
create table t as select '123,456,789' acct from dual
select distinct (instr(acct||',',',',1,level)) loc
from t
connect by level <= length(acct)- length(replace(acct,','))+1
Having done this, you could alter your procedure so that your
WHERE batch_id in (read_batch)
becomes
WHERE batch_id in (select distinct (instr(:batch||',',',',1,level)) loc
from t
connect by level <= length(:batch)- length(replace(:batch,','))+1
)
In the general sense, never let an input coming from the outside world be folded directly into a SQL statement. You create the risk of "SQL Injection" which is the most common way people get hacked.
Full video demo on the string-to-rows technique here:
https://youtu.be/cjvpXL3H64c?list=PLJMaoEWvHwFIUwMrF4HLnRksF0H8DHGtt

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;

Using a single select statment to get the next row from a table or return the first row if the end of table is reached

I have a table say STAFF like below:
STAFF_NAME
============
ALEX
BERNARD
CARL
DOMINIC
EMMA
Now, I want to write a stored function with a single argument. E.g. GET_NEXT_STAFF(CURRENT_STAFF).
The input and output should be like:
Input | Output
=====================
NULL | ALEX
ALEX | BERNARD
BERNARD | CARL
EMMA | ALEX (Start from the beginning of the table again)
I know how to handle this problem using PL/SQL, but is it possible to deal with this problem with a single select statement?
In the solution below, I assume the rows are ordered alphabetically by names. They may be ordered by another column in the same table (for example by hire date, or by salary, etc. - it doesn't matter) - then the name of that column should be used in the ORDER BY clause of the two analytic functions.
The input name is passed in as a bind variable, :input_staff_name. The solution uses pure SQL, with no need for functions (PL/SQL), but if you must make it into a function, you can adapt it easily.
Edit: In my original answer I missed the required behavior when the input is null. The last line of code (excluding the semicolon) takes care of that. As written currently, the query returns ALEX (or in general the first value in the table) when the input is null, and it returns no rows when the input is not null and not in the table. If instead the requirement is to return the first name when the input is null or not found in the table, then it can be accommodated easily by removing and :input_staff_name is null from the last line.
with
tbl ( staff_name ) as (
select 'ALEX' from dual union all
select 'BERNARD' from dual union all
select 'CARL' from dual union all
select 'DOMINIC' from dual union all
select 'EMMA' from dual
),
prep ( staff_name, next_name, first_name ) as (
select staff_name,
lead(staff_name) over (order by staff_name),
first_value (staff_name) over (order by staff_name)
from tbl
)
select nvl(next_name, first_name) as next_staff_name
from prep
where staff_name = :input_staff_name
or (next_name is null and :input_staff_name is null)
;
Based on the answer from #mathguy I have made a few changes that seem to work. I have added the follow
UNION ALL
SELECT NULL
FROM DUAL
and
WHERE NVL (staff_name, 'X') = NVL (NULL, 'X');
The full code
WITH tbl (staff_name) AS
(SELECT 'ALEX' FROM DUAL
UNION ALL
SELECT 'BERNARD' FROM DUAL
UNION ALL
SELECT 'CARL' FROM DUAL
UNION ALL
SELECT 'DOMINIC' FROM DUAL
UNION ALL
SELECT 'EMMA' FROM DUAL
UNION ALL
SELECT NULL
FROM DUAL),
prep (staff_name,
next_name,
first_name,
last_name) AS
(SELECT staff_name,
LEAD (staff_name) OVER (ORDER BY staff_name),
FIRST_VALUE (staff_name) OVER (ORDER BY staff_name),
LAG (staff_name) OVER (ORDER BY staff_name)
FROM tbl)
SELECT NVL (next_name, first_name) AS next_staff_name
FROM prep
WHERE NVL (staff_name, 'X') = NVL (:input_staff_name, 'X');

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.

oracle 10g IN clause query

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 );

Resources