Diferencia entre dos cadenas delimitadas desordenadas en PL/SQL
Pregunta
¿Existe un método elegante simple para devolver la diferencia entre dos cadenas delimitadas y desordenadas en Oracle usando PL/SQL?
Ejemplo:
String A: "a1, b4, g3, h6, t8, a0"
String B: "b4, h6, a0, t8, a1"
Diferencia: "G3"
No tengo acceso a apex_util como se sugiere en esta respuesta Diferencia entre dos listas deliminadas desordenadas (Oracle)
Solución
Normalmente prefiero usar el Múltiple Operador para situaciones como esta.
Sin embargo, la solución a continuación no es exactamente elegante, ya que tiene que tokenizar la cadena para usar el operador multiset, pero si obtiene las listas como colecciones, esto sería muy fácil (o si ya tiene un tokenizador común). (El tokenizador de abajo no es muy rápido).
DECLARE
TYPE VARCHARTABLE IS TABLE OF VARCHAR2(2000);
A VARCHAR2(32767) := 'a1, b4, g3, h6, t8, a0';
B VARCHAR2(32767) := 'b4, h6, a0, t8, a1';
onlyInA VARCHARTABLE;
onlyInB VARCHARTABLE;
FUNCTION tokenize( v IN VARCHAR2 )
RETURN VARCHARTABLE
IS
mReturn VARCHARTABLE := VARCHARTABLE();
mTemp VARCHAR2(2000);
mChar VARCHAR2(1);
mIdx INTEGER := 1;
PROCEDURE appendToken( token IN VARCHAR2 )
IS
BEGIN
IF TRIM(token) IS NOT NULL THEN
mReturn.EXTEND(1);
mReturn( mReturn.LAST ) := TRIM(token);
END IF;
END appendToken;
BEGIN
LOOP
mChar := SUBSTR( v, mIdx, 1);
IF mChar = ',' THEN
appendToken( mTemp );
mTemp := NULL;
ELSIF mChar IS NULL THEN
appendToken( mTemp );
EXIT;
ELSE
mTemp := mTemp || mChar;
END IF;
mIdx := mIdx + 1;
END LOOP;
RETURN mReturn;
END tokenize;
FUNCTION toVarchar( v IN VARCHARTABLE )
RETURN VARCHAR2
IS
mReturn VARCHAR2(32767);
mIdx INTEGER := 0;
BEGIN
mIdx := v.FIRST;
WHILE mIdx IS NOT NULL LOOP
IF mReturn IS NOT NULL THEN
mReturn := mReturn || ',';
END IF;
mReturn := mReturn || v(mIdx);
mIdx := v.NEXT(mIdx);
END LOOP;
RETURN mReturn;
END toVarchar;
BEGIN
onlyInA := tokenize(A) MULTISET EXCEPT tokenize(B);
onlyInB := tokenize(B) MULTISET EXCEPT tokenize(A);
DBMS_OUTPUT.put_line( 'Only in A : ' || toVarchar(onlyInA) );
DBMS_OUTPUT.put_line( 'Only in B : ' || toVarchar(onlyInB) );
END;
Otros consejos
No estoy seguro de elegante, pero esto funcionaría:
WITH t1 AS (SELECT 'a1, b4, g3, h6, t8, a0' str FROM dual),
t2 AS (SELECT 'b4, h6, a0, t8, a1' str FROM dual)
--
SELECT val
FROM t1,
xmltable('/root/e/text()'
passing xmltype('<root><e>' ||
replace(t1.str,', ','</e><e>') ||
'</e></root>')
columns val varchar2(10) path '/')
MINUS
SELECT val
FROM t2,
xmltable('/root/e/text()'
passing xmltype('<root><e>' ||
replace(t2.str,', ','</e><e>') ||
'</e></root>')
columns val varchar2(10) path '/')
espero que esto ayude
Si sabe que van a ser delimitados por coma y / o espacio, esto funcionaría y es mucho más simple.
create or replace function compare_strings ( PString1 char, Pstring2 char ) return char is
v_string1 varchar2(100) := replace(replace(Pstring1,',',''),' ','');
v_string2 varchar2(100) := replace(replace(Pstring2,',',''),' ','');
begin
if replace(translate( v_string1, v_string2, ' '), ' ', '') is null then
return replace(translate( v_string2, v_string1, ' '), ' ', '') ;
else
return replace(translate( v_string1, v_string2, ' '), ' ', '');
end if;
end;
Editar: Cambiar para devolver la cadena.