有没有一种快速的方法可以将 varchar(8) 二进制序列转换为 T-SQL 中的整数?

Is there a quick way to convert a varchar(8) binary sequence to an integer in T-SQL?

我有一个DECLARE @binarySequence varchar(8) = '00000000';。我使用 STUFF 手动更改每个索引中的位值。该代码与这个问题无关 though.Once 我更改了所有必要的位,然后 SELECT @binarySequence; returns '01001001'。有没有办法让我改变它,使其 returns 该二进制序列的十进制表示 (73) 而无需遍历每个字符? 欢迎所有建议以及任何 answers/questions 感谢您的宝贵时间!

带符号的幅度表示二进制文件

这是我几年前写的东西。它使用 Tally 将值拆分为各个字符,然后在相关 POWER 处聚合每个值以获得最终结果:

CREATE FUNCTION [dbo].[SignedBinaryToDec] (@Binary varchar(64)) 
RETURNS table
AS RETURN
    WITH N AS(
        SELECT N
        FROM (VALUES(NULL),(NULL),(NULL),(NULL))N(N)),
    Tally AS(
        SELECT TOP (LEN(@Binary)-1) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
        FROM N N1, N N2, N N3)
    SELECT SUM(SS.C * POWER(CONVERT(decimal(2,0),2),T.I-1)) *
           CASE LEFT(@Binary,1) WHEN 0 THEN 1
                                WHEN 1 THEN -1
           END AS Dec
    FROM Tally T
         CROSS APPLY (VALUES(SUBSTRING(REVERSE(@Binary),T.I,1)))SS(C)
    WHERE @Binary NOT LIKE '%[^0-1]%';
GO

SELECT Dec
FROM dbo.SignedBinaryToDec('01001001');

这是如何运作的

以上是一个inline-table值函数。这意味着它 return 是一个数据集,而不是标量值,您在 FROM 中引用它。原因是多线标量函数的性能可能很差。在 SQL Server 2019+ 中,它可以内联用户定义的标量函数,但是,我不确定这样的查询是否可以。因此,当具有基于集合的逻辑的 iTVF 性能良好时,我不想冒险使用它。

让我们把它分成几个步骤:

理货

您将看到的查询的第一部分是 2 个通用 Table 表达式 (CTE); NTallyN 字面上只包含 4 行,值为 NULL。为什么是4?我稍后会解释。为什么 NULL?好吧,它可以是任意值,我只使用 NULL 因为它的值没有意义。

接下来是 CTE Tally。首先,您会注意到它在 FROM 中引用了 N 3 次;这意味着 NCROSS JOINed(使用旧的 ANSI-89 语法,是的)自身 3 次,导致(最多)64 行,4^3 = 64,这是(你会注意到)参数的长度,varchar(64)。这就是为什么我使用 4 NULL 个值。

SELECT 中,我将要 return 编辑的行数限制为 varchar-1 的长度。 -1 因为二进制值是有符号的,所以字符串中的第一个数字不会用于确定聚合值(稍后完成),而是表示该值是正数还是负数。

最后我们得到了 ROW_NUMBER,它毫不奇怪地给每一行一个升序数字,从 1 开始。 (SELECT NULL)ORDER BY 中,因为我们(再次)需要一些任意值。

如果我们在此处停止,使用您的值,这将导致数据集包含 7 行,值 17。你可以用下面的

来测试这个
DECLARE @Binary varchar(64) = '01001001';

    WITH N AS(
        SELECT N
        FROM (VALUES(NULL),(NULL),(NULL),(NULL))N(N)),
    Tally AS(
        SELECT TOP (LEN(@Binary)-1) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
        FROM N N1, N N2, N N3)
    SELECT I
    FROM Tally;

外层SELECT

我们先再去FROM。显然 FROM Tally return 是我们刚刚讨论的 CTE Tally 中的行。接下来我们有 CROSS APPLYVALUES table 结构。 return 将 varchar 的每个单独字符放在单独的行中。请注意,我们 REVERSE 的值是最低分值的值在数字的右侧。如果您要 return 仅从 FROM 中获取您的值的结果,您最终会得到以下数据集:

I C
1 1
2 0
3 0
4 1
5 0
6 0
7 1

Note
REVERSE was not in my original solution, not sure how I missed that, but as the value was a palindrome we "lucked" out.

WHERE 用于阻止查询尝试处理任何无效值。例如,如果您要输入 '01001a01''013240101',函数将不会 return 结果。

现在 SELECT。我们将对此进行分解。

  • POWER(CONVERT(decimal(2,0),2),T.I-1)
    这里有一些。首先,我们采用 int2 并将其具体转换为 decimal(2,0)。然后我们有 POWER ,它“按照罐子上说的做”;它采用第一个值并将其赋予第二个值。 因此,对于第一行(在上面的数据集中),那将是 POWER(2,1-1)1。然后我们有POWER(2,2-1)POWER(2,3-1)POWER(2,4-1),分别是248。如您所知,这些是您的二进制数。
  • SUM(SS.C * POWER(CONVERT(decimal(2,0),2),T.I-1))
    在这里,我们采用之前的表达式并将其乘以 varchar 中的数字,然后聚合这些值。对于您的示例,这意味着您有一个表达式可以解析为如下内容: SUM((1*1) + (0*2) + (0*4) + (1*8) + (0*16) + (0*32) + (1*64)) = SUM(1 + 0 + 0 + 8 + 0 + 0 + 64) = SUM(1 + 8 + 64) = 73
  • CASE表达式
    这实际上只是检查 varchar 最左边的字符。如果它是 0,它会将上一步中的值乘以 1(不变值),如果它是 1,则乘以 -1,使该值变为负数。这是 Signed magnitude representation.
    的实现 如果您使用 One's Compliment or Two's Complement this will not give the expected value for negative values. (You are, however, clearly not using Negative Base).

其他方法

无符号二进制

CREATE OR ALTER FUNCTION [dbo].[UnsignedBinaryToDec] (@Binary varchar(64)) 
RETURNS table
AS RETURN
    WITH N AS(
        SELECT N
        FROM (VALUES(NULL),(NULL),(NULL),(NULL))N(N)),
    Tally AS(
        SELECT TOP (LEN(@Binary)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
        FROM N N1, N N2, N N3)
    SELECT SUM(SS.C * POWER(CONVERT(decimal(2,0),2),T.I-1)) AS Dec
    FROM Tally T
         CROSS APPLY (VALUES(SUBSTRING(REVERSE(@Binary),T.I,1)))SS(C)
    WHERE @Binary NOT LIKE '%[^0-1]%';
GO

这使用与以前相同的逻辑,因此不需要更多细节。

补语

CREATE OR ALTER FUNCTION [dbo].[OnesComplementBinaryToDec] (@Binary varchar(64)) 
RETURNS table
AS RETURN
    WITH N AS(
        SELECT N
        FROM (VALUES(NULL),(NULL),(NULL),(NULL))N(N)),
    Tally AS(
        SELECT TOP (LEN(@Binary)-1) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
        FROM N N1, N N2, N N3)
    SELECT 
           SUM(BW.B * POWER(CONVERT(decimal(2,0),2),T.I-1)) *
           CASE LEFT(@Binary,1) WHEN 0 THEN 1
                                WHEN 1 THEN -1
           END AS Dec           
    FROM Tally T
         CROSS APPLY (VALUES(TRY_CONVERT(bit,SUBSTRING(REVERSE(@Binary),T.I,1))))SS(C)
         CROSS APPLY (VALUES(CASE LEFT(@Binary,1) WHEN 0 THEN SS.C ELSE ~SS.C END))BW(B)
    WHERE @Binary NOT LIKE '%[^0-1]%';
GO

这里大同小异。但是,您会注意到额外的 CROSS APPLY~ 是一个 Bitwise NOT。这意味着 1 变为 00 变为 1,因此当第一个数字为 1(表示值为负数)时,则按位未应用。

补码

CREATE OR ALTER FUNCTION [dbo].[TwosComplementBinaryToDec] (@Binary varchar(64)) 
RETURNS table
AS RETURN
    WITH N AS(
        SELECT N
        FROM (VALUES(NULL),(NULL),(NULL),(NULL))N(N)),
    Tally AS(
        SELECT TOP (LEN(@Binary)-1) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
        FROM N N1, N N2, N N3)
    SELECT 
           SUM(BW.B * POWER(CONVERT(decimal(2,0),2),T.I-1)) *
           CASE LEFT(@Binary,1) WHEN 0 THEN 1
                                WHEN 1 THEN -1
           END - LEFT(@Binary,1) AS Dec
    FROM Tally T
         CROSS APPLY (VALUES(TRY_CONVERT(bit,SUBSTRING(REVERSE(@Binary),T.I,1))))SS(C)
         CROSS APPLY (VALUES(CASE LEFT(@Binary,1) WHEN 0 THEN SS.C ELSE ~SS.C END))BW(B)
    WHERE @Binary NOT LIKE '%[^0-1]%';

除了减去最左边的数字外,与 One's 相同。

示例结果:

SELECT V.Binary AS B,
       U.[Dec] AS U,
       S.[Dec] AS S,
       [1s].[Dec] AS [1s],
       [2s].[Dec] AS [2s]
FROM (VALUES('0000'),('0001'),('0010'),('0011'),
            ('0100'),('0101'),('0110'),('0111'),
            ('1000'),('1001'),('1010'),('1011'),
            ('1100'),('1101'),('1110'),('1111'))V(Binary)
     CROSS APPLY dbo.UnsignedBinaryToDec(Binary) U
     CROSS APPLY dbo.SignedBinaryToDec(Binary) S
     CROSS APPLY dbo.OnesComplementBinaryToDec(Binary) [1s]
     CROSS APPLY dbo.TwosComplementBinaryToDec(Binary) [2s]
ORDER BY V.Binary;
Binary Unsigned Signed One's Two's
0000 0 0 0 0
0001 1 1 1 1
0010 2 2 2 2
0011 3 3 3 3
0100 4 4 4 4
0101 5 5 5 5
0110 6 6 6 6
0111 7 7 7 7
1000 8 0* -7 -8
1001 9 -1 -6 -7
1010 10 -2 -5 -6
1011 11 -3 -4 -5
1100 12 -4 -3 -4
1101 13 -5 -2 -3
1110 14 -6 -1 -2
1111 15 -7 0* -1

*代表-0

现在我已经设法创建了一个将 varchar(8) 位序列转换为整数的函数。

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE FUNCTION  BinarySequenceToInt
(
    -- Add the parameters for the function here
    @BinarySequence varchar(64)
)
RETURNS int
AS
BEGIN
    -- Declare the return variable here
    
    DECLARE @bitIndex int = 1;
    DECLARE @SequenceLength int = LEN(@BinarySequence)
    DECLARE @powerInversionNumber int = @SequenceLength;
    DECLARE @BitSequenceNumber int = 0;
    
    WHILE @bitIndex <= @SequenceLength
        BEGIN
            IF(SUBSTRING(@BinarySequence , @bitIndex,1) = '1')
                BEGIN
                    SET @BitSequenceNumber = @BitSequenceNumber + POWER(2,@powerInversionNumber-@bitIndex);
                END
            SET @bitIndex += 1;
        END
    return @BitSequenceNumber

END
GO


returns 73 如果我传递它 '01001000'。 我知道我可以采用传递的 varchar 的长度而不是硬编码 8,所以我会继续努力。