【问题标题】:SQL Server - simple select and conversion between int and stringSQL Server - int 和 string 之间的简单选择和转换
【发布时间】:2011-07-10 21:10:38
【问题描述】:

我有一个这样的简单选择语句:

SELECT [dok__Dokument].[dok_Id], 
       [dok__Dokument].[dok_WartUsNetto], 
       [dok__Dokument].[dok_WartUsBrutto], 
       [dok__Dokument].[dok_WartTwNetto], 
       [dok__Dokument].[dok_WartTwBrutto], 
       [dok__Dokument].[dok_WartNetto], 
       [dok__Dokument].[dok_WartVat], 
       [dok__Dokument].[dok_WartBrutto], 
       [dok__Dokument].[dok_KwWartosc] 
  FROM [dok__Dokument] 
 WHERE [dok_NrPelnyOryg] = 2753 
   AND [dok_PlatnikId] = 174 
   AND [dok_OdbiorcaId] = 174 
   AND [dok_PlatnikAdreshId] = 625 
   AND [dok_OdbiorcaAdreshId] = 624

dok_NrPelnyOryg 列的类型为 varchar(30),并且不为空。

该表在该列中同时包含整数和字符串值,并且该 select 语句被触发了数百万次。

但是最近这开始崩溃并显示消息:

将 varchar 值“garbi czerwiec B”转换为数据类型 int 时转换失败。

小解释:该表包含多个“文档”记录,并且提到的列包含文档原始编号(来自多个不同来源)。

我知道我可以通过在数字周围添加 '' 来解决此问题,但我宁愿寻找一个解释,为什么它曾经可以工作,而现在它崩溃了却没有改变任何东西。

【问题讨论】:

  • 显然错误信息说明了一切。如果dok_NrPelnyOrygvarchar 类型,你为什么不查询它呢? dok_NrPelnyOryg = '2753'。自动转换完全失败,因为他无法将您的值转换为数字。
  • 公平地说,问题不在于如何修复它,而更多的是关于为什么它工作了一段时间然后停止工作。
  • @Aaron Bertrand - 这就是重点。我知道如何解决它,我写过它。

标签: sql sql-server tsql


【解决方案1】:

可能是计划更改(由于更改的统计信息、重新编译等)导致更早地评估此数据(例如完整扫描),或者此特定数据以前不在表中(可能在此开始发生之前,那里没有坏数据)。如果它应该是数字,则将其设为数字​​列。如果它也需要允许字符串,则停止将其视为数字。如果您正确参数化您的语句并始终传递一个 varchar,则您不必担心该值是否包含在单引号中。

【讨论】:

  • 查询是参数化的,这些值只是示例。它是通过使用 SqlCommand 对象和 ExecuteReader() 触发的。带有 reader.Read() 的 While 循环执行多次,然后在这一次上崩溃。这种方式已经工作了半年多了。
  • 如果它是参数化的,那么如何通过在它周围加上单引号来修复它?我想也许我们对参数化的含义有不同的想法。您应该能够从 C# 将参数传递给您的查询,而不必担心字符串分隔符等技术细节。
  • 不,我认为我们对参数化的含义有相同的想法:) 我的意思是我可以在从管理工作室执行它的同时修复它。我现在检查了代码中的参数是 System.Data.DbType.Int32 类型,这是完全错误的。在代码中解决这个问题并不容易,因为我将所有值都作为对象(不是具体类型)SqlParameter ,因为我发现即使对象下的真实类型是 System.String 也无法自行处理。
  • 如果您使用存储过程,您应该能够正确获取参数类型,而无需任何魔法巫术。 :-)
  • 我认为我无法以这种方式解决它。该软件同步两个数据库(会计和电子商务)并根据 xml 配置文件即时生成查询。引入存储过程将是一个重要的步骤,需要很多天才能正确实施。 (例如,配置文件有时会发生变化,这应该会触发更新存储过程)
【解决方案2】:

所有这些相等比较操作都受制于 SQL Server 的Data Type Precedence 规则:

当一个运算符将两个 不同数据类型的表达式, 数据类型优先规则 指定数据类型与 较低的优先级转换为 具有较高优先级的数据类型。

由于字符类型的优先级低于int类型,所以查询基本相同:

SELECT ...
  FROM [dok__Dokument] 
 WHERE cast([dok_NrPelnyOryg] as int) = 2753
  ...

这有两个效果:

  • 它使 WHERE 子句中涉及的列上的所有索引都无用
  • 可能会导致转换错误。

你不是第一个遇到这个问题的,事实上我遇到的几个 CSS 案例让我最终写了一篇关于这个的文章:On SQL Server boolean operator short-circuit

解决问题的正确方法是如果字段值是数字,那么列类型应该是数字。由于您说数据来自您无法更改的第 3rd 方应用程序,因此最好的解决方案是放弃此应用程序的供应商并选择一个知道在做什么的供应商。除此之外,您需要在字符列上搜索字符类型:

SELECT ...
  FROM [dok__Dokument] 
 WHERE [dok_NrPelnyOryg] = '2753'
  ...

在 .Net 托管的 ADO.Net 术语中,这意味着您使用 SqlCommand,如下所示:

SqlCommand cmd = new SqlCommand (@"    SELECT ...
      FROM [dok__Dokument] 
     WHERE [dok_NrPelnyOryg] = @nrPelnyOryg
      ... ");
cmd.Parameters.Add("@nrPelnyOryg", SqlDbType.Varchar).Value = "2754";
...

请确保您不会陷入传递 NVARCHAR 参数 (Unicode) 以与 VARCHAR 列进行比较的简单陷阱,因为之前引用的相同数据类型优先规则将强制在 NVARCHAR 类型上进行比较,从而使索引再次变得无用。陷入这个陷阱的最简单方法是使用删除的AddWithValue 并传入一个字符串值。

【讨论】:

  • 谢谢,我会按照您建议的方式实现查询。
【解决方案3】:

您的查询停止工作,因为有人使用INT 将文本字符串插入到您正在查询的字段中。在那之前,可以隐式转换数据,但现在不再是这种情况了。

我会检查您的数据,更重要的是,检查模型;正如 Aaron 所说,您是否需要在该字段中允许字符串?如果不是,请更改数据类型以防止将来发生这种情况。

【讨论】:

  • 此字段来自会计软件数据库,无法更改。更重要的是我不想改变它,因为它包含字符串和整数(正如我写的那样,这些值来自不同的来源)。字符串从一开始就在那里,因为采购发票也存储在那里(根据许多不同的模式编号,如 22/FV/2011 等)
  • 在这种情况下,正如 Aaron 所说,这可能是因为数据发生了变化,并且正在以与以前不同的方式进行评估。如果所有字符串数据在查询之前都被排除在外,那么隐式转换就不是问题。似乎字符串数据现在正在使其进一步进入管道并且转换失败。我知道您不是在问如何解决问题,而是在编写使用隐式转换的代码,并希望它能够永远工作,这似乎有点乐观:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多