'String or binary data would be truncated'没有超过长度的数据

'String or binary data would be truncated' without any data exceeding the length

昨天突然报告有人无法再获取一些数据,因为问题Msg 2628, Level 16, State 1, Line 57 String or binary data would be truncated in table 'tempdb.dbo.#BC6D141E', column 'string_2'. Truncated value: '!012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678'.出现了。

没有我们的表格,我无法创建复制。这是我能到达的最接近的:

-- Create temporary table for results
DECLARE @results TABLE (
    string_1 nvarchar(100) NOT NULL,
    string_2 nvarchar(100) NOT NULL
);

CREATE TABLE #table (
    T_ID BIGINT NULL,
    T_STRING NVARCHAR(1000) NOT NULL
);

INSERT INTO #table VALUES
    (NULL, '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789'),
    (NULL, '!0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789!');

WITH abc AS 
(
    SELECT
        '' AS STRING_1,
        t.T_STRING AS STRING_2
    FROM
        UT
        INNER JOIN UTT ON UTT.UT_ID = UT.UT_ID 
        INNER JOIN MV ON MV.UTT_ID = UTT.UTT_ID
        INNER JOIN OT ON OT.OT_ID = MV.OT_ID
        INNER JOIN #table AS T ON T.T_ID = OT.T_ID -- this will never get hit because T_ID of #table is NULL
)
INSERT INTO @results
SELECT STRING_1, STRING_2 FROM abc
ORDER BY LEN(STRING_2) DESC

DROP TABLE #table;

如您所见,#table 的连接无法产生任何结果,因为所有 T_ID 都是 NULL,但我收到上述错误。结果集为空。

如果结果集中包含超过 100 个字符的文本,那还好,但事实并非如此,因为它是空的。如果我删除 INSERT INTO @results 并显示结果,它不包含任何超过 100 个字符的文本。 ORDER BY 仅用于确定错误的文本值(与原始数据)。

当我使用 SELECT STRING_1, LEFT(STRING_2, 100) FROM abc 时它确实有效,但它也不包含要截断的文本。

因此:我错过了什么?是 SQL 服务器的错误吗?

-- this will never get hit 是一个错误的假设。众所周知,SQL 服务器可能会在结果明显不可能之前尝试评估您的部分查询。

更简单的重现(来自 this post and this db<>fiddle):

CREATE TABLE dbo.t1(id int NOT NULL, s varchar(5) NOT NULL);
CREATE TABLE dbo.t2(id int NOT NULL);
 
INSERT dbo.t1 (id, s) VALUES (1, 'l=3'), (2, 'len=5'), (3, 'l=3');
INSERT dbo.t2 (id)    VALUES (1), (3), (4), (5);
GO
 
DECLARE @t table(dest varchar(3) NOT NULL);
 
INSERT @t(dest) SELECT t1.s 
  FROM dbo.t1 
  INNER JOIN dbo.t2 ON t1.id = t2.id;

结果:

Msg 2628, Level 16, State 1
String or binary data would be truncated in table 'tempdb.dbo.#AC65D70E', column 'dest'. Truncated value: 'len'.

虽然我们应该只检索具有适合目标列的值的行(id 是 1 或 3,因为这是唯一符合连接条件的两行),但错误消息表明 id 所在的行is 2 也被返回,尽管我们知道它不可能被返回。

这是预计的计划:

这表明 SQL 服务器希望在过滤器消除较长的值之前转换 t1 中的所有值 。而且很难预测或控制 SQL 服务器何时会以您不希望的顺序处理您的查询 - 您可以尝试使用查询提示来尝试强制顺序或远离散列连接,但那些可以以后会导致其他更严重的问题。

最好的解决方法是调整临时 table 的大小以匹配源(换句话说,使其足够大以适应源中的任何值)。 blog post and db<>fiddle 解释了解决此问题的其他一些方法,但声明列足够宽是最简单且干扰最少的方法。