【问题标题】:Convert Numeric into Rowversion / Timestamp将数字转换为行版本/时间戳
【发布时间】:2011-06-29 22:16:28
【问题描述】:

使用 SQL Server 2008+。

我有一个 rowversion 列(又名时间戳),我从数据库中检索并使用以下代码将其转换为数字(20,0):

CONVERT(NUMERIC(20,0), RowVersionColumn + 0) AS [RowVersion]    

稍后,我需要获取该数值(作为 ulong/UInt64 存储在 ADO.NET 数据集中),并将其转换回 rowversion 数据类型。

不过,这会产生问题。似乎虽然您可以转换为 numeric(20,0),但反向操作无法产生正确的值。例如:

DECLARE @MyNumericValue NUMERIC(20,0)
DECLARE @MyRowVersionValue ROWVERSION

SET @MyNumericValue = 12345
SET @MyRowVersionValue = CONVERT(rowversion, @MyNumericValue)

PRINT CONVERT(NUMERIC(20,0), @MyRowVersionValue + 0)

运行此代码会打印出 959447040 的值,而不是 12345。从 CONVERT 语句中删除“+ 0”会产生正确的结果,但我仍然无法可靠地获取曾经是 rowversion 的数值,并且将其转回一个行版本,其值应为应有的值。

下面是这个问题的一个更好的演示:

DECLARE @MyNumericValue NUMERIC(20,0)
DECLARE @MyRowVersionValue ROWVERSION
DECLARE @MyRowVersionValue2 ROWVERSION

SET @MyRowVersionValue = @@DBTS
SET @MyNumericValue = CONVERT(NUMERIC(20,0), @MyRowVersionValue + 0)
SET @MyRowVersionValue2 = CONVERT(rowversion, @MyNumericValue)

SELECT @MyRowVersionValue 
SELECT @MyNumericValue
SELECT @MyRowVersionValue2

您的结果会因您的输入而异,但作为示例,我的输出是:

0x0000000003ADBB2F
61717295
0x140000012FBBAD03

第一个和最后一个值应该匹配,但它们不匹配。我猜这与二进制数据转换通常会导致填充这一事实有关,而这种填充会改变值。见:

http://msdn.microsoft.com/en-us/library/aa223991(v=SQL.80).aspx

想法?

编辑:澄清一下,我正在处理 rowversion/timestamp 的事实是偶然的。 BINARY(8) 而不是 ROWVERSION 也会出现同样的问题,因为它们是等效的数据类型。

【问题讨论】:

  • 不确定应该归功于谁。没有人真正提供解决方案(显然他们也不能),因为这是一个 SQL Server 错误。参见:support.microsoft.com/kb/141664 最后,我只取二进制值并使用以下 C# 代码将其转换为 ulong:BitConverter.ToUInt64(rowVersionByteArray.Reverse().ToArray(), 0);.

标签: sql sql-server tsql sql-server-2008


【解决方案1】:

虽然我不明白您为什么要将无意义的 ROWVERSION 值转换为数字然后再返回,但您是否尝试使用 BIGINT - 它匹配 UInt64 所需的 8 个字节而不是可变的字节数(取决于实际值)强制 NUMERIC(20,0) 将其更改为?

CREATE TABLE dbo.rv(a INT, rv ROWVERSION);

INSERT dbo.rv(a) SELECT 1 UNION SELECT 2;

WITH x AS
(
    SELECT 
        rv, 
        rv_as_bigint = CONVERT(BIGINT, rv) 
    FROM dbo.rv
)
SELECT 
    rv, 
    rv_as_bigint, 
    rv_back_to_rv = CONVERT(ROWVERSION, rv_as_bigint) 
FROM x;

DROP TABLE dbo.rv;

或者获取您的原始样本并将 BIGINT 换成 NUMERIC(20,0):

DECLARE @MyNumericValue BIGINT
DECLARE @MyRowVersionValue ROWVERSION
DECLARE @MyRowVersionValue2 ROWVERSION

SET @MyRowVersionValue = @@DBTS
SET @MyNumericValue = CONVERT(BIGINT, @MyRowVersionValue) -- removed the +0 hack
SET @MyRowVersionValue2 = CONVERT(rowversion, @MyNumericValue)

SELECT @MyRowVersionValue 
SELECT @MyNumericValue
SELECT @MyRowVersionValue2

【讨论】:

  • BIGINT 不起作用。 BIGINT 是一个有符号值,因为我的 NUMERIC(20,0) 可以包含 ROWVERSION 列的所有可能值作为无符号数据。使用 big int 会在转换大 rowversion 值后产生负值。
  • 另外,为了回答您的问题,我对 C# 程序中的值进行了一些排序操作。 ROWVERSION 列并非毫无意义——它是一个永远递增的值。这对我的应用程序日志很重要,这就是我使用它的原因。你可以用 BINARY(8) 替换我的示例中的所有 ROWVERSION 引用,你也会遇到同样的问题。
  • 我尝试使用 -9000000000000000000 和 9000000000000000000 的值,但它仍然可以正常工作。您能告诉我您将生成超出此值的 ROWVERSION 值吗?你能提供一个repro吗?
  • 另外,我很难相信您的 C# 程序如果不先将二进制值转换为数值,就无法对它们进行排序。 C# 不能再处理字节数组了?然后为什么你需要再次将它们转换回来?您不能转换为数字并保持原始值不变吗?为什么需要双向转换?
  • 按照我的假设,按字节排序是行不通的。 byte[] 没有实现 IComparible (显然),所以排序不会开箱即用。但是,我可以先将值转换为 ulong (BitConverter.ToUInt64(byteArray, 0)),然后对此进行排序。
【解决方案2】:

当您将数据转换为binaryrowversion 本质上是binary(8))时,您会得到它的内部表示而不是逻辑值。您可以使用以下 sn-p 验证行为:

DECLARE @MyNumericValue NUMERIC(20,0) = 0
SELECT @MyNumericValue, CONVERT(binary(8), @MyNumericValue)

或者你可以试试

DECLARE @MyBinary binary(8) = 0x00
SELECT @MyBinary, CONVERT(numeric(20,0), @MyBinary)

它根本不会转换。 因此,您的脚本的实际情况是:

SET @MyNumericValue = 12345
-- @MyRowVersionValue gets the internal representation of the numeric value 12345
SET @MyRowVersionValue = CONVERT(rowversion, @MyNumericValue)
-- +0 converts the binary value to int, then performs int-to-numeric conversion 
PRINT CONVERT(NUMERIC(20,0), @MyRowVersionValue + 0)
-- Real binary-to-numeric conversion.
PRINT CONVERT(NUMERIC(20,0), @MyRowVersionValue)

真正的二进制到数字的转换是安全的,但在您的情况下,您可能希望将 rowversion 转换为 bigint(或者忘记数值并将其存储为二进制字符串)而不是 @ 987654328@,因为CONVERT(NUMERIC(20,0), @MyRowVersionValue) 并不能真正为您提供数值。

【讨论】:

  • 我怀疑 +0 导致了 INT 转换。问题是,没有它的 CONVERT 语句会失败。这似乎是 SQL Server 中的一个错误。
  • @RMD - 这不是错误,这是因为某些二进制值不是数值的有效内部表示。例如,没有数值表示为 0x0000000000000000。是的,你可以转换 binary->int->numeric,但是当你需要反转它时,你也需要 numeric->int->binary。
  • @forcey - 我不确定这对我是否有意义。 0x0000000000000000 字节数组值 = 数值 0。如果问题是某些字节数组值在转换为数字时没有意义,那么在转换为 INT 时它们是否也没有意义?
  • @RMD - SQL 数字类型不以这种方式表示数字,因为它需要处理十进制数字。并且表示在不同版本中可能有所不同。另一个例子是浮点数——浮点值 1.0 不一定表示为 0x0000000000000001。但是,整数类型总是“按原样”表示数字。
  • @forcey - 啊,这很有道理。尽管如此,SQL Server BOL 明确指出二进制和数字之间的转换是可能的,但是当我尝试运行转换语句时它会引发错误 - 这就是迫使 int/bigint hack 开始的原因。这似乎是一个错误。
猜你喜欢
  • 1970-01-01
  • 2019-08-15
  • 2020-11-22
  • 2016-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-09
相关资源
最近更新 更多