1

I want remove specific value from comma separated sting using oracle.

Sample Input -

col
1,2,3,4,5

Suppose i want to remove 3 from the string.

Sample Output -

col
1,2,4,5

Please suggest how i can do this using oracle query.

Thanks.

Tajinder
  • 2,248
  • 4
  • 33
  • 54

5 Answers5

4

Here is a solution that uses only standard string functions (rather than regular expressions) - which should result in faster execution in most cases; it removes 3 only when it is the first character followed by comma, the last character preceded by comma, or preceded and followed by comma, and it removes the comma that precedes it in the middle case and it removes the comma that follows it in the first and third case.

It is able to remove two 3's in a row (which some of the other solutions offered are not able to do) while leaving in place consecutive commas (which presumably stand in for NULL) and do not disturb numbers like 38 or 123.

The strategy is to first double up every comma (replace , with ,,) and append and prepend a comma (to the beginning and the end of the string). Then remove every occurrence of ,3,. From what is left, replace every ,, back with a single , and finally remove the leading and trailing ,.

with
     test_data ( str ) as (
       select '1,2,3,4,5'     from dual union all
       select '1,2,3,3,4,4,5' from dual union all
       select '12,34,5'       from dual union all
       select '1,,,3,3,3,4'   from dual
     )
select str,
       trim(both ',' from 
             replace( replace(',' || replace(str, ',', ',,') || ',', ',3,'), ',,', ',')
           ) as new_str
from   test_data
;

STR           NEW_STR
------------- ----------
1,2,3,4,5     1,2,4,5
1,2,3,3,4,4,5 1,2,4,4,5
12,34,5       12,34,5
1,,,3,3,3,4   1,,,4

4 rows selected.

Note As pointed out by MT0 (see Comments below), this will trim too much if the original string begins or ends with commas. To cover that case, instead of wrapping everything within trim(both ',' from ...) I should wrap the rest within a subquery, and use something like substr(new_str, 2, length(new_str) - 2) in the outer query.

  • You need to only remove the first and last characters rather than using `TRIM` as this will strip empty elements from the head and the tail of the list. – MT0 Feb 24 '17 at 21:21
  • @MT0 - Oh, you are right. I made a mental note to pay attention to that and then I still forgot to. Thanks for pointing it out! –  Feb 24 '17 at 21:28
  • this query worked for me with one change. one parameter is missing for the 3rd replace function. we need to add another parameter as '' (basically, we will need this to replace all occurrences of the user string ',3,' with an empty string). **replace(str, ',', ',,') || ',', ',3,' ,'' )** – Saurabh Saha Jun 22 '18 at 07:52
  • 1
    @SaurabhSaha - there is nothing missing. The third argument is optional, it has the default value of NULL. Are you saying that you tried it without the third argument, and it didn’t work, or it produced the wrong result? If so, please share your test case and Oracle version. –  Jun 22 '18 at 13:49
  • @mathguy I did not see this is oracle specific answer. I am using postgres where the method signature is replace(string text, from text, to text). – Saurabh Saha Jun 26 '18 at 10:07
  • @SaurabhSaha - I see. If your comment had been "this query worked for me IN POSTGRE with one change, ..." that would have been crystal clear. Thank you for clarifying! –  Jun 26 '18 at 13:43
3

Here is one method:

select trim(both ',' from replace(',' || '1,2,3,4,5' || ',', ',' || '3' || ',', ','))

That said, storing comma-delimited strings is a really, really bad idea. There is almost no reason to do such a thing. Oracle supports JSON, XML, and nested tables -- all of which are better alternatives.

The need to remove an element suggests a poor data design.

Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786
  • 1
    This solution will not remove both 3's from a sequence like `1,2,2,3,3,4`. –  Feb 24 '17 at 16:42
  • 2
    @Gordon Linoff Thanks for your suggestion.I think you are right. – Tajinder Feb 24 '17 at 16:48
  • This will remove empty elements from the list if they are at the head or the tail of the list. Instead of using `TRIM` you should remove only the first and last characters. – MT0 Feb 24 '17 at 18:50
1

You can convert the list rows using an XMLTABLE, filter to remove the unwanted rows and then re-aggregate them:

SELECT LISTAGG( x.value.getStringVal(), ',' ) WITHIN GROUP ( ORDER BY idx )
FROM   XMLTABLE(
         ( '1,2,3,4,5' )
         COLUMNS value XMLTYPE PATH '.',
                 idx   FOR ORDINALITY
       ) x
WHERE  x.value.getStringVal() != 3;

For a simple filter this is probably not worth it and you should use something like (based on @mathguy's solution):

SELECT SUBSTR( new_list, 2, LENGTH( new_list ) - 2 ) AS new_list
FROM   (
  SELECT REPLACE(
           REPLACE(
             ',' || REPLACE( :list, ',', ',,' ) || ',',
             ',' || :value_to_replace || ','
           ),
           ',,',
           ','
         ) AS new_list
  FROM   DUAL
)

However, if the filtering is more complicated then it might be worth converting the list to rows, filtering and re-aggregating.

Community
  • 1
  • 1
MT0
  • 143,790
  • 11
  • 59
  • 117
  • The simple solution will fail on 1,2,3,3,4,5 because the comma used to recognize the first ,3, cannot be used again to recognize the second ,3, –  Feb 24 '17 at 20:43
0

I do not knwo how to do this in Oracle, but with SQL-Server I'd use a trick:

  • convert the list to XML by replacing the comma with tags
  • use XQuery to filter the data
  • reconcatenate

This is SQL Server syntax but might point you the direction:

declare @s varchar(100)='1,2,2,3,3,4';
declare @exclude int=3;

WITH Casted AS
(
    SELECT CAST('<x>' + REPLACE(@s,',','</x><x>') + '</x>' AS XML) AS TheXml
)
SELECT x.value('.','int')
FROM Casted
CROSS APPLY TheXml.nodes('/x[text()!=sql:variable("@exclude")]') AS A(x)

UPDATE

I just found this answer which seems to show pretty well how to start...

Community
  • 1
  • 1
Shnugo
  • 66,100
  • 9
  • 53
  • 114
0

I agree with Gordon regarding the fact that storing comma delimited data in a column is a really bad idea.

I just preceed the csv with a ',', then use the replace function followed by a left trim function to clean-up the preceeding ','.

SCOTT@tst>VAR b_number varchar2(5);
SCOTT@tst>EXEC :b_number:= '3';

PL/SQL procedure successfully completed.

SCOTT@tst>WITH srce AS (
  2      SELECT
  3          ',' || '3,1,2,3,3,4,5,3' col
  4      FROM
  5          dual
  6  ) SELECT
  7      ltrim(replace(col,',' ||:b_number),',') col
  8  FROM
  9      srce;
COL      
1,2,4,5  
Patrick Bacon
  • 4,490
  • 1
  • 26
  • 33
  • @MTO Yes, you are correct. I did not even try that. The OP stated in the comments, "I want to remove 3 if it is the entire number." I certainly would have taken a different approach (e.g. regexp_replace or something). – Patrick Bacon Feb 24 '17 at 19:26