这有点偏离了真正的问题。如果提供上下文有帮助,则生成此数据可能有助于测试字符串处理方式的性能,生成需要在游标中对其应用某些操作的字符串,或生成敏感数据的唯一匿名名称替换。我只是对在 SQL Server 中生成数据的有效方法感兴趣,请不要问我为什么需要生成这些数据。
我将尝试从一个有点正式的定义开始。如果一个字符串仅由 A - Z 的大写字母组成,则该字符串包含在该系列中。该系列的第一项是“A”。该系列由所有有效字符串组成,这些字符串首先按长度排序,然后按典型字母顺序排序。如果字符串位于名为 的列中的表中STRING_COL
,则可以在 T-SQL 中将顺序定义为ORDER BY LEN(STRING_COL) ASC, STRING_COL ASC
。
要给出不太正式的定义,请查看 excel 中按字母顺序排列的列标题。该系列是相同的模式。考虑如何将整数转换为 26 进制数:
1 -> A, 2 -> B, 3 -> C, ... , 25 -> Y, 26 -> Z, 27 -> AA, 28 -> AB, ...
这个类比并不十分完美,因为“A”的行为与以十进制表示的 0 不同。下面是一个选定值的表格,希望能使它更清楚:
╔════════════╦════════╗
║ ROW_NUMBER ║ STRING ║
╠════════════╬════════╣
║ 1 ║ A ║
║ 2 ║ B ║
║ 25 ║ Y ║
║ 26 ║ Z ║
║ 27 ║ AA ║
║ 28 ║ AB ║
║ 51 ║ AY ║
║ 52 ║ AZ ║
║ 53 ║ BA ║
║ 54 ║ BB ║
║ 18278 ║ ZZZ ║
║ 18279 ║ AAAA ║
║ 475253 ║ ZZZY ║
║ 475254 ║ ZZZZ ║
║ 475255 ║ AAAAA ║
║ 100000000 ║ HJUNYV ║
╚════════════╩════════╝
目标是编写一个SELECT
查询,按上面定义的顺序返回前 100000000 个字符串。我通过在 SSMS 中运行查询并丢弃结果集而不是将其保存到表中来进行测试:
理想情况下,查询将相当有效。在这里,我将高效定义为串行查询的 cpu 时间和并行查询的运行时间。您可以使用您喜欢的任何未记录的技巧。依赖未定义或非保证的行为也可以,但如果您在回答中指出这一点,我们将不胜感激。
有哪些有效生成上述数据集的方法?Martin Smith指出,由于处理这么多行的开销,CLR 存储过程可能不是一个好的方法。
Your solution runs for 35 seconds on my laptop. The following code takes 26 seconds (including creating and populating the temporary tables):
Temporary tables
The idea there is to pre-populate ordered combinations of up to four characters.
Main code
这是四个预先计算的表的简单保序并集*,根据需要导出 5 个字符和 6 个字符的字符串。将前缀与后缀分开可以避免排序。
执行计划
* 上面的 SQL 中没有任何内容直接指定保序联合。优化器选择具有与 SQL 查询规范匹配的属性的物理运算符,包括顶级排序依据。在这里,它选择由 merge join 物理运算符实现的串联以避免排序。
保证是执行计划按规范传递查询语义和顶级顺序。知道 merge join concat 保留顺序允许查询编写者预测执行计划,但优化器只会在预期有效时交付。
我将发布一个答案以开始。我的第一个想法是,应该可以利用嵌套循环连接的顺序保持特性以及一些每个字母一行的辅助表。棘手的部分将以这样一种方式循环,即结果按长度排序并避免重复。例如,当交叉连接包含所有 26 个大写字母和 '' 的 CTE 时,您最终可能会生成
'A' + '' + 'A'
并且'' + 'A' + 'A'
当然是相同的字符串。第一个决定是将辅助数据存储在何处。我尝试使用临时表,但这对性能产生了令人惊讶的负面影响,即使数据适合单个页面。临时表包含以下数据:
与使用 CTE 相比,使用聚簇表的查询时间要长 3 倍,使用堆的查询时间要长 4 倍。我不认为问题是数据在磁盘上。它应该作为单个页面读入内存,并在内存中为整个计划进行处理。也许 SQL Server 可以比处理存储在典型行存储页面中的数据更有效地处理来自 Constant Scan 运算符的数据。
有趣的是,SQL Server 选择将具有有序数据的单页 tempdb 表的有序结果放入表假脱机中:
SQL Server 经常将交叉连接的内部表的结果放入表假脱机中,即使这样做看起来很荒谬。我认为优化器需要在这方面做一些工作。我使用 运行查询
NO_PERFORMANCE_SPOOL
以避免性能下降。使用 CTE 存储辅助数据的一个问题是不能保证数据是有序的。我想不出为什么优化器会选择不对其进行排序,并且在我所有的测试中,数据都是按照我编写 CTE 的顺序处理的:
但是,最好不要冒险,尤其是如果有一种方法可以在不增加大量性能开销的情况下做到这一点。可以通过添加多余的
TOP
运算符来对派生表中的数据进行排序。例如:添加到查询中应该保证结果将以正确的顺序返回。我预计所有这些都会对性能产生很大的负面影响。查询优化器也根据估计的成本期望这一点:
非常令人惊讶的是,在有或没有显式排序的情况下,我无法观察到 cpu 时间或运行时的任何统计显着差异。如果有的话,查询似乎运行得更快
ORDER BY
!我对这种行为没有任何解释。问题的棘手部分是弄清楚如何将空白字符插入正确的位置。如前所述,简单
CROSS JOIN
会导致重复数据。我们知道第 100000000 个字符串的长度为六个字符,因为:但
因此我们只需要六次连接到字母CTE。假设我们六次加入 CTE,从每个 CTE 中获取一个字母,并将它们连接在一起。假设最左边的字母不是空白。如果任何后续字母为空,则表示该字符串的长度少于六个字符,因此它是重复的。因此,我们可以通过找到第一个非空白字符并要求其后的所有字符也不是空白来防止重复。我选择通过
FLAG
为其中一个 CTE 分配一列并向WHERE
子句添加检查来跟踪这一点。在查看查询后,这应该会更清楚。最终查询如下:CTE 如上所述。
ALL_CHAR
连接到五次,因为它包含一个空白字符行。字符串中的最后一个字符绝不能为空,因此为其定义了一个单独的 CTEFIRST_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
提示在技术上是不必要的,因为CROSS JOIN
在 SQL Server 中只能作为循环连接来实现。这NO_PERFORMANCE_SPOOL
是为了防止不必要的表假脱机。省略假脱机提示会使查询在我的机器上花费 3 倍的时间。最终查询的 CPU 时间约为 17 秒,总耗时为 18 秒。那是在通过 SSMS 运行查询并丢弃结果集时。我对查看其他生成数据的方法非常感兴趣。
我有一个优化的解决方案,可以获取最多 217,180,147,158(8 个字符)的任何特定数字的字符串代码。但我不能打败你的时间:
在我的机器上,使用 SQL Server 2014,你的查询需要 18 秒,而我的需要 3m 46s。这两个查询都使用未记录的跟踪标志 8690,因为 2014 不支持该
NO_PERFORMANCE_SPOOL
提示。这是代码:
这里的技巧是预先计算不同排列的开始位置:
The other trick used is to simply use sum to get to the right value instead of trying to concat. To achieve this I simply offset the digits from base 26 to base 256 and add the ascii value of 'A' for each digit. So we obtain the binary representation of the string we're looking for. After that some string manipulations complete the process.