【问题标题】:Finding non-numeric values in varchar column在 varchar 列中查找非数字值
【发布时间】:2017-12-08 09:50:16
【问题描述】:

要求:

通用查询/函数,用于检查表中 varchar 列中提供的值是否实际上是数字且精度不超过允许的精度。

可用值:

Table_Name、Column_Name、允许的精度、允许的比例

一般建议是创建一个函数并使用 to_number() 来验证值,但它不会验证允许的长度(精度刻度)。

我的解决方案:

使用正则表达式验证号码NOT REGEXP_LIKE(COLUMN_NAME, '^-?[0-9.]+$')

验证左组件的长度(小数点前)(我不知道它的实际名称是什么)因为对于规模,oracle 会在需要时自动四舍五入。由于实际列是 varchar,我将使用 substr、instr 来查找小数点左侧的组件。

如上所述,Regexp 允许像 123...123124..55 这样的数字,我还将验证小数点的数量。 [如果 > 1 则错误]

查询无效号码:

Select * From Table_Name 
Where
(NOT REGEXP_LIKE(COLUMN_NAME, '^-?[0-9.]+$')
OR
Function_To_Fetch_Left_Component(COLUMN_NAME) > (Precision-Scale)
/* Can use regexp_substr now but i already had a function for that */
OR
LENGTH(Column_Name) - LENGTH(REPLACE(Column_Name,'.','')) > 1
/* Can use regexp_count aswell*/)

我对我的解决方案感到满意和满意,直到出现只有“。”的列value 逃脱了我的支票,我看到了支票的局限性。虽然添加另一个检查来验证这一点也可以解决我的问题,但 solution 整体上看起来对我来说效率很低。

我将非常感谢[以任何方式]提供更好的解决方案。

提前致谢。

【问题讨论】:

    标签: sql oracle oracle11g


    【解决方案1】:

    寻找:

    • 一个或多个数字可选地后跟一个小数点和零个或多个数字;或
    • 前导小数点(前面没有个位),然后是一个或多个(十进制)数字。

    像这样:

    Select *
    From   Table_Name 
    Where  NOT REGEXP_LIKE(COLUMN_NAME, '^[+-]?(\d+(\.\d*)?|\.\d+)$')
    

    如果您不想在数字字符串中填充零值,那么:

    Select *
    From   Table_Name 
    Where  NOT REGEXP_LIKE(COLUMN_NAME, '^[+-]?(([1-9]\d*|0)(\.\d*)?|\.\d+)$')
    

    具有精度和规模(假设它按照 NUMBER( precision, scale ) 数据类型和 scale < precision 工作):

    Select *
    From   Table_Name 
    Where  NOT REGEXP_LIKE(COLUMN_NAME, '^[+-]?(\d{1,'||(precision-scale)||'}(\.\d{0,'||scale||'})?|\.\d{1,'||scale||'})$')
    

    或者,对于具有精度和比例的非零填充数字:

    Select *
    From   Table_Name 
    Where  NOT REGEXP_LIKE(COLUMN_NAME, '^[+-]?(([1-9]\d{0,'||(precision-scale-1)||'}|0)(\.\d{0,'||scale||'})?|\.\d{1,'||scale||'})$')
    

    或者,对于任何精度和比例:

    Select *
    From   Table_Name 
    Where  NOT REGEXP_LIKE(
                 COLUMN_NAME,
                 CASE
                   WHEN scale <= 0
                   THEN '^[+-]?(\d{1,'||precision||'}0{'||(-scale)||'})$'
                   WHEN scale < precision
                   THEN '^[+-]?(\d{1,'||(precision-scale)||'}(\.\d{0,'||scale||'})?|\.\d{1,'||scale||'})$'
                   WHEN scale >= precision
                   THEN '^[+-]?(0(\.0{0,'||scale||'})?|0?\.0{'||(scale-precision)||'}\d{1,'||precision||'})$'
                 END
               )
    

    【讨论】:

    • 仅供我理解 ^ - 开始... [+-]? - 0 或 1 次出现 + 或 -... ([1-9]\d*|0) - 以 1-9 开头,后跟 0 或 1 次出现数字或零,因此允许出现 1 个零,但可以有多个不是.....(\.\d*)? - 0 或 1 次出现 1 个小数 + 数字.... 或.... 小数后跟 1 个或多个数字... $ - 结束.... 谢谢您的宝贵时间。
    • @pOrinG [1-9]\d* 以数字 1-9 开头,然后是零个或多个任意数字 (0-9),因此 ([1-9]\d*|0)[1-9]\d* 模式或单个 0 以便整数部分可以是任意数量的数字,但必须以非零数字开头,除非它是单个数字 0。关于小数的一点 - 是的。
    • 非常感谢。
    • 您能否解释一下这如何与 OP 想要的可变精度和规模一起工作?
    • @MatthewMcPeak 更新
    【解决方案2】:

    精度意味着您最多需要allowed_precision 数字(严格来说,不包括前导零,但我会忽略它)。刻度表示allowed_scale 最多可以在小数点后。

    这建议使用正则表达式,例如:

    [-]?[0-9]{1,<before>}[.]?[0-9]{0,<after>}
    

    可以构造正则表达式:

    NOT REGEXP_LIKE(COLUMN_NAME,
                    REPLACE(REPLACE('[-]?[0-9]{1,<before>}[.]?[0-9]{0,<after>}', '<before>', allowed_precision - allowed_scale
                                   ), '<after>', allowed_scale)
    

    现在,可变正则表达式的效率非常低。您也可以使用like 和其他函数来执行逻辑。我认为条件是:

    (column_name not like '%.%.%' and
     column_name not like '_%-%' and
     translate(column_name, '0123456789-.x', 'x') is null and
     length(translate(column_name, '-.x', 'x') <= allowed_precision and
     length(translate(column_name, '-.x', 'x') >= 1 and
     instr(translate(column_name, '-.x', 'x'), '.') <= allowed_precision - allowed_scale
    )
    

    【讨论】:

    • 您好,建议 REGEXP [-]?[0-9]{1,&lt;before&gt;}[.]?[0-9]{0,&lt;after&gt;} 无法按预期工作,因为如果缺少小数点,它会接受总计超过小数点左侧允许值的 before + after 数字。例如对于 number(4,2),查询接受 1111,稍后将被 oracle 拒绝。如果您再解释一下翻译,我将不胜感激,因为我无法理解类似 queryyy 的翻译。
    • 如果使用translate(column_name, '0123456789-.x', 'x') is null这个条件是为了查找除了提到的字符之外是否还有任何字符,那么语法应该是translate(column_name, 'x0123456789-.', 'x') is null否则它将用x替换0并显示而不是替换所有带有 ''/blank/empty 的数字。请指教。
    • 您删除正则表达式的最后提示使我创建了以下查询,这在我的测试中将执行时间减少了一半以上!!! with x as (Select '012341' temp_test from dual) Select * From x WHERE (Translate(temp_test,'x0123456789-.','x') is not null OR temp_test = '.' OR temp_test = '-' OR temp_test like '%-%-%' OR temp_test like '%.%.%' OR Instr(temp_test||'.','.')-1 &gt; 5 ) ;我希望我涵盖了所有需要的检查,因为我特别不担心接受科学记数法或超过小数部分的精度,因为甲骨文会 tc。
    • 更新查询:with x as (Select '012341' temp_test from dual) Select * From x WHERE (Translate(temp_test,'x0123456789-.','x') is not null OR temp_test = '.' OR temp_test = '-' OR temp_test = '-.' OR Instr(temp_test,'-') &gt; 1 OR temp_test like '%-%-%' OR temp_test like '%.%.%' OR Instr(temp_test||'.','.')-1 &gt; 5 ) ;
    猜你喜欢
    • 1970-01-01
    • 2011-04-22
    • 1970-01-01
    • 1970-01-01
    • 2019-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-10
    相关资源
    最近更新 更多