web-dev-qa-db-fra.com

Problème de comparaison de chaîne Oracle PL/SQL

1) .Bonjour, j’ai les codes Oracle PL/SQL suivants qui risquent d’être rouillés de votre point de vue: 

 DECLARE
 str1  varchar2(4000);
 str2  varchar2(4000);
 BEGIN
   str1:='';
   str2:='sdd';
   IF(str1<>str2) THEN
    dbms_output.put_line('The two strings is not equal');
   END IF;
 END;
 /

Ceci est très évident que deux chaînes str1 et str2 ne sont pas égales, mais pourquoi "Les deux chaînes ne sont pas égales" n'a pas été imprimé? Oracle a-t-il une autre méthode commune pour comparer deux chaînes?

21
C.c

Comme Phil l'a noté, la chaîne vide est traitée comme une valeur NULL et NULL n'est pas égal ou différent de rien. Si vous attendez des chaînes vides ou des valeurs NULL, vous devez gérer celles avec NVL():

 DECLARE
 str1  varchar2(4000);
 str2  varchar2(4000);
 BEGIN
   str1:='';
   str2:='sdd';
-- Provide an alternate null value that does not exist in your data:
   IF(NVL(str1,'X') != NVL(str2,'Y')) THEN
    dbms_output.put_line('The two strings are not equal');
   END IF;
 END;
 /

Concernant les comparaisons nulles:

Selon la documentation Oracle 12c sur NULLS , les comparaisons nulles à l'aide de IS NULL ou IS NOT NULL sont évaluées en TRUE ou FALSE. Cependant, toutes les autres comparaisons donnent UNKNOWN, notFALSE. La documentation indique en outre:

Une condition qui est évaluée à UNKNOWN agit presque comme FALSE. Par exemple, une instruction SELECT avec une condition dans la clause WHERE évaluée à UNKNOWN ne renvoie aucune ligne. Toutefois, une condition évaluée à UNKNOWN diffère de FALSE en ce que les opérations ultérieures sur une évaluation de condition UNKNOWN seront évaluées à UNKNOWN. Ainsi, NOT FALSE est évalué à VRAI, mais NOT INCONNU est évalué à INCONNU.

Une table de référence est fournie par Oracle:

Condition       Value of A    Evaluation
----------------------------------------
a IS NULL       10            FALSE
a IS NOT NULL   10            TRUE        
a IS NULL       NULL          TRUE
a IS NOT NULL   NULL          FALSE
a = NULL        10            UNKNOWN
a != NULL       10            UNKNOWN
a = NULL        NULL          UNKNOWN
a != NULL       NULL          UNKNOWN
a = 10          NULL          UNKNOWN
a != 10         NULL          UNKNOWN

J'ai aussi appris qu'il ne faut pas écrire PL/SQL en supposant que les chaînes vides seront toujours considérées comme NULL:

Oracle Database traite actuellement une valeur de caractère avec une longueur égale à zéro comme étant nulle. Toutefois, cela risque de ne plus être vrai dans les versions à venir et Oracle vous recommande de ne pas traiter les chaînes vides de la même manière que les chaînes null.

44
Wolf

Remplissons les lacunes de votre code en ajoutant les autres branches de la logique et voyons ce qui se passe:

SQL> DECLARE
  2   str1  varchar2(4000);
  3   str2  varchar2(4000);
  4  BEGIN
  5     str1:='';
  6     str2:='sdd';
  7     IF(str1<>str2) THEN
  8      dbms_output.put_line('The two strings is not equal');
  9     ELSIF (str1=str2) THEN
 10      dbms_output.put_line('The two strings are the same');
 11     ELSE
 12      dbms_output.put_line('Who knows?');
 13     END IF;
 14   END;
 15  /
Who knows?

PL/SQL procedure successfully completed.

SQL>

Donc, les deux chaînes ne sont ni les mêmes ni les mêmes? Hein? 

Cela revient à ceci. Oracle traite une chaîne vide comme une valeur NULL. Si nous essayons de comparer un NULL et une autre chaîne, le résultat n'est ni VRAI ni FAUX, il s'agit de NULL. Cela reste le cas même si l'autre chaîne est également une valeur NULL.

11
APC

Je compare les chaînes en utilisant = et non pas <>. J'ai découvert que, dans ce contexte, = semble fonctionner de manière plus raisonnable que <>. J'ai spécifié que deux chaînes vides (ou NULL) sont égales. L'implémentation réelle retourne PL/SQL booléen, mais ici j'ai changé cela en pls_integer (0 est false et 1 est true) pour pouvoir facilement démontrer la fonction.

create or replace function is_equal(a in varchar2, b in varchar2)
return pls_integer as
begin
  if a is null and b is null then
    return 1;
  end if;

  if a = b then
    return 1;
  end if;

  return 0;
end;
/
show errors

begin
  /* Prints 0 */
  dbms_output.put_line(is_equal('AAA', 'BBB'));
  dbms_output.put_line(is_equal('AAA', null));
  dbms_output.put_line(is_equal(null, 'BBB'));
  dbms_output.put_line(is_equal('AAA', ''));
  dbms_output.put_line(is_equal('', 'BBB'));

  /* Prints 1 */
  dbms_output.put_line(is_equal(null, null));
  dbms_output.put_line(is_equal(null, ''));
  dbms_output.put_line(is_equal('', ''));
  dbms_output.put_line(is_equal('AAA', 'AAA'));
end;
/
2
user272735

Pour résoudre la question centrale, "comment détecter que ces deux variables n'ont pas la même valeur quand l'une d'entre elles est nulle?", Je n'aime pas l'approche de nvl(my_column, 'some value that will never, ever, ever appear in the data and I can be absolutely sure of that') car vous ne pouvez pas toujours garantir qu'une valeur n'apparaîtra pas ... surtout avec NUMBER.

J'ai utilisé les éléments suivants:

if (str1 is null) <> (str2 is null) or str1 <> str2 then
  dbms_output.put_line('not equal');
end if;

Clause de non-responsabilité: je ne suis pas un assistant Oracle, je l'ai moi-même créé et je ne l'ai pas encore vu ailleurs. Il peut donc y avoir une raison subtile pour laquelle c'est une mauvaise idée. Mais cela évite le piège mentionné par APC, qui consiste à comparer un zéro à autre chose, mais ne donne ni VRAI ni FAUX mais NULL. Parce que les clauses (str1 is null) renverront toujours TRUE ou FALSE, jamais null.

(Notez que PL/SQL effectue une évaluation de court-circuit, comme noté ici .)

1
Andrew Spencer

Ne changez que la ligne str1: = ''; à str1: = '';

0
MARIO GARCIA

J'ai créé une fonction stockée pour cet objectif de comparaison de texte:

CREATE OR REPLACE FUNCTION TextCompare(vOperand1 IN VARCHAR2, vOperator IN VARCHAR2, vOperand2 IN VARCHAR2) RETURN NUMBER DETERMINISTIC AS
BEGIN
  IF vOperator = '=' THEN
    RETURN CASE WHEN vOperand1 = vOperand2 OR vOperand1 IS NULL AND vOperand2 IS NULL THEN 1 ELSE 0 END;
  ELSIF vOperator = '<>' THEN
    RETURN CASE WHEN vOperand1 <> vOperand2 OR (vOperand1 IS NULL) <> (vOperand2 IS NULL) THEN 1 ELSE 0 END;
  ELSIF vOperator = '<=' THEN
    RETURN CASE WHEN vOperand1 <= vOperand2 OR vOperand1 IS NULL THEN 1 ELSE 0 END;
  ELSIF vOperator = '>=' THEN
    RETURN CASE WHEN vOperand1 >= vOperand2 OR vOperand2 IS NULL THEN 1 ELSE 0 END;
  ELSIF vOperator = '<' THEN
    RETURN CASE WHEN vOperand1 < vOperand2 OR vOperand1 IS NULL AND vOperand2 IS NOT NULL THEN 1 ELSE 0 END;
  ELSIF vOperator = '>' THEN
    RETURN CASE WHEN vOperand1 > vOperand2 OR vOperand1 IS NOT NULL AND vOperand2 IS NULL THEN 1 ELSE 0 END;
  ELSIF vOperator = 'LIKE' THEN
    RETURN CASE WHEN vOperand1 LIKE vOperand2 OR vOperand1 IS NULL AND vOperand2 IS NULL THEN 1 ELSE 0 END;
  ELSIF vOperator = 'NOT LIKE' THEN
    RETURN CASE WHEN vOperand1 NOT LIKE vOperand2 OR (vOperand1 IS NULL) <> (vOperand2 IS NULL) THEN 1 ELSE 0 END;
  ELSE
    RAISE VALUE_ERROR;
  END IF;
END;

Par exemple:

SELECT * FROM MyTable WHERE TextCompare(MyTable.a, '>=', MyTable.b) = 1;
0
David Gausmann