如何确定性地将 varchar iPv4 地址转换为数字

Dev*_*ard 4 sql-server

PARSENAME()在 SQL Server 中不使用(非确定性)函数将 varchar iPv4 地址转换为数字以创建持久计算列的各种方法有哪些?

我使用了基于第 n 个字符实例提取字符串部分的字符串操作技术来创建以下内容:

询问

select 
((256*256*256)*convert(bigint, ltrim(rtrim(substring(replace('255.255.255.255','.',replicate(' ',8)),1, 9)))))
+
((256*256)*convert(bigint,ltrim(rtrim(substring(replace('255.255.255.255','.',replicate(' ',8)),10, 9)))))
+
((256)*convert(bigint,ltrim(rtrim(substring(replace('255.255.255.255','.',replicate(' ',8)),19, 9)))))
+
(convert(bigint,ltrim(rtrim(substring(replace('255.255.255.255','.',replicate(' ',8)),28, 9)))))
Run Code Online (Sandbox Code Playgroud)

这是在将每个段转换为 BIGINT 并相乘之前字符串操作的视觉效果: IP 字符串操作

...这可以创建一个可以持久化的确定性计算列。还有哪些其他更有效的技术?

Sol*_*zky 7

一种选择是使用 SQLCLR。创建一个方法来分割句点上的字符串,将每个字符串转换为Int64,然后乘以 256 的适当幂是相当容易的。一定要IsDeterministic=trueSqlFunction属性中设置,这不仅允许它被持久化,而且还允许它参与并行计划:-)。事实上,SQL# SQLCLR 库(我创建的)包含执行此转换的函数(在免费版本中):INET_AddressToNumberINET_NumberToAddress

测试设置:

CREATE TABLE #TempAddresses (IPAddress VARCHAR(15) NULL);

-- populate 1 million rows, with a NULL every 1000 rows
;WITH cte AS
(
  SELECT  TOP (1000000) ac1.column_id,
          ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS [RowNum]
  FROM    master.sys.all_columns ac1
  CROSS APPLY  master.sys.all_columns ac2
)
INSERT INTO #TempAddresses ([IPAddress])
SELECT CASE cte.[RowNum] % 1000
         WHEN 0 THEN NULL
         ELSE CONVERT(VARCHAR(3), CONVERT(INT, CRYPT_GEN_RANDOM(1)))
              + '.' +
              CONVERT(VARCHAR(3), CONVERT(INT, CRYPT_GEN_RANDOM(1)))
              + '.' +
              CONVERT(VARCHAR(3), CONVERT(INT, CRYPT_GEN_RANDOM(1)))
              + '.' +
              CONVERT(VARCHAR(3), CONVERT(INT, CRYPT_GEN_RANDOM(1)))
       END
FROM   cte
Run Code Online (Sandbox Code Playgroud)

关于以下三个测试,请注意:

  • 它们按最快到最慢的顺序显示。
  • 我每次都跑SELECT了好几次,每次都保持最好的时间,而不是平均时间。

SQLCLR 测试和结果:

DECLARE @Test1 BIGINT;
SET STATISTICS TIME ON;
SELECT @Test1 =  -- Comment out "@Test1 = " to test returning the value
       SQL#.INET_AddressToNumber(CONVERT(NVARCHAR(15), tmp.[IPAddress]))
FROM   #TempAddresses tmp;
SET STATISTICS TIME OFF;

-- CPU time = 2454 ms,  elapsed time = 5133 ms.
-- CPU time = 1594 ms,  elapsed time = 1617 ms. (into variable)
Run Code Online (Sandbox Code Playgroud)

MartinSmith 的测试和结果:

DECLARE @Test3 BIGINT;
SET STATISTICS TIME ON;
SELECT @Test3 =  -- Comment out "@Test3 = " to test returning the value
         256 * 256 * 256 * CAST(FLOOR(LEFT(tmp.[IPAddress],3)) AS BIGINT)
       +       256 * 256 * CAST(FLOOR(SUBSTRING(tmp.[IPAddress],1 + CHARINDEX('.', tmp.[IPAddress]),3)) AS BIGINT)
       +             256 * SUBSTRING(REPLACE(tmp.[IPAddress],'.',SPACE(8)),19, 9)
       +                   RIGHT(tmp.[IPAddress], -1 + CHARINDEX('.', REVERSE(tmp.[IPAddress])))
FROM   #TempAddresses tmp;
SET STATISTICS TIME OFF;

-- CPU time = 2781 ms,  elapsed time = 5151 ms.
-- CPU time = 2156 ms,  elapsed time = 2156 ms. (into variable)
Run Code Online (Sandbox Code Playgroud)

OP 的测试和结果:

DECLARE @Test2 BIGINT;
SET STATISTICS TIME ON;
SELECT @Test2 =  -- Comment out "@Test2 = " to test returning the value
       ((256*256*256) * CONVERT(BIGINT, LTRIM(RTRIM(SUBSTRING(REPLACE(tmp.[IPAddress],'.',REPLICATE(' ',8)),1, 9)))))
       + ((256*256)*CONVERT(BIGINT,LTRIM(RTRIM(SUBSTRING(REPLACE(tmp.[IPAddress],'.',REPLICATE(' ',8)),10, 9)))))
       + ((256)*CONVERT(BIGINT,LTRIM(RTRIM(SUBSTRING(REPLACE(tmp.[IPAddress],'.',REPLICATE(' ',8)),19, 9)))))
       + (CONVERT(BIGINT,LTRIM(RTRIM(SUBSTRING(REPLACE(tmp.[IPAddress],'.',REPLICATE(' ',8)),28, 9)))))
FROM   #TempAddresses tmp;
SET STATISTICS TIME OFF;

-- CPU time = 3531 ms,  elapsed time = 5249 ms.
-- CPU time = 2515 ms,  elapsed time = 2534 ms. (into variable)
Run Code Online (Sandbox Code Playgroud)

关于 SQLCLR 选项要记住的一件事:在计算列中使用 SQLCLR 函数是一种依赖关系,它将防止包含该函数的程序集被删除。如果您能够使用ALTER ASSEMBLY,那么您就不会丢弃并重新创建程序集。但ALTER ASSEMBLY仅当没有新的或删除的方法,并且现有方法的签名仍然相同时才允许。如果进行了任何这些更改,则您必须删除程序集,然后重新创建它。这将要求您首先删除引用该程序集中包含的 SQLCLR 函数的计算列。在将函数放置在计算列中的用例中,这可能会使 SQLCLR 选项不太受欢迎,而如果函数在查询、存储过程、视图等中,则这不会成为问题。

可以将 SQLCLR 函数包装在 T-SQL 函数中并在计算列中引用 T-SQL 函数,但是这样您就失去了 SQLCLR 函数能够参与并行执行计划的好处。