Differenza tra due stringhe delimitate non ordinate in PL/SQL
Domanda
Esiste un semplice metodo elegante per restituire la differenza tra due stringhe non ordinate e delimitate in Oracle usando PL/SQL?
Esempio:
String A: "a1, b4, g3, h6, t8, a0"
String B: "b4, h6, a0, t8, a1"
Differenza: "G3"
Non ho accesso ad Apex_Util come suggerito in questa risposta Differenza tra due elenchi delicati non ordinati (Oracle)
Soluzione
Di solito preferisco usare il Multiset Operatore per situazioni come questa.
La soluzione di seguito non è esattamente elegante, anche se devi tokenizzare la stringa per utilizzare l'operatore multiset, ma se si ottengono gli elenchi come raccolte, sarebbe molto facile (o se hai già un tokenizzatore comune). (Il tokenizer di seguito non è molto veloce.)
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;
Altri suggerimenti
Non sono sicuro di elegante ma questo funzionerebbe:
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 '/')
spero che sia di aiuto
Se sai che saranno virgole e / o spazio delimitato, funzionerebbe ed è molto più semplice.
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;
EDIT: Modifica per restituire la stringa.