Как удалить дубликаты из списка через запятую с помощью regexp_replace в Oracle?

Я имею

 POW,POW,POWPRO,PRO,PRO,PROUTL,TNEUTL,TNEUTL,UTL,UTLTNE,UTL,UTLTNE

я хочу

POW,POWPRO,PRO,PROUTL,TNEUTL,UTL,UTLTNE

Я старался

select regexp_replace('POW,POW,POWPRO,PRO,PRO,PROUTL,TNEUTL,TNEUTL,UTL,UTLTNE,UTL,UTLTNE','([^,]+)(,\1)+','\1') from dual

И я получаю вывод

 POWPROUTL,TNEUTL,UTLTNE,UTLTNE

Но я хочу, чтобы результат был

POW,POWPRO,PRO,PROUTL,TNEUTL,UTL,UTLTNE

Пожалуйста помоги.

3 ответа

Два решения, которые используют только SQL, и третье решение, которое использует небольшую / простую функцию PL/SQL, которая делает очень короткий окончательный запрос SQL.

Установка Oracle:

CREATE TABLE data ( value ) AS
SELECT 'POW,POW,POWPRO,PRO,PRO,PROUTL,TNEUTL,TNEUTL,UTL,UTLTNE,UTL,UTLTNE' FROM DUAL;

CREATE TYPE stringlist AS TABLE OF VARCHAR2(4000);
/

Запрос 1:

SELECT LISTAGG( t.COLUMN_VALUE, ',' ) WITHIN GROUP ( ORDER BY t.COLUMN_VALUE ) AS list
FROM   data d,
       TABLE(
         SET(
           CAST(
             MULTISET(
              SELECT REGEXP_SUBSTR( d.value, '[^,]+', 1, LEVEL )
              FROM   DUAL
              CONNECT BY LEVEL <= REGEXP_COUNT( d.value, '[^,]+' )
             ) AS stringlist
           )
         )
       ) t
GROUP BY d.value;

Выходы:

LIST
---------------------------------------
POW,POWPRO,PRO,PROUTL,TNEUTL,UTL,UTLTNE

Запрос 2:

SELECT ( SELECT LISTAGG(  COLUMN_VALUE, ',' ) WITHIN GROUP ( ORDER BY ROWNUM )
         FROM TABLE( d.uniques ) ) AS list
FROM   (
  SELECT ( SELECT CAST(
                    COLLECT(
                      DISTINCT
                      REGEXP_SUBSTR( d.value, '[^,]+', 1, LEVEL )
                    )
                    AS stringlist
                  )
            FROM  DUAL
            CONNECT BY LEVEL <= REGEXP_COUNT( d.value, '[^,]+' )
         ) uniques
  FROM   data d
) d;

Выход:

LIST
---------------------------------------
POW,POWPRO,PRO,PROUTL,TNEUTL,UTL,UTLTNE

Установка Oracle:

Маленькая вспомогательная функция:

CREATE FUNCTION split_String(
  i_str    IN  VARCHAR2,
  i_delim  IN  VARCHAR2 DEFAULT ','
) RETURN stringlist DETERMINISTIC
AS
  p_result       stringlist := stringlist();
  p_start        NUMBER(5) := 1;
  p_end          NUMBER(5);
  c_len CONSTANT NUMBER(5) := LENGTH( i_str );
  c_ld  CONSTANT NUMBER(5) := LENGTH( i_delim );
BEGIN
  IF c_len > 0 THEN
    p_end := INSTR( i_str, i_delim, p_start );
    WHILE p_end > 0 LOOP
      p_result.EXTEND;
      p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, p_end - p_start );
      p_start := p_end + c_ld;
      p_end := INSTR( i_str, i_delim, p_start );
    END LOOP;
    IF p_start <= c_len + 1 THEN
      p_result.EXTEND;
      p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, c_len - p_start + 1 );
    END IF;
  END IF;
  RETURN p_result;
END;
/

Запрос 3:

SELECT ( SELECT LISTAGG(  COLUMN_VALUE, ',' ) WITHIN GROUP ( ORDER BY ROWNUM )
         FROM TABLE( SET( split_String( d.value ) ) ) ) AS list
FROM   data d;

или (если вы хотите передать только одно значение):

SELECT LISTAGG(  COLUMN_VALUE, ',' ) WITHIN GROUP ( ORDER BY ROWNUM ) AS list
FROM   TABLE( SET( split_String(
          'POW,POW,POWPRO,PRO,PRO,PROUTL,TNEUTL,TNEUTL,UTL,UTLTNE,UTL,UTLTNE'
       ) ) );

Выход:

LIST
---------------------------------------
POW,POWPRO,PRO,PROUTL,TNEUTL,UTL,UTLTNE

Предлагаемое ниже решение использует прямой SQL (без PL/SQL). Он работает с любой возможной входной строкой и удаляет дубликаты на месте - он сохраняет порядок входных токенов, каким бы он ни был. Он также удаляет последовательные запятые (он "удаляет нули" из входной строки), одновременно обрабатывая нулевые входы. Обратите внимание на вывод для входной строки, состоящей только из запятых, и правильную обработку "токенов", состоящих из двух пробелов и одного пробела соответственно.

Запрос выполняется относительно медленно; если производительность является проблемой, ее можно переписать как рекурсивный запрос, используя "традиционный" substr а также instr которые немного быстрее, чем регулярные выражения.

with inputs (input_string) as (
       select 'POW,POW,POWPRO,PRO,PRO,PROUTL,TNEUTL,TNEUTL,UTL,UTLTNE,UTL,UTLTNE' from dual
       union all
       select null from dual
       union all
       select 'ab,ab,st,ab,st,  , ,  ,x,,,r' from dual
       union all
       select ',,,' from dual
     ),
     tokens (input_string, rk, token) as (
       select     input_string, level, 
                  regexp_substr(input_string, '([^,]+)', 1, level, null, 1)
       from       inputs 
       connect by level <= 1 + regexp_count(input_string, ',')
     ),
     distinct_tokens (input_string, rk, token) as (
       select     input_string, min(rk) as rk, token
       from       tokens
       group by   input_string, token
     )
select   input_string, listagg(token, ',') within group (order by rk) output_string
from     distinct_tokens
group by input_string
;

Результаты для входов, которые я создал:

INPUT_STRING                                                       OUTPUT_STRING
------------------------------------------------------------------ ----------------------------------------
,,,                                                                (null)
POW,POW,POWPRO,PRO,PRO,PROUTL,TNEUTL,TNEUTL,UTL,UTLTNE,UTL,UTLTNE  POW,POWPRO,PRO,PROUTL,TNEUTL,UTL,UTLTNE
ab,ab,st,ab,st,  , ,  ,x,,,r                                       ab,st,  , ,x,r
(null)                                                             (null)


4 rows selected.

У меня эта функция работает нормально.

      DECLARE  
input_string varchar2(255);
merged_users VARCHAR2(4000);
merged_list VARCHAR2(4000);

BEGIN
input_string:='abc3,abc1,abc2,abc3,abc2,abc4';

  -- Remove leading and trailing commas from input_string
input_string := TRIM(',' FROM input_string);

 -- Split the input_string into individual elements
 WITH data AS (
 SELECT TRIM(REGEXP_SUBSTR(input_string, '[^,]+', 1, LEVEL)) AS token
 FROM dual
 CONNECT BY LEVEL <= REGEXP_COUNT(input_string, '[^,]+')
  ),

 -- Select distinct tokens and concatenate them
distinct_data AS (
SELECT DISTINCT token
FROM data
)
SELECT LISTAGG(token, ',') WITHIN GROUP (ORDER BY 1) INTO merged_users
FROM distinct_data;

 DBMS_OUTPUT.PUT_LINE(merged_users);

 END;
 /
Другие вопросы по тегам