为什么值的长度有时是列的宽度而不是字符串的长度?

alx*_*x9r 6 sql-server sql-server-2017

考虑以下 SQL

DECLARE @JSON VARCHAR(max);
DECLARE @t AS TABLE(
    field  char(32),
    len1   int,
    nfield nchar(32),
    nlen1  int,
    vfield varchar(32),
    vlen1  int
);

SET @JSON = '[
    { "Field" : "abcd" }
]'
INSERT INTO @t
SELECT
    Field      as field,
    Len(Field) as len1,
    Field      as nfield,
    Len(Field) as nlen1,
    Field      as vfield,
    Len(Field) as vlen1
FROM OPENJSON (@JSON)
WITH ( Field nchar(32) );

INSERT INTO @t ( field , len1       ,nfield, nlen1      ,vfield, vlen1      )
VALUES         ( 'efgh', len('efgh'),'efgh', len('efgh'),'efgh', len('efgh'))

SELECT
    field,
    len1,
    len(field)  as len2,
    nlen1,
    len(nfield) as nlen2,
    vlen1,
    len(vfield) as vlen2
FROM @t;
Run Code Online (Sandbox Code Playgroud)

哪个输出

field  len1  len2  nlen1  nlen2  vlen1  vlen2
------ ----- ----- ------ ------ ------ ------
abcd   4     4     4      32     4      4
efgh   4     4     4      4      4      4
Run Code Online (Sandbox Code Playgroud)

该字段中源自 JSON 的值的长度nchar(32)为 32,而其他所有值都是字符串的长度。这让我感到惊讶,所以我正在寻找潜在的解释。

12 个案例中的这一案例有不同的结果,这是怎么回事?

Aar*_*and 6

更简单的重现:

DECLARE @t TABLE(f1 char(32), f2 nchar(32));

INSERT @t SELECT f AS f1, f AS f2 FROM OPENJSON('[
    { "f" : "abcd" }
]') WITH (f nchar(32));

SELECT f1, f2, f1l = LEN(f1), f2l = LEN(f2) FROM @t;
Run Code Online (Sandbox Code Playgroud)

输出:

f1 f2 f1l f2l
A B C D A B C D 4 32

我不太知道这里发生了什么,但是 Hannah 可能会关注一些有关数据类型/隐式转换的内容,同时从 JSON 中提取数据并将其插入表中,并进行谁知道 JSON 内部有多少次翻译-土地。

我确实注意到,当我在 Azure Data Studio 中运行此查询时,网格输出看起来与我的预期相反,例如,长度为 4,我预计左列是瘦的,右列是宽的:

Azure Data Studio 中的奇怪输出

当然,LEN()当有尾随空格时,它并不能真正告诉您字符串的宽度,因为它们被忽略了。

如果我复制这些单元格的内容,就会更清楚发生了什么(只是不是为什么)。左边,居然有32个字符。那么为什么右侧不这样做呢?好吧,该字符串填充的东西绝对不是空格:

Azure Data Studio 中更奇怪的输出

其他注意事项:

  • DATALENGTH()按预期工作(左侧 32,右侧 64)。

  • 通过元数据发现函数传递它并没有产生任何有趣的东西,也没有提供有关这些非打印字符的来源的任何线索:

      SELECT name, system_type_name
      FROM sys.dm_exec_describe_first_result_set
      (N'DECLARE @t TABLE(f1 char(32), f2 nchar(32));
    
      INSERT @t SELECT f AS f1, f AS f2 FROM OPENJSON(''[
          { "f" : "abcd" }
      ]'') WITH (f nchar(32));
    
      SELECT f1, f2 FROM @t;', NULL, 0);
    
    Run Code Online (Sandbox Code Playgroud)
    姓名 系统类型名称
    f1 字符(32)
    f2 nchar(32)

然后我回去决定检查每个字符串最右边的字符,果然,在一种情况下它是空格 ( char(32)),在另一种情况下它是终止符 ( nchar(0)):

DECLARE @t TABLE(f1 char(32), f2 nchar(32));

INSERT @t SELECT f AS f1, f AS f2 FROM OPENJSON('[
    { "f" : "abcd" }
]') WITH (f nchar(32));

SELECT f1, f2, ASCII(RIGHT(f1,1)),UNICODE(RIGHT(f2,1)) FROM @t;
Run Code Online (Sandbox Code Playgroud)
f1 f2 - -
A B C D A B C D 32 0

所以,我不知道为什么 JSON 会产生这个工件,但您可以使用DATALENGTH代替来解决它LEN,或者也许通过使用nvarchar代替nchar(这对于长度明显不同的字符串来说可以说是更好的选择)。


Pau*_*ite 5

例子

DECLARE @t table (f nchar(5) NOT NULL);

INSERT @t (f)
SELECT OJ.f 
FROM OPENJSON(N'[{ "f" : "abcd" }]')
    WITH (f nchar(5)) AS OJ;

SELECT 
    T.f,
    len_f = LEN(T.f),
    dl_f = DATALENGTH(T.f),
    bin_f = CONVERT(varbinary(10), f) 
FROM @t AS T;
Run Code Online (Sandbox Code Playgroud)
F 只_f dl_f bin_f
A B C D 5 10 0x61006200630064000000

原因

当 JSON 元素的数据类型与目标表完全匹配时,SQL Server 错误地认为不需要填充。

在上面的例子中,声明的类型是,nchar(5)但源字符串只有 4 个字符。尽管类型匹配,字符串仍应用空格填充(ANSI_PADDING始终启用 for nchar)。

它看起来用零填充的事实可能是由于缓冲区溢出保护和用零初始化缓冲区。也可以想象,它也可能是错误的填充,这对于二进制值来说是合适的。

无论如何,这都是产品缺陷。

解决方法

确保 JSON 中的字符串与声明的类型匹配而不需要填充,或者将返回的列转换为稍大的类型,以便 SQL Server 应用通常的填充和截断规则。

DECLARE @t table (f nchar(5) NOT NULL);

INSERT @t (f)
SELECT CONVERT(nchar(6), OJ.f) -- Workaround
FROM OPENJSON(N'[{ "f" : "abcd" }]')
    WITH (f nchar(5)) AS OJ;

SELECT 
    T.f,
    len_f = LEN(T.f),
    dl_f = DATALENGTH(T.f),
    bin_f = CONVERT(varbinary(10), f) 
FROM @t AS T;
Run Code Online (Sandbox Code Playgroud)
F 只_f dl_f bin_f
A B C D 4 10 0x61006200630064002000

您也可以nvarchar(5)WITH子句中使用。