Как удалить дубликаты из списка через запятую с помощью 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;
/