【问题标题】:Oracle - Extract numbers for comparison from varchar2 columnOracle - 从 varchar2 列中提取用于比较的数字
【发布时间】:2018-12-06 09:56:54
【问题描述】:

我在解决任务时遇到了以下问题:

在 Oracle 数据库中,我有一个结构相当简单的表 ENTITY_INFO。它包含 3 列:

ENTITY_ID (VARCHAR2) - 数据库中实体的PK

NAME (VARCHAR2) - 信息名称,即“位置”、“成本”、“最后一次相遇”

VALUE (VARCHAR2) - 信息的值,即“assets/music”、“1500”、“1.1.2000”

目前,我需要过滤掉“成本”

一种天真的方法

SELECT ENTITY_ID FROM ENTITY_INFO WHERE NAME = 'cost' AND TO_NUMBER(VALUE)<1000

不起作用,因为列VALUE 包含不是数字的值。 但是与过滤器NAME = 'cost'匹配的所有列值都是数字,所以我需要做的情况是有效的。

我找到了Select string as number on Oracle 主题,但里面的信息证明对解决这个问题没有用处。

由于ENTITY_INFO 的性质和项目的状态,改变数据模型也不是可行的解决方案。

感谢任何提示。

【问题讨论】:

    标签: sql oracle casting


    【解决方案1】:

    您可以有条件地转换为数字:

    SELECT ENTITY_ID
    FROM ENTITY_INFO
    WHERE NAME = 'cost'
    AND TO_NUMBER(CASE WHEN NAME = 'cost' THEN VALUE ELSE NULL END) < 1000
    

    【讨论】:

    • 您和我的答案都依赖于name='cost' 的值将真正是类似数字的字符串的承诺。如果不是,我们的两个代码都会导致错误 (invalid number)。另一方面,如果它们真的是类似数字的字符串,那么在这个问题上使用CASE 是多余的,无论是在代码方面还是在性能方面,因为简单的VALUE &lt; 1000 就足够了。我在回答中更详细地描述了它,并用一些示例数据展示了它。
    • 问题是与“成本”以外的名称相关的值会发生什么。我正在阻止它,甚至试图转换它们。你的第二个代码块也在尝试,但优化器可能会导致它仍然失败。
    • 感谢您的帮助。最后,我选择了“WITH - AS”方式,因为它帮助我更好地定位代码的其余部分。
    • 这是正确的答案。您不应该依赖在外部WHERE“之前”执行的 CTE。 Oracle 可能会决定重新安排这些操作。
    【解决方案2】:

    利用WITH 子句的替代方法,假设所有带有name 的记录都是数字

    在tab1部分,使用过滤条件并从tab1查询TO_NUMBER

    WITH tab1
         AS (SELECT entity_id, name, VALUE
               FROM entity_info
              WHERE name = 'cost')
    SELECT *
      FROM tab1
     WHERE TO_NUMBER (VALUE) < 1000
    

    在一列中有数字和字符是等待发生的意外。添加另一列来区分数字和非数字不是一种选择,如果namecost,我认为有一个约束来阻止输入非数字。

    【讨论】:

      【解决方案3】:

      在我的编译器中,我认为您的代码(或它的等价物)没有问题:

      SELECT ENTITY_ID 
        FROM ENTITY_INFO
       WHERE NAME = 'cost'
         AND VALUE < 1000
      

      数据样本示例:

      with ENTITY_INFO as (
          select 1 as ENTITY_ID,  'cost' as name, '2000' as value from dual
          union all
          select 2 as ENTITY_ID,  'cost' as name, '900' as value from dual
          union all
          select 3 as ENTITY_ID,  'cost' as name, '3000' as value from dual
          union all
          select 4 as ENTITY_ID,  'cost' as name, '2500' as value from dual
          union all
          select 5 as ENTITY_ID,  'cost' as name, '700' as value from dual
          union all
          select 6 as ENTITY_ID,  'frf' as name, '250sasd0' as value from dual
          union all
          select 7 as ENTITY_ID,  'corfrst' as name, '70fa0' as value from dual
          )
      SELECT ENTITY_ID 
        FROM ENTITY_INFO
       WHERE NAME = 'cost'
         AND VALUE < 1000
      

      结果:

      ENTITY_ID

          2
      
          5
      

      或者,您可以使用子查询来确保所有生成的列值都是类似数字的字符串:

      SELECT ENTITY_ID 
        FROM (SELECT ENTITY_ID,
                     VALUE
                FROM ENTITY_INFO 
               WHERE NAME = 'cost' )
       WHERE TO_NUMBER(VALUE)<1000
      

      希望我能帮上忙!

      【讨论】:

      • 您的第一个查询只是使 to_number() 隐含 - 它仍在发生。您可以通过查看执行计划来了解这一点。 (如果它将固定值转换为字符串,或者如果您提供了字符串,那么 ASCII 比较无论如何都会得到错误的结果。)其次,优化器可以推送谓词并重写查询,以便它仍然可以命中原始错误,因此它有时可能会起作用,但并非总是如此,或者可靠。
      • @AlexPoole 我从来没有说过to_char()to_number() 没有隐含地发生,其中一个必须发生才能进行比较。在你的例子中发生的事情不一样吗,使用CASE,当您将varchar2 VALUE 与数字1000 进行比较时?我们的两个答案都依赖于name='cost' 的值将真正是类似数字的字符串的承诺。否则,我的示例和您的示例都会导致 invalid number 错误。您可以在将select 8 as ENTITY_ID, 'cost' as name, 'b' as value from dual 添加到我的示例数据并使用它后尝试运行您的代码
      • 是的,但这不是我想要表达的意思; OP 的原始代码和您的第一个(可能是第二个,取决于优化器)代码块可以尝试转换 name isn't 'cost' 的值;我的至少将尝试限制在它“成本”的时候。 (如果存在无法转换为数字的“成本”值,那么无论如何它都会被破坏,我同意!问题表明所有成本值都是数字。)
      • @AlexPoole 你是对的,ASCII 比较首先是错误的,OP 的代码也适用于我的示例数据,我的编译器。我编辑了我的答案。
      猜你喜欢
      • 1970-01-01
      • 2021-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-13
      相关资源
      最近更新 更多