SQL,辅助数字表
对于某些types的sql查询,辅助数字表格可能非常有用。 它可以创build为一个表格,其中包含特定任务所需的行数,或者作为用户定义的函数返回每个查询所需的行数。
什么是创build这样一个function的最佳方式?
呃…对不起,我对一个老post的回应太晚了。 而且,是的,我不得不回答,因为在这个主题上最受欢迎的回答(当时,recursionCTE回答与14种不同方法的联系)是最好的。
首先,有14种不同的解决scheme的文章是好的,看到不同的方法创build一个数字/理货表的dynamic,但正如在文章和引用的线程中指出,有一个非常重要的引文…
“关于效率和性能的build议往往是主观的,不pipe如何使用查询,物理实现决定了查询的效率,因此,而不是依赖于有偏见的指导方针,你必须testing查询并确定哪一个performance更好“。
具有讽刺意味的是,文章本身包含许多主观的陈述和“有偏见的指导方针”,如“recursionCTE可以非常有效地生成一个数字列表”和“这是一个从Itzik Ben-gen发布的新闻组中使用WHILE循环的有效方法 ”我相信他只是为了比较而发布的)。 来吧人们…只是提到Itzik的好名字可能会导致一些可怜的懒惰实际使用这种可怕的方法。 作者应该练习他所传授的内容,并且在做出如此荒谬的错误陈述之前,特别是面对任何可扩展性时,应该做一些性能testing。
考虑到在对任何代码做什么或某人“喜欢”做出任何主观声明之前实际做了一些testing,下面是一些代码,您可以使用自己的testing。 你正在运行testing的SPID安装分析器,并自己检查出来…只是做一个“Search'n'Replace”的数字1000000你最喜欢的号码,看到…
--===== Test for 1000000 rows ================================== GO --===== Traditional RECURSIVE CTE method WITH Tally (N) AS ( SELECT 1 UNION ALL SELECT 1 + N FROM Tally WHERE N < 1000000 ) SELECT N INTO #Tally1 FROM Tally OPTION (MAXRECURSION 0); GO --===== Traditional WHILE LOOP method CREATE TABLE #Tally2 (N INT); SET NOCOUNT ON; DECLARE @Index INT; SET @Index = 1; WHILE @Index <= 1000000 BEGIN INSERT #Tally2 (N) VALUES (@Index); SET @Index = @Index + 1; END; GO --===== Traditional CROSS JOIN table method SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS N INTO #Tally3 FROM Master.sys.All_Columns ac1 CROSS JOIN Master.sys.ALL_Columns ac2; GO --===== Itzik's CROSS JOINED CTE method WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1), E02(N) AS (SELECT 1 FROM E00 a, E00 b), E04(N) AS (SELECT 1 FROM E02 a, E02 b), E08(N) AS (SELECT 1 FROM E04 a, E04 b), E16(N) AS (SELECT 1 FROM E08 a, E08 b), E32(N) AS (SELECT 1 FROM E16 a, E16 b), cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32) SELECT N INTO #Tally4 FROM cteTally WHERE N <= 1000000; GO --===== Housekeeping DROP TABLE #Tally1, #Tally2, #Tally3, #Tally4; GO
虽然我们在这里,这里是我从SQL事件探查器获得的数值为100,1000,10000,100000和1000000 …
SPID TextData Dur(ms) CPU Reads Writes ---- ---------------------------------------- ------- ----- ------- ------ 51 --===== Test for 100 rows ============== 8 0 0 0 51 --===== Traditional RECURSIVE CTE method 16 0 868 0 51 --===== Traditional WHILE LOOP method CR 73 16 175 2 51 --===== Traditional CROSS JOIN table met 11 0 80 0 51 --===== Itzik's CROSS JOINED CTE method 6 0 63 0 51 --===== Housekeeping DROP TABLE #Tally 35 31 401 0 51 --===== Test for 1000 rows ============= 0 0 0 0 51 --===== Traditional RECURSIVE CTE method 47 47 8074 0 51 --===== Traditional WHILE LOOP method CR 80 78 1085 0 51 --===== Traditional CROSS JOIN table met 5 0 98 0 51 --===== Itzik's CROSS JOINED CTE method 2 0 83 0 51 --===== Housekeeping DROP TABLE #Tally 6 15 426 0 51 --===== Test for 10000 rows ============ 0 0 0 0 51 --===== Traditional RECURSIVE CTE method 434 344 80230 10 51 --===== Traditional WHILE LOOP method CR 671 563 10240 9 51 --===== Traditional CROSS JOIN table met 25 31 302 15 51 --===== Itzik's CROSS JOINED CTE method 24 0 192 15 51 --===== Housekeeping DROP TABLE #Tally 7 15 531 0 51 --===== Test for 100000 rows =========== 0 0 0 0 51 --===== Traditional RECURSIVE CTE method 4143 3813 800260 154 51 --===== Traditional WHILE LOOP method CR 5820 5547 101380 161 51 --===== Traditional CROSS JOIN table met 160 140 479 211 51 --===== Itzik's CROSS JOINED CTE method 153 141 276 204 51 --===== Housekeeping DROP TABLE #Tally 10 15 761 0 51 --===== Test for 1000000 rows ========== 0 0 0 0 51 --===== Traditional RECURSIVE CTE method 41349 37437 8001048 1601 51 --===== Traditional WHILE LOOP method CR 59138 56141 1012785 1682 51 --===== Traditional CROSS JOIN table met 1224 1219 2429 2101 51 --===== Itzik's CROSS JOINED CTE method 1448 1328 1217 2095 51 --===== Housekeeping DROP TABLE #Tally 8 0 415 0
正如你所看到的, recursionCTE方法是仅次于持续时间和CPU的While循环的第二差,并且具有逻辑读取forms的8倍于While循环的存储器压力 。 这是RBAR的类固醇,应该避免,不惜一切代价,任何单行计算就像一个While循环应该避免。 有些地方recursion是相当有价值的,但这不是其中之一 。
作为一个侧杆,丹尼先生绝对是… …正确大小的永久数字或理货表是大多数事情去的方式。 大小正确的意思是什么? 那么,大多数人使用Tally表来生成date或在VARCHAR(8000)上进行分割。 如果你创build了一个11,000行的Tally表,并在“N”上有正确的聚集索引,那么你将有足够的行来创build超过30年的date(我与抵押贷款合作,所以30年对我来说是一个关键数字),当然也足以处理VARCHAR(8000)分割。 为什么“正确的尺寸”如此重要? 如果Tally表被使用了很多,它很容易适应caching,这使得它非常快速,没有太多的内存压力。
最后但并非最不重要的是,每个人都知道,如果你创build了一个永久性的Tally表,那么用什么方法来构build它就没有太大的关系,因为1)只有一次和两次)如果它是11000行表,所有的方法都将运行“足够好”。 那么为什么我所有的关于使用哪种方法的遐想?
答案是,一些不太了解和需要完成工作的可怜家伙/加仑可能会看到类似于recursionCTE方法的东西,并决定使用它来做比build筑更大,更常用的东西一个永久的Tally表,我试图保护这些人,他们的代码运行的服务器,以及拥有这些服务器上的数据的公司 。 是的…这是一个很大的交易。 它应该是为了其他人。 教好正确的做法,而不是“够好”。 在发布或使用post或书籍中的某些内容之前进行一些testing……如果您认为recursionCTE是一种类似的方式,那么您保存的生活实际上可能是您自己的。 😉
感谢收听…
最优化的function是使用表格而不是function。 使用函数会导致额外的CPU负载为正在返回的数据创build值,特别是在返回值覆盖范围非常大的情况下。
本文给出了14种不同的可能的解决scheme,并分别讨 重要的一点是:
关于效率和performance的build议往往是主观的。 无论如何使用查询,物理实现都会决定查询的效率。 因此,不要依赖有偏见的准则,而是要testing查询并确定哪一个更好。
我个人喜欢:
WITH Nbrs ( n ) AS ( SELECT 1 UNION ALL SELECT 1 + n FROM Nbrs WHERE n < 500 ) SELECT n FROM Nbrs OPTION ( MAXRECURSION 500 )
这个视图非常快,包含所有正整数值。
CREATE VIEW dbo.Numbers WITH SCHEMABINDING AS WITH Int1(z) AS (SELECT 0 UNION ALL SELECT 0) , Int2(z) AS (SELECT 0 FROM Int1 a CROSS JOIN Int1 b) , Int4(z) AS (SELECT 0 FROM Int2 a CROSS JOIN Int2 b) , Int8(z) AS (SELECT 0 FROM Int4 a CROSS JOIN Int4 b) , Int16(z) AS (SELECT 0 FROM Int8 a CROSS JOIN Int8 b) , Int32(z) AS (SELECT TOP 2147483647 0 FROM Int16 a CROSS JOIN Int16 b) SELECT ROW_NUMBER() OVER (ORDER BY z) AS n FROM Int32 GO
使用SQL Server 2016+
来生成数字表,你可以使用OPENJSON
:
-- range from 0 to @max - 1 DECLARE @max INT = 40000; SELECT rn = CAST([key] AS INT) FROM OPENJSON(CONCAT('[1', REPLICATE(CAST(',1' AS VARCHAR(MAX)),@max-1),']'));
LiveDemo
我们如何使用OPENJSON生成一系列数字?
编辑:请参阅下面的Conrad的评论。
杰夫·摩德恩的答案是伟大的…但我发现在Postgres的Itzik方法失败,除非你删除E32行。
postgres稍微快一点(40ms vs 100ms)是我在这里find的适用于postgres的另一种方法:
WITH E00 (N) AS ( SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 ), E01 (N) AS (SELECT aN FROM E00 a CROSS JOIN E00 b), E02 (N) AS (SELECT aN FROM E01 a CROSS JOIN E01 b ), E03 (N) AS (SELECT aN FROM E02 a CROSS JOIN E02 b LIMIT 11000 -- end record 11,000 good for 30 yrs dates ), -- max is 100,000,000, starts slowing eg 1 million 1.5 secs, 2 mil 2.5 secs, 3 mill 4 secs Tally (N) as (SELECT row_number() OVER (ORDER BY aN) FROM E03 a) SELECT N FROM Tally
当我从SQL Server移动到Postgres的世界,可能错过了一个更好的方式来在该平台上进行统计… INTEGER()? 序列()?
还有更晚的时候,我想贡献一个稍微不同的“传统”CTE(不要触及基表获取行数):
--===== Hans CROSS JOINED CTE method WITH Numbers_CTE (Digit) AS (SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) SELECT HundredThousand.Digit * 100000 + TenThousand.Digit * 10000 + Thousand.Digit * 1000 + Hundred.Digit * 100 + Ten.Digit * 10 + One.Digit AS Number INTO #Tally5 FROM Numbers_CTE AS One CROSS JOIN Numbers_CTE AS Ten CROSS JOIN Numbers_CTE AS Hundred CROSS JOIN Numbers_CTE AS Thousand CROSS JOIN Numbers_CTE AS TenThousand CROSS JOIN Numbers_CTE AS HundredThousand
这个CTE执行更多的读数,然后是Itzik的CTE,但是比传统的CTEless。 但是,它一直执行更less的WRITES,然后其他查询。 正如你所知道的,Writes的读取总是非常昂贵。
持续时间在很大程度上取决于内核数量(MAXDOP),但是在我的8核心上,执行速度始终更快(以ms为单位的持续时间更短),然后执行其他查询。
我在用:
Microsoft SQL Server 2012 - 11.0.5058.0 (X64) May 14 2014 18:34:29 Copyright (c) Microsoft Corporation Enterprise Edition (64-bit) on Windows NT 6.3 <X64> (Build 9600: )
在Windows Server 2012 R2,32 GB,Xeon X3450 @ 2.67Ghz,已启用4个核心HT。