如何在SQL Server中需要比Datalength更多的字节数时将十进制转换为二进制

时间:2017-03-01 23:58:39

标签: sql-server

Decimal(4,0)需要5个字节才能存储在SQL Server中。这是根据文档说明所有decimal and numeric types精度1-9需要5个字节。 DATALENGTH函数也确认:

select DATALENGTH(convert(Decimal(4,0),256)) result

result
-----------
5

(1 row(s) affected)

但是,当我转换为binary(5)然后返回decimal(4,0)时,它会截断数据。

DECLARE @myval decimal (4, 0);  
SET @myval = 257;  
SELECT CONVERT(decimal(4,0), CONVERT(varbinary(5), @myval)) result

result
---------------------------------------
1

(1 row(s) affected)

数字的最后一个字节被切断。但是,如果我转换为二进制(6)或更多...我得到了正确的结果:

DECLARE @myval decimal (4, 0);  
SET @myval = 257;  
SELECT CONVERT(decimal(4,0), CONVERT(binary(6), @myval)) result  

result
---------------------------------------
257

(1 row(s) affected)

发生了什么事?如果我需要存储十进制值的二进制表示,我怎么知道需要多少字节?具体来说,确定将十进制(p,s)转换为二进制(x)所需的最小字节数x的通用公式是什么?

我需要将一些二进制数据编组到服务代理消息中,因此我需要将各种类型的数据转换为二进制字符串。是否有更健壮的方法来存储二进制值,而不是使用cast / convert?

6 个答案:

答案 0 :(得分:4)

让我们从DATALENGTH开始。来自MSDN

  

DATALENGTH对varchar,varbinary,text,image特别有用,   nvarchar和ntext数据类型,因为这些数据类型可以存储   可变长度数据。

最小十进制长度为5个字节,最大值为17个字节。 Decimal(p,s)不是可变长度数据。它具有精确的固定长度。例如,如果数字长度为1到9,则DATALENGTH将始终返回5

select DATALENGTH(convert(Decimal(38,0), 1)) -- result 5
select DATALENGTH(convert(Decimal(38,0), 1234567890)) -- result 5

如果数字长度为10到19,则DATALENGTH将始终返回9

select DATALENGTH(convert(Decimal(38,0), 12345678901)) -- result 9
select DATALENGTH(convert(Decimal(38,0), 111111111111111)) -- result 9

因此,DATALENGTH的结果将取决于数字的长度,但它不是真正的长度。

decimal(4,0)转换为binary(5)后,您将获得0x04 00 00 01 00 在这种情况下,只剩下最后一个字节用于您的号码。您可以在1个字节中存储的最大数量为255(HEX中的255等于FF

通过这种方式一切正常:

DECLARE @myval decimal (4, 0);  
SET @myval = 255;  
SELECT CONVERT(decimal(4,0), CONVERT(binary(5), @myval)) result, CONVERT(binary(5), @myval)

result                                  HEX
--------------------------------------- ------------
255                                     0x04000001FF

现在,请尝试编号256而不是255。 HEX中的256等于100,我们不能将100存储在1个字节中(HEX应该是0x04 00 00 01 00 1 但是没有空间 1

DECLARE @myval decimal (4, 0);  
SET @myval = 256;  
SELECT CONVERT(decimal(4,0), CONVERT(binary(5), @myval)) result, CONVERT(binary(5), @myval) HEX
result                                  HEX
--------------------------------------- ------------
0                                       0x0400000100

如果要存储0到9999之间的数字,则至少需要6个字节。查看257(HEX等于101

DECLARE @myval decimal (4, 0);  
SET @myval = 257;  
SELECT CONVERT(decimal(4,0), CONVERT(binary(6), @myval)) result, CONVERT(binary(6), @myval) HEX
result                                  HEX
--------------------------------------- --------------
256                                     0x040000010101

这里我们最后有6个字节0x04 00 00 01 01 0101 01 然后9999(HEX等于270F

DECLARE @myval decimal (4, 0);  
SET @myval = 9999;  
SELECT CONVERT(decimal(4,0), CONVERT(binary(6), @myval)) result, CONVERT(binary(6), @myval) HEX
result                                  HEX
--------------------------------------- --------------
9999                                    0x040000010F27

最后6个字节0x04 00 00 01 0F 2727 0F。 (从右到左阅读)

答案 1 :(得分:3)

文档说明它使用5个字节的存储空间。它没有声明在使用CAST或CONVERT将其转换为varbinary(N)时需要5个字节。事实上,文档警告您不要使用following note

将数字类型转换为二进制文件
  

不要尝试构造二进制值,然后将它们转换为数据   数字数据类型类别的类型。 SQL Server不保证   将十进制或数字数据类型的结果转换为二进制   在SQL Server版本之间将是相同的。

我怀疑在将数字转换为varbinary时,除了数据位之外,它还包括精度和比例信息。当存储在表中时,此信息由模式定义,因此不需要存储每个值的精度和比例,只需存储数据位。

答案 2 :(得分:2)

SQL中的二进制数据类型显然使用的编码方案取决于源数据类型。尝试运行以下内容,看看你得到了什么结果:

SELECT CAST(256 AS Binary(8)), 
       CAST(CAST(256 as decimal(4,0)) AS Binary(8)),  
       CAST(CAST(256 as decimal(9,6)) AS Binary(8)), 
       CAST(CAST(256 as float(2)) AS Binary(8))

请注意,您会收到以下结果:

0x0000000000000100  0x0400000100010000  0x090600010040420F  0x0000000043800000

这表明当十进制转换为二进制时,二进制包含显示十进制的长度和小数位数的编码。您还可以看到,根据这些,您将获得不同的二进制表示。同样清楚的是,整数和浮点数的编码方式不同。我认为这解释了为什么你会被截断。

对于原因不完全是答案,但如果您可以更改表格,则可以将列指定为sql_variant:

declare @test sql_variant


SET @test = 'I''m a string'


SELECT  @test As Col,
         SQL_VARIANT_PROPERTY(@test,'BaseType') AS 'Base Type',  
         SQL_VARIANT_PROPERTY(@test,'Precision') AS 'Precision',  
         SQL_VARIANT_PROPERTY(@test,'Scale') AS 'Scale'  

<强>结果

Col             Base Type   Precision   Scale
I'm a string    varchar     0           0

现在尝试使用decmal:

SET @test = 1.2

SELECT  @test As Col,
         SQL_VARIANT_PROPERTY(@test,'BaseType') AS 'Base Type',  
         SQL_VARIANT_PROPERTY(@test,'Precision') AS 'Precision',  
         SQL_VARIANT_PROPERTY(@test,'Scale') AS 'Scale'  

<强>结果

Col Base Type   Precision   Scale
1.2 numeric     2           1

答案 3 :(得分:2)

不应该依赖位模式,除非你可以控制其格式!

如果我理解正确,您希望传输大量数据,并且希望尽可能快速安全地执行此操作。

我知道获得可靠位模式的唯一方法是定义到字符串的转换 FOR XML(从2016 FOR JSON开始)使用定义良好的字符串格式来保证类型安全

以下示例将创建具有各种类型的XML,将其强制转换为NVARCHAR(MAX)并将此中间字符串转换为二进制文件。这个二进制文件是普通的unicode,将被(几乎)任何系统正确解释。

只是为了证明,这个位模式可以安全地重新分类并且可以重新读入类型化结果我也会显示阅读过程:

DECLARE @Source NVARCHAR(MAX)=
CAST(
(
    SELECT 'Some varchar string' AS SimpleString
          ,N'Some nvarchar string with foreign characters: слов в тексте' AS WideString
          ,CAST(100.0/3.0 AS DECIMAL(10,4)) AS Decimal_10_4
          ,CAST(100.0/3.0 AS FLOAT(12)) AS Float_12
          ,CAST(100.0/3.0 AS FLOAT(53)) AS Float_53
          ,GETDATE() AS SimpleDateTime
          ,CAST(GETDATE() AS DATETIME2) AS ExtDateTime
    FOR XML RAW,ELEMENTS
) AS NVARCHAR(MAX));

SELECT @Source;

- 这是中间XML

/*
<row>
  <SimpleString>Some varchar string</SimpleString>
  <WideString>Some nvarchar string with foreign characters: слов в тексте</WideString>
  <Decimal_10_4>33.3333</Decimal_10_4>
  <Float_12>3.3333332e+001</Float_12>
  <Float_53>3.333333300000000e+001</Float_53>
  <SimpleDateTime>2017-03-20T09:28:10.873</SimpleDateTime>
  <ExtDateTime>2017-03-20T09:28:10.8730000</ExtDateTime>
</row>
*/

- 现在我们可以将其转换为VARBINARY以便将其发送到某个地方

DECLARE @binaryToMarshal VARBINARY(MAX)=CAST(@Source AS VARBINARY(MAX));

- 其余的是类型安全!阅读方:

DECLARE @Target NVARCHAR(MAX)=CAST(@binaryToMarshal AS NVARCHAR(MAX));

DECLARE @ReCastes XML=CAST(@Target AS XML);

SELECT @ReCastes.value('(/row/SimpleString)[1]','varchar(max)') AS SimpleString
      ,@ReCastes.value('(/row/WideString)[1]','nvarchar(max)') AS WideString
      ,@ReCastes.value('(/row/Decimal_10_4)[1]','decimal(10,4)') AS Decimal_10_4
      ,@ReCastes.value('(/row/Float_12)[1]','float(12)') AS Float_12
      ,@ReCastes.value('(/row/Float_53)[1]','float(53)') AS Float_53
      ,@ReCastes.value('(/row/SimpleDateTime)[1]','datetime') AS SimpleDateTime
      ,@ReCastes.value('(/row/ExtDateTime)[1]','datetime2') AS ExtDateTime

提示1

您可以使用FOR XML RAW,ELEMENTS,XMLSCHEMA创建内联架构。此模式允许目标系统了解键入读数的所有必需详细信息。

提示2

如果要保存字节,可以使用最小元素名称和JSON

并且 - 如果您不包含特殊字符 - 您可以使用CAST ... AS VARCHAR(MAX)仅发送一半大小。在这种情况下,您不会发送utf-161-byte-codes *(extended ASCII)*,除非您不需要任何代码页,整理,等等,否则它们应该在任何系统上都可读;

答案 4 :(得分:2)

根据MSDN:https://msdn.microsoft.com/en-us//library/ms188362.aspx

  

varbinary [(n | max)]:   可变长度的二进制数据。 n可以是1到8,000之间的值。 max表示最大存储大小为2 ^ 31-1个字节。 存储大小是输入数据的实际长度+ 2个字节。输入的数据长度可以是0个字节。 varbinary的ANSI SQL同义词是二进制变量。

宣布

DECLARE @myval decimal (4, 0);  

您定义了一个4位十进制数,可以存储-9999到+9999。 要将它转换为/从varbinary转换,你应该在varbinary类型中使用/ expect +2额外字节:

DECLARE @myval decimal (4, 0);  
SET @myval = 9999;  
SELECT CONVERT(decimal(4,0), CONVERT(varbinary(6), @myval)) result

请注意precision中的decimal参数确定最大小数位数,而不是二进制中的最大值。

答案 5 :(得分:1)

当你转换为varbinary时,你不仅转储值(在你的情况下最多可能需要5个字节),而且还有关于类型的信息,因此你需要额外的字节来适应你的情况。

考虑一下,如果将源数据类型从十进制(4,0)切换到十进制(5,0),您将得到不同的varbinary结果。

总而言之,我建议不要使用varbinary:)