有没有一种快速的方法可以将 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); N
和 Tally
。 N
字面上只包含 4 行,值为 NULL
。为什么是4?我稍后会解释。为什么 NULL
?好吧,它可以是任意值,我只使用 NULL
因为它的值没有意义。
接下来是 CTE Tally
。首先,您会注意到它在 FROM
中引用了 N
3 次;这意味着 N
被 CROSS JOIN
ed(使用旧的 ANSI-89 语法,是的)自身 3 次,导致(最多)64 行,4^3 = 64
,这是(你会注意到)参数的长度,varchar(64)
。这就是为什么我使用 4 NULL
个值。
在 SELECT
中,我将要 return 编辑的行数限制为 varchar
值 -1
的长度。 -1
因为二进制值是有符号的,所以字符串中的第一个数字不会用于确定聚合值(稍后完成),而是表示该值是正数还是负数。
最后我们得到了 ROW_NUMBER
,它毫不奇怪地给每一行一个升序数字,从 1
开始。 (SELECT NULL)
在 ORDER BY
中,因为我们(再次)需要一些任意值。
如果我们在此处停止,使用您的值,这将导致数据集包含 7 行,值 1
到 7
。你可以用下面的
来测试这个
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 APPLY
到 VALUES
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)
这里有一些。首先,我们采用 int
值 2
并将其具体转换为 decimal(2,0)
。然后我们有 POWER
,它“按照罐子上说的做”;它采用第一个值并将其赋予第二个值。
因此,对于第一行(在上面的数据集中),那将是 POWER(2,1-1)
即 1
。然后我们有POWER(2,2-1)
、POWER(2,3-1)
、POWER(2,4-1)
,分别是2
、4
、8
。如您所知,这些是您的二进制数。
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
变为 0
而 0
变为 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,所以我会继续努力。
我有一个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); N
和 Tally
。 N
字面上只包含 4 行,值为 NULL
。为什么是4?我稍后会解释。为什么 NULL
?好吧,它可以是任意值,我只使用 NULL
因为它的值没有意义。
接下来是 CTE Tally
。首先,您会注意到它在 FROM
中引用了 N
3 次;这意味着 N
被 CROSS JOIN
ed(使用旧的 ANSI-89 语法,是的)自身 3 次,导致(最多)64 行,4^3 = 64
,这是(你会注意到)参数的长度,varchar(64)
。这就是为什么我使用 4 NULL
个值。
在 SELECT
中,我将要 return 编辑的行数限制为 varchar
值 -1
的长度。 -1
因为二进制值是有符号的,所以字符串中的第一个数字不会用于确定聚合值(稍后完成),而是表示该值是正数还是负数。
最后我们得到了 ROW_NUMBER
,它毫不奇怪地给每一行一个升序数字,从 1
开始。 (SELECT NULL)
在 ORDER BY
中,因为我们(再次)需要一些任意值。
如果我们在此处停止,使用您的值,这将导致数据集包含 7 行,值 1
到 7
。你可以用下面的
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 APPLY
到 VALUES
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)
这里有一些。首先,我们采用int
值2
并将其具体转换为decimal(2,0)
。然后我们有POWER
,它“按照罐子上说的做”;它采用第一个值并将其赋予第二个值。 因此,对于第一行(在上面的数据集中),那将是POWER(2,1-1)
即1
。然后我们有POWER(2,2-1)
、POWER(2,3-1)
、POWER(2,4-1)
,分别是2
、4
、8
。如您所知,这些是您的二进制数。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
变为 0
而 0
变为 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,所以我会继续努力。