【问题标题】:How does one filter based on whether a field can be converted to a numeric?如何根据字段是否可以转换为数字进行过滤?
【发布时间】:2011-06-16 01:59:54
【问题描述】:

我有一份已经使用了一段时间的报告——事实上,公司的发票系统在很大程度上依赖于这份报告(免责声明:我没有写它)。过滤基于 VarChar(50) 类型的字段是否介于用户传入的两个数值之间。

问题在于,现在过滤数据的字段不仅有简单的非数字值,例如“/A”、“TEST”和大量其他非数字数据,而且还有数字值似乎在挑战我能想到的任何类型的数字转换。

以下(简化的)测试查询演示了失败:

Declare  @StartSummary Int,
         @EndSummary Int

Select   @StartSummary = 166285,
         @EndSummary = 166289

Select   SummaryInvoice
From     Invoice
Where    IsNull(SummaryInvoice, '') <> ''
And      IsNumeric(SummaryInvoice) = 1
And      Convert(int, SummaryInvoice) Between @StartSummary And @EndSummary

我也尝试过使用 bigint、real 和 float 进行转换,但都给了我类似的错误:

消息 8115,第 16 级,状态 2,第 7 行 算术溢出错误转换 将表达式转换为数据类型 int。

我尝试过其他较大的数值数据类型,例如 BigInt,但出现了同样的错误。我还尝试使用子查询通过仅提取具有数字数据的字段然后在包装查询中转换这些字段来回避转换问题,但随后我得到其他错误,这些错误都是主题的变体,表明存储的值SummaryInvoice 字段无法转换为相关数据类型。

除了仅将带有数字 SummaryInvoice 字段的记录提取到临时表中,然后针对临时表进行查询之外,是否有任何一步解决方案可以解决此问题?

编辑:这是我怀疑导致问题的字段数据:

汇总发票


1111111111111111111111111

IsNumeric 表明该字段是数字的——它。但是尝试将其转换为 BigInt 会导致算术溢出。有任何想法吗?这似乎不是一个孤立的事件,似乎有许多记录填充了导致此问题的数据。

【问题讨论】:

    标签: sql tsql sql-server-2000


    【解决方案1】:

    看来ISNUMERIC 函数会出现问题,因为如果可以转换为任何数字类型(包括.,e0 等),它就会返回 1。如果您的数字超过 2^63-1,您可以使用 DECIMALNUMERIC。我不确定你是否可以使用PATINDEXSummaryInvoice 执行正则表达式查看,但如果可以,那么你应该试试这个:

    SELECT SummaryInvoice
    FROM Invoice
    WHERE ISNULL(SummaryInvoice, '') <> ''
    AND CASE WHEN PATINDEX('%[^0-9]%',SummaryInvoice) > 0 THEN CONVERT(DECIMAL(30,0), SummaryInvoice) ELSE -1 END
    BETWEEN @StartSummary And @EndSummary
    

    【讨论】:

    • +1 我忘记了 %[^0-9]% 技巧。我认为更新后 OP 也需要小数(38,0)
    【解决方案2】:

    您无法保证 WHERE 子句过滤器的应用顺序。

    分离内部和外部的一个丑陋的选择。

    SELECT
       *
    FROM
        (
        Select   TOP 2000000000
                 SummaryInvoice
        From     Invoice
        Where    IsNull(SummaryInvoice, '') <> ''
        And      IsNumeric(SummaryInvoice) = 1
        ORDER BY SummaryInvoice
        ) foo
    WHERE
        Convert(int, SummaryInvoice) Between @StartSummary And @EndSummary
    

    另一个使用 CASE

    Select   SummaryInvoice
    From     Invoice
    Where    IsNull(SummaryInvoice, '') <> ''
        And     
        CASE WHEN IsNumeric(SummaryInvoice) = 1 THEN Convert(int, SummaryInvoice) ELSE -1 END
              Between @StartSummary And @EndSummary
    

    YMMV

    编辑:问题更新后

    1. 使用小数(38,0)而不是整数
    2. 将 ISNUMERIC(SummaryInvoice) 更改为 ISNUMERIC(SummaryInvoice + '0e0')

    【讨论】:

    • 我已经尝试了你的第一个例子,它给了我Arithmetic overflow error converting expression to data type bigint。我以为我尝试了您的第二个示例,但没有成功,将再次检查。
    • @BenAlabaster 那么你应该尝试使用CONVERT(BIGINT, 而不是CONVERT(INT,
    • +1,但使用ISNUMERIC要谨慎,转换为INT可能会遇到问题
    • @Lamak 如果您注意到我的评论,您就会意识到我已经尝试过 bigint。同样,IsNumeric 将报告任何由数字(加上许多其他字符)组成的值都是数字的,例如,+、-、$ 和任何其他货币符号都是数字的。问题是,如何获取包含 26 位数字的数字字符串并将其用作 between 子句的一部分?
    【解决方案3】:

    与 IsNumeric(SummaryInvoice) = 1 的 AND,在 SQL Server 中不会短路。

    但也许你可以使用

    AND (CASE IsNumeric(SummaryInvoice) = 1 THEN Convert(int, SummaryInvoice) ELSE 0 END) 在@StartSummary 和@EndSummary 之间

    【讨论】:

      【解决方案4】:

      您的第一个问题是修复您的数据库结构,使不良数据无法进入现场。您正在将创可贴放在需要缝合的伤口上,并想知道为什么它不愈合。

      数据库重构并不好玩,但需要在出现数据完整性问题时进行。我假设您并没有真正为 11,111,111,111,111,111,111,111,111 或“测试”开具发票。因此,永远不要让这些值被输入(如果您无法将结构更改为正确的数据类型,请考虑使用触发器来防止错误数据进入)并删除您确实拥有的错误数据。

      【讨论】:

      • 这个数据库是一个遗留系统,正如我们所说,它正在逐步淘汰。该字段不是数量,因此您指定的假设在这种情况下并不真正相关。重构数据库可能是一种解决方案,但是该表中有几百万条记录,其中有许多资源位于它们之上,这使得该任务变得非常艰巨。在这种情况下,我找到一个解决问题的方法真的很关键,而且不需要额外的工作,但这将允许需要此报告的工作人员在新产品不久发布之前绑定。
      猜你喜欢
      • 1970-01-01
      • 2019-09-30
      • 1970-01-01
      • 2018-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-02
      相关资源
      最近更新 更多