我将发布答案以开始使用。我的第一个想法是,应该可以利用嵌套循环连接的顺序保留特性以及一些每个字母都有一行的辅助表。棘手的部分将以这样一种方式循环:结果按长度排序,并避免重复。例如,当交叉连接包含全部26个大写字母和''的CTE时,您最终可以生成'A' + '' + 'A'
,'' + 'A' + 'A'
并且当然是相同的字符串。
第一个决定是在哪里存储帮助程序数据。我尝试使用临时表,但是即使数据适合单个页面,这也会对性能产生令人惊讶的负面影响。临时表包含以下数据:
SELECT 'A'
UNION ALL SELECT 'B'
...
UNION ALL SELECT 'Y'
UNION ALL SELECT 'Z'
与使用CTE相比,使用群集表的查询花费了3倍的时间,而使用堆则花费了4倍的时间。我不认为问题在于数据在磁盘上。应该将其作为一个页面读入内存,并在整个计划中在内存中进行处理。也许SQL Server可以比使用常规行存储页面中存储的数据更有效地处理来自Constant Scan运算符的数据。
有趣的是,SQL Server选择将来自单页tempdb表的有序结果和有序数据放入表假脱机中:
SQL Server通常将交叉联接的内部表的结果放入表假脱机中,即使这样做似乎很不明智。我认为优化器在这方面需要一些工作。我使用来运行查询NO_PERFORMANCE_SPOOL
以避免性能下降。
使用CTE存储帮助程序数据的一个问题是不能保证对数据进行排序。我想不出为什么优化器会选择不对它进行排序,而在所有测试中,数据都是按照编写CTE的顺序进行处理的:
但是,最好不要冒险,尤其是如果有一种方法可以在不增加性能开销的情况下。通过添加多余的TOP
运算符可以对派生表中的数据进行排序。例如:
(SELECT TOP (26) CHR FROM FIRST_CHAR ORDER BY CHR)
该查询的补充应保证结果将以正确的顺序返回。我希望所有这些都会对性能产生很大的负面影响。查询优化器也基于估计的成本预期了这一点:
非常令人惊讶的是,无论是否进行显式排序,我都无法观察到CPU时间或运行时的任何统计学显着差异。如果有的话,使用ORDER BY
!似乎查询运行得更快。我对此行为没有任何解释。
问题的棘手部分是弄清楚如何在正确的位置插入空白字符。如前所述,简单CROSS JOIN
将导致重复数据。我们知道第100000000个字符串的长度为六个字符,因为:
26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 = 914654 <100000000
但
26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 + 26 ^ 6 = 321272406> 100000000
因此,我们只需要加入CTE六次。假设我们六次加入CTE,从每个CTE那里抓一封信,然后将它们全部串联在一起。假设最左边的字母不为空。如果随后的任何字母为空,则表示该字符串的长度少于六个字符,因此它是重复的字符串。因此,我们可以通过查找第一个非空白字符并要求之后的所有字符也不为空白来防止重复。我选择通过FLAG
为一个CTE 分配一列并在该WHERE
子句中添加一个检查来跟踪此情况。查看查询后,这应该会更清楚。最终查询如下:
WITH FIRST_CHAR (CHR) AS
(
SELECT 'A'
UNION ALL SELECT 'B'
UNION ALL SELECT 'C'
UNION ALL SELECT 'D'
UNION ALL SELECT 'E'
UNION ALL SELECT 'F'
UNION ALL SELECT 'G'
UNION ALL SELECT 'H'
UNION ALL SELECT 'I'
UNION ALL SELECT 'J'
UNION ALL SELECT 'K'
UNION ALL SELECT 'L'
UNION ALL SELECT 'M'
UNION ALL SELECT 'N'
UNION ALL SELECT 'O'
UNION ALL SELECT 'P'
UNION ALL SELECT 'Q'
UNION ALL SELECT 'R'
UNION ALL SELECT 'S'
UNION ALL SELECT 'T'
UNION ALL SELECT 'U'
UNION ALL SELECT 'V'
UNION ALL SELECT 'W'
UNION ALL SELECT 'X'
UNION ALL SELECT 'Y'
UNION ALL SELECT 'Z'
)
, ALL_CHAR (CHR, FLAG) AS
(
SELECT '', 0 CHR
UNION ALL SELECT 'A', 1
UNION ALL SELECT 'B', 1
UNION ALL SELECT 'C', 1
UNION ALL SELECT 'D', 1
UNION ALL SELECT 'E', 1
UNION ALL SELECT 'F', 1
UNION ALL SELECT 'G', 1
UNION ALL SELECT 'H', 1
UNION ALL SELECT 'I', 1
UNION ALL SELECT 'J', 1
UNION ALL SELECT 'K', 1
UNION ALL SELECT 'L', 1
UNION ALL SELECT 'M', 1
UNION ALL SELECT 'N', 1
UNION ALL SELECT 'O', 1
UNION ALL SELECT 'P', 1
UNION ALL SELECT 'Q', 1
UNION ALL SELECT 'R', 1
UNION ALL SELECT 'S', 1
UNION ALL SELECT 'T', 1
UNION ALL SELECT 'U', 1
UNION ALL SELECT 'V', 1
UNION ALL SELECT 'W', 1
UNION ALL SELECT 'X', 1
UNION ALL SELECT 'Y', 1
UNION ALL SELECT 'Z', 1
)
SELECT TOP (100000000)
d6.CHR + d5.CHR + d4.CHR + d3.CHR + d2.CHR + d1.CHR
FROM (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d6
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d5
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d4
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d3
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d2
CROSS JOIN (SELECT TOP (26) CHR FROM FIRST_CHAR ORDER BY CHR) d1
WHERE (d2.FLAG + d3.FLAG + d4.FLAG + d5.FLAG + d6.FLAG) =
CASE
WHEN d6.FLAG = 1 THEN 5
WHEN d5.FLAG = 1 THEN 4
WHEN d4.FLAG = 1 THEN 3
WHEN d3.FLAG = 1 THEN 2
WHEN d2.FLAG = 1 THEN 1
ELSE 0 END
OPTION (MAXDOP 1, FORCE ORDER, LOOP JOIN, NO_PERFORMANCE_SPOOL);
CTE如上所述。ALL_CHAR
之所以加入五次,是因为其中包括一行空白字符。字符串中的最后一个字符绝不能为空,因此为其定义了单独的CTE FIRST_CHAR
。如上所述,额外的标志列ALL_CHAR
用于防止重复。可能有一种更有效的方法来执行此检查,但肯定有效率更低的方法。一个试图通过我LEN()
和POWER()
作出的查询运行速度比目前的版本慢六倍。
在MAXDOP 1
和FORCE ORDER
提示是必要的,以确保订单查询保存。带注释的估计计划可能会有助于了解为什么联接按当前顺序排列:
查询计划通常从右到左读取,但是行请求从左到右发生。理想情况下,SQL Server将向d1
常量扫描运算符精确地请求1亿行。当您从左向右移动时,我希望每个运算符将请求更少的行。我们可以在实际执行计划中看到这一点。此外,下面是SQL Sentry Plan Explorer的屏幕截图:
我们从d1中获得了1亿行,这是一件好事。请注意,d2和d3之间的行比几乎完全是27:1(165336 * 27 = 4464072),这在考虑交叉连接的工作原理时很有意义。d1和d2之间的行比为22.4,这表示有些浪费的工作。我相信多余的行来自重复项(由于字符串中间有空白字符),因此它们不会超过执行过滤的嵌套循环联接运算符。
该LOOP JOIN
提示在技术上是不必要的,因为a CROSS JOIN
只能在SQL Server中实现为循环联接。这NO_PERFORMANCE_SPOOL
是为了防止不必要的表假脱机。省略后台打印提示使查询在我的计算机上花费的时间延长了3倍。
最终查询的cpu时间约为17秒,总经过时间为18秒。那是通过SSMS运行查询并丢弃结果集时的情况。我对查看其他生成数据的方法非常感兴趣。