当没有INSERT语句时,为什么我会收到"字符串或二进制数据会被截断错误"?

dew*_*rde 3 sql t-sql sql-server truncation sql-server-2012

我有这个问题:

USE [SomeDatabase];
GO

DECLARE @percentageValue decimal(15,4) = 1.50; 

SELECT a.ID, a.Amount, a.Status 
FROM [dbo].ATable as a          
INNER JOIN [dbo].BTable as b 
  ON a.LinkToB = b.ID
INNER JOIN [OtherDatabase].[dbo].CTable as value 
  ON value.[Key] = CONCAT(N'APrefixAboutThisLongThatsNecessaryBecauseDontAsk',b.AltID)
WHERE a.Status = N'SomeStatus'
AND a.Amount > (COALESCE(TRY_CONVERT(DECIMAL(15,2), value.Value), 0)*@percentageValue);
GO
Run Code Online (Sandbox Code Playgroud)

(为保密而编辑的实际列名称)

我得到了传统的说法:"消息8152,级别16,状态10,行3字符串或二进制数据将被截断." 错误.谷歌告诉我,我正试图在一个太小的列中插入一些东西,这是有道理的.

但是,这不是一个INSERT操作(这实际上是我的查询的所有SQL),所以我不能在我的生活中检测截断发生的位置或原因.我认为这是Transact-SQL的内容,但最奇怪的问题是,尽管有错误,我仍然从查询中获得结果.

根据要求,这是表模式的相关部分.

USE [SomeDatabase]

CREATE TABLE [dbo].[ATable](
    [ID] [uniqueidentifier] NOT NULL,
    [Amount] [decimal](15, 2) NOT NULL,
    [Status] [nvarchar](32) NOT NULL,
    [LinkToB] [uniqueidentifier] NOT NULL,
 CONSTRAINT [PK_TableA] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
))
GO

CREATE TABLE [dbo].[BTable](
    [ID] [uniqueidentifier] NOT NULL,
    [AltID]  [uniqueidentifier] NOT NULL
 CONSTRAINT [PK_TableA] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
))
GO

USE [OtherDatabase]
GO

CREATE TABLE [dbo].[CTable](
    [ID] [uniqueidentifier] NOT NULL,
    [Key] [nvarchar](100) NOT NULL,
    [Value] [nvarchar](max) NOT NULL)
CONSTRAINT [PK_TableC] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)
Run Code Online (Sandbox Code Playgroud)

dew*_*rde 8

好的,一旦你知道这个秘密就会变得有趣但很明显.

如注释(和架构)中所述,value.Value是nvarchar(max).它是传统的Key-Value反模式,用于存储您不希望存储在特定位置的数据.现在,我们在这个列上运行try_convert(nvarchar(max)),但这很好,因为

 ON Key = N'SomethingSpecific'
Run Code Online (Sandbox Code Playgroud)

子句意味着它只在Key = N'SomethingSpecific'行上运行try_convert,对吗?

对?

不.

根据表中数据,执行计划可以选择try_convert列中的每个值.并且nvarchar(max)中的一行超出了try_convert参数的容量.因此崩溃.这也解释了为什么我得到了我期望得到的所有结果,它正在评估这些结果,然后在我预期它忽略的数据行上崩溃.

更好的是,不相关的数据或结构更改可能会改变执行计划的行为,所以这个错误可能会变得"Heisenbug"非常容易和消失,因为更多/更少的数据,对结构的微小更改,以及在一个调试过程中,我是非常确定空白.

那么最简单的解决方案是什么?将配置值分别输入到try_convert的变量中,然后使用它.

对于任何设计数据库或编写数据库代码的人:

  • 不要在SQL where子句中操作数据类型
  • 不要使用一列来存储截然不同的数据类型,并编写复杂的转换例程来处理问题
  • 请为数据使用正确的数据类型,并使用正确的数据类型处理其他表
  • 请记住,SQL函数通常在引擎盖下执行类似INSERT的操作