SQL NVARCHAR 和 VARCHAR 限制

Posted

技术标签:

【中文标题】SQL NVARCHAR 和 VARCHAR 限制【英文标题】:SQL NVARCHAR and VARCHAR Limits 【发布时间】:2012-09-20 08:02:23 【问题描述】:

所有,我有一个大型(不可避免的)动态 SQL 查询。由于选择标准中的字段数量,包含动态 SQL 的字符串正在增长超过 4000 个字符。现在,我知道NVARCHAR(MAX) 的最大值为 4000,但是在 Server Profiler 中查看执行的 SQL 以获取语句

DELARE @SQL NVARCHAR(MAX);
SET @SQL = 'SomeMassiveString > 4000 chars...';
EXEC(@SQL);
GO

似乎工作(!?),对于另一个同样大的查询,它会引发与此 4000 限制相关联的错误(!?),它基本上会在此 4000 限制之后修剪所有 SQL,并给我留下一个语法错误。尽管在探查器中,它以完整(!?)显示此动态 SQL 查询。

这里到底发生了什么,我是否应该将此@SQL 变量转换为 VARCHAR 并继续使用它?

感谢您的宝贵时间。

附言。如果能够打印出超过 4000 个字符来查看这些大查询,那也太好了。以下限4000个

SELECT CONVERT(XML, @SQL);
PRINT(@SQL);

有没有其他很酷的方法?

【问题讨论】:

MAX 不是 4000 限制的同义词,它的 1..4000 MAX 当这只是一个 Sql Server 问题时,为什么你用 C# dll 和设置 s 标记了这个问题 已编辑。感谢您发现... PRINT 将连接 4000 个字符(对于 unicode)或 8000 个字符(对于单字节编码)。我怀疑这是造成混乱的根源。 【参考方案1】:

我了解NVARCHAR(MAX) 的最大设置为 4000

你的理解是错误的。 nvarchar(max) 最多可以存储(有时甚至超过)2GB 的数据(10 亿个双字节字符)。

来自在线书籍中的nchar and nvarchar,语法是

nvarchar [ ( n | max ) ]

| 字符表示这些是替代品。即您指定 either n 或文字 max

如果您选择指定特定的n,则它必须介于 1 和 4,000 之间,但使用 max 会将其定义为大型对象数据类型(替换 ntext,已弃用)。

事实上,在 SQL Server 2008 中,对于一个变量,如果tempdb (Shown here) 中有足够的空间,似乎可以无限期超过 2GB 的限制

关于你问题的其他部分

连接时的截断取决于数据类型。

    varchar(n) + varchar(n) 将截断 8,000 个字符。 nvarchar(n) + nvarchar(n) 将截断 4,000 个字符。 varchar(n) + nvarchar(n) 将截断 4,000 个字符。 nvarchar 具有更高的优先级,因此结果为 nvarchar(4,000) [n]varchar(max) + [n]varchar(max) 不会截断(对于 varchar(max) + varchar(n) 不会截断(对于 varchar(max)。 varchar(max) + nvarchar(n) 不会截断(对于 nvarchar(max)。 nvarchar(max) + varchar(n) 将首先将varchar(n) 输入转换为nvarchar(n),然后进行连接。 如果 varchar(n) 字符串的长度大于 4,000 个字符,则强制转换为 nvarchar(4000) 并且会发生截断

字符串字面量的数据类型

如果您使用N 前缀并且字符串长度nvarchar(n),其中n 是字符串的长度。所以 N'Foo' 将被视为 nvarchar(3) 例如。如果字符串长度超过 4,000 个字符,它将被视为nvarchar(max)

如果您不使用N 前缀并且字符串长度varchar(n),其中n 是字符串的长度。如果长为varchar(max)

对于上述两种情况,如果字符串的长度为零,则 n 设置为 1。

较新的语法元素。

1.CONCAT 函数在这里没有帮助

DECLARE @A5000 VARCHAR(5000) = REPLICATE('A',5000);

SELECT DATALENGTH(@A5000 + @A5000), 
       DATALENGTH(CONCAT(@A5000,@A5000));

以上两种连接方法都返回 8000。

2.小心+=

DECLARE @A VARCHAR(MAX) = '';

SET @A+= REPLICATE('A',5000) + REPLICATE('A',5000)

DECLARE @B VARCHAR(MAX) = '';

SET @B = @B + REPLICATE('A',5000) + REPLICATE('A',5000)


SELECT DATALENGTH(@A), 
       DATALENGTH(@B);`

返回

-------------------- --------------------
8000                 10000

注意@A 遇到了截断。

如何解决您遇到的问题。

由于您将两个非 max 数据类型连接在一起,或者因为您将 varchar(4001 - 8000) 字符串连接到 nvarchar 类型的字符串(甚至是 nvarchar(max)),您将被截断。

要避免第二个问题,只需确保所有字符串文字(或至少那些长度在 4001 - 8000 范围内的字符串)都以 N 开头。

为了避免第一个问题,将分配从

DECLARE @SQL NVARCHAR(MAX);
SET @SQL = 'Foo' + 'Bar' + ...;

DECLARE @SQL NVARCHAR(MAX) = ''; 
SET @SQL = @SQL + N'Foo' + N'Bar'

因此NVARCHAR(MAX) 从一开始就参与串联(因为每个串联的结果也将是NVARCHAR(MAX) 这将传播)

查看时避免截断

确保您选择了“结果到网格”模式,然后您可以使用

select @SQL as [processing-instruction(x)] FOR XML PATH 

SSMS 选项允许您为XML 结果设置无限长度。 processing-instruction 位避免了诸如< 等字符显示为< 的问题。

【讨论】:

@Killercam - 在此过程中,您可能会隐式转换为 nvarchar(4000)。如果字符串文字少于 4,000 个字符,则将其视为nvarchar(x)。连接另一个 nvarchar(x) 值将截断而不是向上转换为 nvarchar(max) @Killercam - 根据我的第一条评论,您可能会被截断。尝试将分配更改为DECLARE @SQL NVARCHAR(MAX) = ''; SET @SQL = @SQL + ,以便NVARCHAR(MAX) 参与连接。 @Killercam - 可能你有一个 4,000 到 8,000 个字符之间的字符串。使用 N 前缀将被视为 nvarchar(max) 没有它,它将被视为 varchar(n) 然后在连接到 nvarchar 时隐式转换为 nvarchar(4000) 我被这个答案启发了 很棒的答案。非常感谢!【参考方案2】:

好的,所以如果稍后问题是您有一个大于允许大小的查询(如果它不断增长可能会发生),您将不得不中断它成块并执行字符串值。因此,假设您有一个如下所示的存储过程:

CREATE PROCEDURE ExecuteMyHugeQuery
    @SQL VARCHAR(MAX) -- 2GB size limit as stated by Martin Smith
AS
BEGIN
    -- Now, if the length is greater than some arbitrary value
    -- Let's say 2000 for this example
    -- Let's chunk it
    -- Let's also assume we won't allow anything larger than 8000 total
    DECLARE @len INT
    SELECT @len = LEN(@SQL)

    IF (@len > 8000)
    BEGIN
        RAISERROR ('The query cannot be larger than 8000 characters total.',
                   16,
                   1);
    END

    -- Let's declare our possible chunks
    DECLARE @Chunk1 VARCHAR(2000),
            @Chunk2 VARCHAR(2000),
            @Chunk3 VARCHAR(2000),
            @Chunk4 VARCHAR(2000)

    SELECT @Chunk1 = '',
           @Chunk2 = '',
           @Chunk3 = '',
           @Chunk4 = ''

    IF (@len > 2000)
    BEGIN
        -- Let's set the right chunks
        -- We already know we need two chunks so let's set the first
        SELECT @Chunk1 = SUBSTRING(@SQL, 1, 2000)

        -- Let's see if we need three chunks
        IF (@len > 4000)
        BEGIN
            SELECT @Chunk2 = SUBSTRING(@SQL, 2001, 2000)

            -- Let's see if we need four chunks
            IF (@len > 6000)
            BEGIN
                SELECT @Chunk3 = SUBSTRING(@SQL, 4001, 2000)
                SELECT @Chunk4 = SUBSTRING(@SQL, 6001, (@len - 6001))
            END
              ELSE
            BEGIN
                SELECT @Chunk3 = SUBSTRING(@SQL, 4001, (@len - 4001))
            END
        END
          ELSE
        BEGIN
            SELECT @Chunk2 = SUBSTRING(@SQL, 2001, (@len - 2001))
        END
    END

    -- Alright, now that we've broken it down, let's execute it
    EXEC (@Chunk1 + @Chunk2 + @Chunk3 + @Chunk4)
END

【讨论】:

【参考方案3】:

您也必须使用 nvarchar 文本。这意味着你必须在你的大弦之前简单地有一个“N”,就是这样!没有限制了

DELARE @SQL NVARCHAR(MAX);
SET @SQL = N'SomeMassiveString > 4000 chars...';
EXEC(@SQL);
GO

【讨论】:

这不是完整的图片...如果您使用 N 前缀并且字符串的长度 nvarchar(n),其中 n 是字符串的长度。因此,例如 N'Foo' 将被视为nvarchar(3)。如果字符串长度超过 4,000 个字符,它将被视为nvarchar(max)。如果您不使用 N 前缀并且字符串长度为 varchar(n) 其中 n 是字符串的长度。如果更长为varchar(max)。对于上述两种情况,如果字符串的长度为零,则 n 设置为 1。【参考方案4】:
declare @p varbinary(max)
set @p = 0x
declare @local table (col text)

SELECT   @p = @p + 0x3B + CONVERT(varbinary(100), Email)
 FROM tbCarsList
 where email <> ''
 group by email
 order by email

 set @p = substring(@p, 2, 100000)

 insert @local values(cast(@p as varchar(max)))
 select DATALENGTH(col) as collen, col from @local

result collen > 8000, length col value is more than 8000 chars

【讨论】:

【参考方案5】:

接受的答案对我有帮助,但在连接涉及案例陈述的 varchars 时我被绊倒了。我知道 OP 的问题不涉及 case 语句,但我认为这对于像我这样在努力构建涉及 case 语句的长动态 SQL 语句时最终来到这里的其他人来说会有所帮助。

当使用带有字符串连接的 case 语句时,接受的答案中提到的规则独立地适用于 case 语句的每个部分。

declare @l_sql varchar(max) = ''

set @l_sql = @l_sql +
case when 1=1 then
    --without this correction the result is truncated
    --CONVERT(VARCHAR(MAX), '')
 +REPLICATE('1', 8000)
 +REPLICATE('1', 8000)
end

print len(@l_sql)

【讨论】:

以上是关于SQL NVARCHAR 和 VARCHAR 限制的主要内容,如果未能解决你的问题,请参考以下文章

SQL中varchar和nvarchar有啥区别?

SQL的语句最大长度限制多少字符

SQL Server中varchar(n)和nvarchar(n)

sqlserver中的nvarchar和varchar的区别

让 Hibernate 和 SQL Server 与 VARCHAR 和 NVARCHAR 配合使用

SQL中 char varchar和nvarchar的区别