24

I have the following Oracle PL/SQL codes that may be rusty from you guys perspective:

 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;
 /

This is very obvious that two strings str1 and str2 are not equal, but why 'The two strings are not equal' was not printed out? Do Oracle have another common method to compare two string?

Draken
  • 3,134
  • 13
  • 34
  • 54
C.c
  • 1,905
  • 6
  • 30
  • 47
  • 7
    Keep in mind, in Oracle an empty string is equivalent to `NULL` which may not work as expected in an inequality comparison statement – Phil Sep 02 '11 at 15:57
  • could you give me some advices about how to solve this problem? – C.c Sep 02 '11 at 16:00
  • 2
    These are two distinct questions. You should ask them as such, as that is how SO works. Having said which, your second question is an old chestnut which has already been answered several times. Please search before asking: http://stackoverflow.com/questions/tagged/oracle+string-aggregation – APC Sep 02 '11 at 16:04
  • Thanks, But Actually I want more is how to solve first problem. – C.c Sep 02 '11 at 16:07
  • 1
    possible duplicate of [Why does Oracle 9i treat an empty string as NULL?](http://stackoverflow.com/questions/203493/why-does-oracle-9i-treat-an-empty-string-as-null) – APC Sep 02 '11 at 16:07
  • 3
    @APC, except this question is not asking why an empty string is null, but why `'' <> 'something'` is not true. It is only related to that question in that the answer to this one, _because Oracle treats empty strings as null_ might lead one to ask the question, _why?_ – Shannon Severance Sep 02 '11 at 16:39

8 Answers8

50

As Phil noted, the empty string is treated as a NULL, and NULL is not equal or unequal to anything. If you expect empty strings or NULLs, you'll need to handle those with 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;
 /

Concerning null comparisons:

According to the Oracle 12c documentation on NULLS, null comparisons using IS NULL or IS NOT NULL do evaluate to TRUE or FALSE. However, all other comparisons evaluate to UNKNOWN, not FALSE. The documentation further states:

A condition that evaluates to UNKNOWN acts almost like FALSE. For example, a SELECT statement with a condition in the WHERE clause that evaluates to UNKNOWN returns no rows. However, a condition evaluating to UNKNOWN differs from FALSE in that further operations on an UNKNOWN condition evaluation will evaluate to UNKNOWN. Thus, NOT FALSE evaluates to TRUE, but NOT UNKNOWN evaluates to UNKNOWN.

A reference table is provided by 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

I also learned that we should not write PL/SQL assuming empty strings will always evaluate as NULL:

Oracle Database currently treats a character value with a length of zero as null. However, this may not continue to be true in future releases, and Oracle recommends that you do not treat empty strings the same as nulls.

WoMo
  • 7,136
  • 2
  • 29
  • 36
  • 7
    While practically correct, the precise definition is that "any comparison that involves a NULL value will always return FALSE" (even if comparing to another NULL value.) So: (NULL = NULL) = False AND (NULL <> NULL) = FALSE – Kerbocat Jun 15 '12 at 18:49
  • 1
    @Kerbocat I have been pondering this comment for the last couple of years. Do you have a reference regarding how Oracle evaluates null comparisons? The Oracle [documentation](http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements005.htm#g194888) I find states that null comparisons actually evaluate to `UNKNOWN`. – WoMo Apr 03 '14 at 17:07
  • I have no references for this now, it may very well be that I am in fact mistaken. I do find UNKNOWN to be of very little practical use as a concept though given this: (from your source) "A condition that evaluates to UNKNOWN acts almost like FALSE. For example, a SELECT statement with a condition in the WHERE clause that evaluates to UNKNOWN returns no rows. However, a condition evaluating to UNKNOWN differs from FALSE in that further operations on an UNKNOWN condition evaluation will evaluate to UNKNOWN. Thus, NOT FALSE evaluates to TRUE, but NOT UNKNOWN evaluates to UNKNOWN." – Kerbocat Apr 03 '14 at 21:24
  • why oracle refuses to deal with `true` and `false` correctly? – rado Mar 19 '19 at 13:24
  • You people if you look at his example it DOES NOT HAVE NULL in it. – John Foll Jul 22 '23 at 00:27
12

Let's fill in the gaps in your code, by adding the other branches in the logic, and see what happens:

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>

So the two strings are neither the same nor are they not the same? Huh?

It comes down to this. Oracle treats an empty string as a NULL. If we attempt to compare a NULL and another string the outcome is not TRUE nor FALSE, it is NULL. This remains the case even if the other string is also a NULL.

APC
  • 144,005
  • 19
  • 170
  • 281
3

I compare strings using = and not <>. I've found out that in this context = seems to work in more reasonable fashion than <>. I have specified that two empty (or NULL) strings are equal. The real implementation returns PL/SQL boolean, but here I changed that to pls_integer (0 is false and 1 is true) to be able easily demonstrate the function.

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;
/
user272735
  • 10,473
  • 9
  • 65
  • 96
1

To fix the core question, "how should I detect that these two variables don't have the same value when one of them is null?", I don't like the approach of nvl(my_column, 'some value that will never, ever, ever appear in the data and I can be absolutely sure of that') because you can't always guarantee that a value won't appear... especially with NUMBERs.

I have used the following:

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

Disclaimer: I am not an Oracle wizard and I came up with this one myself and have not seen it elsewhere, so there may be some subtle reason why it's a bad idea. But it does avoid the trap mentioned by APC, that comparing a null to something else gives neither TRUE nor FALSE but NULL. Because the clauses (str1 is null) will always return TRUE or FALSE, never null.

(Note that PL/SQL performs short-circuit evaluation, as noted here.)

Community
  • 1
  • 1
Andrew Spencer
  • 15,164
  • 4
  • 29
  • 48
0

I've created a stored function for this text comparison purpose:

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;

In example:

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

Only change the line str1:=''; to str1:=' ';

0

The '' would be treated as NULL, so, both the strings need to be checked as NULL.

Function:

CREATE OR REPLACE FUNCTION str_cmpr_fnc(str_val1_in IN VARCHAR2, str_val2_in IN VARCHAR2) RETURN VARCHAR2
AS
    l_result    VARCHAR2(50);
 BEGIN
    -- string comparison
    CASE
        WHEN str_val1_in IS NULL AND str_val2_in IS NULL THEN
            l_result := 'Both Unknown';
        WHEN str_val1_in IS NULL THEN
            l_result := 'Str1 Unknown';
        WHEN str_val2_in IS NULL THEN
            l_result := 'Str2 Unknown';
        ELSE
            CASE
                WHEN str_val1_in = str_val2_in THEN
                    l_result := 'Both are equel';
                ELSE
                    l_result := 'Both strings are not equal';
            END CASE;
    END CASE;
    -- return result 
    RETURN l_result;
 EXCEPTION
 WHEN OTHERS THEN
    -- set serveroutput on to get the error information
    DBMS_OUTPUT.put_line(SQLERRM||' ,'|| DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    -- return result
    RETURN l_result;
 END str_cmpr_fnc;

Sql Statement:

SELECT str_cmpr_fnc('7', 'd') FROM DUAL;
Del
  • 1,529
  • 1
  • 9
  • 18
-5

To the first question:

Probably the message wasn't print out because you have the output turned off. Use these commands to turn it back on:

set serveroutput on
exec dbms_output.enable(1000000);

On the second question:

My PLSQL is quite rusty so I can't give you a full snippet, but you'll need to loop over the result set of the SQL query and CONCAT all the strings together.

João Rafael
  • 131
  • 1
  • 7