【问题标题】:Oracle PL/SQL string compare issueOracle PL/SQL 字符串比较问题
【发布时间】:2011-11-09 07:20:48
【问题描述】:

我有以下 Oracle PL/SQL 代码,从你们的角度来看,它们可能会生锈:

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

这很明显两个字符串str1和str2不相等,但是为什么'The two strings are not equal'没有打印出来呢? Oracle 是否有另一种比较两个字符串的常用方法?

【问题讨论】:

  • 请记住,在 Oracle 中,空字符串等价于 NULL,在不等式比较语句中可能无法正常工作
  • 你能给我一些关于如何解决这个问题的建议吗?
  • 这是两个截然不同的问题。你应该这样问他们,因为这就是 SO 的工作方式。话虽如此,您的第二个问题是一个已经回答过几次的老栗子。提问前请先搜索:stackoverflow.com/questions/tagged/oracle+string-aggregation
  • @APC,除了这个问题不是问为什么空字符串为空,而是为什么'' &lt;&gt; 'something' 不是真的。它只与那个问题有关,因为这个问题的答案,因为 Oracle 将空字符串视为 null 可能会导致人们问这个问题,为什么?

标签: sql oracle plsql oracle10g oracle11g


【解决方案1】:

第一个问题:

可能因为您关闭了输出,消息没有打印出来。使用以下命令将其重新打开:

set serveroutput on
exec dbms_output.enable(1000000);

关于第二个问题:

我的 PLSQL 很生锈,所以我不能给你一个完整的 sn-p,但你需要循环 SQL 查询的结果集并将所有字符串连接在一起。

【讨论】:

  • 我确实设置了serveroutput,但是还是显示不出来
【解决方案2】:

正如 Phil 所指出的,空字符串被视为 NULL,而 NULL 不等于或不等于任何东西。如果您期望空字符串或 NULL,则需要使用 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;
 /

关于空值比较:

根据Oracle 12c documentation on NULLS,使用IS NULLIS NOT NULL 进行的空比较会评估为TRUEFALSE。但是,所有其他比较都评估为UNKNOWN不是FALSE。文档进一步指出:

计算结果为 UNKNOWN 的条件几乎类似于 FALSE。例如,在 WHERE 子句中具有条件的 SELECT 语句计算结果为 UNKNOWN 不返回任何行。但是,评估为 UNKNOWN 的条件与 FALSE 的不同之处在于,对 UNKNOWN 条件评估的进一步操作将评估为 UNKNOWN。因此,NOT FALSE 的计算结果为 TRUE,但 NOT UNKNOWN 的计算结果为 UNKNOWN。

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

我还了解到,我们不应该假设空字符串总是评估为 NULL 来编写 PL/SQL:

Oracle 数据库当前将长度为零的字符值视为空值。但是,这在未来的版本中可能不会继续如此,Oracle 建议您不要将空字符串视为空值。

【讨论】:

  • 虽然实际上是正确的,但准确的定义是“任何涉及 NULL 值的比较都将始终返回 FALSE”(即使与另一个 NULL 值比较。)所以:(NULL = NULL) = False AND (NULL NULL) = FALSE
  • @Kerbocat 过去几年我一直在思考这个评论。您是否有关于 Oracle 如何评估空比较的参考?我发现 Oracle documentation 声明空比较实际上评估为 UNKNOWN
  • 我现在没有这方面的参考资料,很可能是我实际上弄错了。我确实发现 UNKNOWN 作为一个概念几乎没有实际用途,尽管考虑到这一点:(来自您的来源)“评估为 UNKNOWN 的条件几乎就像 FALSE。例如,在 WHERE 子句中带有条件的 SELECT 语句评估to UNKNOWN 不返回任何行。但是,评估为 UNKNOWN 的条件与 FALSE 不同,因为对 UNKNOWN 条件评估的进一步操作将评估为 UNKNOWN。因此,NOT FALSE 评估为 TRUE,但 NOT UNKNOWN 评估为 UNKNOWN。"
  • 为什么oracle拒绝正确处理truefalse
【解决方案3】:

我使用= 而不是&lt;&gt; 比较字符串。我发现在这种情况下= 似乎比&lt;&gt; 更合理。我已指定两个空(或 NULL)字符串相等。真正的实现返回 PL/SQL 布尔值,但这里我将其更改为 pls_integer(0 为假,1 为真),以便能够轻松演示该功能。

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

【讨论】:

    【解决方案4】:

    让我们通过在逻辑中添加其他分支来填补代码中的空白,看看会发生什么:

    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>
    

    所以这两个字符串既不一样也不一样?嗯?

    归结为这一点。 Oracle 将空字符串视为 NULL。如果我们尝试比较一个 NULL 和另一个字符串,结果既不是 TRUE 也不是 FALSE,它是 NULL。即使另一个字符串也是 NULL,情况仍然如此。

    【讨论】:

      【解决方案5】:

      要解决核心问题,“当其中一个为空时,我应该如何检测这两个变量不具有相同的值?”,我不喜欢nvl(my_column, 'some value that will never, ever, ever appear in the data and I can be absolutely sure of that') 的方法,因为你不能始终保证不会出现值...尤其是 NUMBER。

      我用过以下:

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

      免责声明:我不是 Oracle 向导,我自己想出了这个,并且没有在其他地方看到它,所以可能有一些微妙的原因说明这是一个坏主意。但它确实避免了 APC 提到的陷阱,即将空值与其他值进行比较既不会给出 TRUE 也不会给出 FALSE,而是会给出 NULL。因为子句(str1 is null) 将始终返回 TRUE 或 FALSE,永远不会为空。

      (请注意,PL/SQL 执行短路评估,如 here 所述。)

      【讨论】:

        【解决方案6】:

        我为此文本比较创建了一个存储函数:

        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;
        

        例如:

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

        【讨论】:

          【解决方案7】:

          只换行 str1:='';str1:=' ';

          【讨论】:

          • 稍微解释一下会让这个答案变得更好。
          【解决方案8】:

          '' 将被视为 NULL,因此,两个字符串都需要检查为 NULL。

          功能:

          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语句:

          SELECT str_cmpr_fnc('7', 'd') FROM DUAL;
          

          【讨论】:

            猜你喜欢
            • 2017-11-13
            • 2015-06-17
            • 1970-01-01
            • 2011-09-12
            • 2015-11-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多