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)

¿Fue útil?

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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top