我需要创建一些涉及层次结构的测试数据。我可以让它变得简单并做几个CROSS JOIN
s,但这会给我一个完全统一/没有任何变化的结构。这不仅看起来乏味,而且测试数据缺乏变化有时会掩盖原本会发现的问题。所以,我想生成一个遵循这些规则的非统一层次结构:
- 3层深
- 级别 1 随机 5 - 20 个节点
- 级别 2 是 1 - 10 个节点,每个级别 1 的每个节点都是随机的
- 级别 3 是 1 - 5 个节点,每个级别 2 的每个节点都是随机的
- 所有分支将有 3 层深。在这一点上,深度均匀性是可以的。
- 在任何给定级别上的子节点的名称都可以有重叠(即,子节点的名称不需要在同一级别的所有节点中都是唯一的)。
- 术语“随机”在这里定义为伪随机,而不是唯一随机。这需要提及,因为术语“随机”通常用于表示“不产生重复的给定集合的随机排序”。我接受随机 = 随机,如果 1 级每个节点的子节点数只有 4、7 和 8 个,即使在 1 级的 20 个节点中,每个节点可能分布 1-10 个子节点,那很好,因为那是随机的。
- 尽管这可以通过嵌套
WHILE
循环很容易地完成,但首选是找到基于集合的方法。一般来说,生成测试数据并没有生产代码对效率的要求,但采用基于集合的方法可能更具教育意义,并有助于在未来找到基于集合的方法来解决问题。因此WHILE
不排除循环,但只能在不可能使用基于集合的方法时使用。 - 基于集合 = 理想情况下是单个查询,无论 CTE、APPLY 等如何。因此使用现有或内联数字表就可以了。使用 WHILE / CURSOR / 程序方法将不起作用。我想将部分数据暂存到临时表或表变量中很好,只要操作都是基于集合的,没有循环。但是,话虽如此,单查询方法可能会比多个查询更受青睐,除非可以证明多查询方法实际上更好。还请记住,什么构成“更好”通常是主观的;-)。还请记住,前一句中“典型地”的使用也是主观的。
- SQL Server 的任何版本(我想是 2005 和更新版本)都可以。
- 只有纯 T-SQL:没有那些愚蠢的 SQLCLR 东西!!至少在生成数据方面。将使用 SQLCLR 创建目录和文件。但在这里,我只专注于创造要创造的价值。
- T-SQL 多语句 TVF 被认为是过程的,而不是基于集合的,即使在外部它们掩盖了集合中的过程方法。有时这是绝对合适的。这不是其中之一。同样,T-SQL 标量函数也是不允许的,不仅因为它们也是过程性的,而且查询优化器有时会缓存它们的值并重复它,从而导致输出不符合预期。
- T-SQL 内联 TVF(又名 iTVF)是 okey-dokey,因为它们是基于集合的,并且实际上与 using 相同
[ CROSS | OUTER ] APPLY
,这在上面说明是可以的。 - 查询的重复执行应产生与先前运行大不相同的结果。
- 澄清更新 1:最终结果集应表示为 Level3 的每个不同节点都有一行,完整路径从 Level1 开始。这意味着 Level1 和 Level2 值必然会在一行或多行中重复,除非只有一个 Level2 节点包含一个 Level3 节点。
- 澄清更新 2:对于具有名称或标签的每个节点,而不仅仅是一个数字,存在非常强烈的偏好。这将使生成的测试数据更有意义和更现实。
我不确定这些附加信息是否重要,但以防万一它有助于了解一些上下文,测试数据与我对这个问题的回答有关:
虽然此时不相关,但生成此层次结构的最终目标是创建一个目录结构来测试递归文件系统方法。级别 1 和 2 将是目录,而级别 3 将最终成为文件名。我已经搜索了(在这里和通过谷歌)并且只找到了一个生成随机层次结构的参考:
这个问题(在 StackOverflow 上)实际上与期望的结果非常接近,因为它也试图创建一个用于测试的目录结构。但这个问题(和答案)集中在 Linux/Unix shell 脚本,而不是我们生活的基于集合的世界。
现在,我知道如何生成随机数据,并且已经在这样做以创建文件的内容,以便它们也可以显示变化。这里棘手的部分是每个集合中的元素数量是随机的,而不是特定的字段。并且,每个节点内的元素数量需要与同一级别上的其他节点随机。
示例层次结构
Level 1
Level 3
|---- A
| |-- 1
| | |--- I
| |
| |-- 2
| |--- III
| |--- VI
| |--- VII
| |--- IX
|
|---- B
| |-- 87
| |--- AAA
| |--- DDD
|
|---- C
|-- ASDF
| |--- 11
| |--- 22
| |--- 33
|
|-- QWERTY
| |--- beft
|
|-- ROYGBP
|--- Poi
|--- Moi
|--- Soy
|--- Joy
|--- Roy
描述上述层次结构的示例结果集
Level 1 Level 2 Level 3
A 1 I
A 2 III
A 2 VI
A 2 VII
A 2 IX
B 87 AAA
B 87 DDD
C ASDF 11
C ASDF 22
C ASDF 33
C QWERTY beft
C ROYGBP Poi
C ROYGBP Moi
C ROYGBP Soy
C ROYGBP Joy
C ROYGBP Roy
(OP的注释:首选解决方案是第4个/最后一个代码块)
在我看来,XML 显然是这里使用的数据结构的明显选择。
使 SQL Server
top()
为每个节点使用不同值的技巧是使子查询相关联。N1.N > 0
和N2.N > 0
。扁平化 XML:
还有一个完全没有 XML 的版本。
相关性
N1.N > 0
仍然N2.N > 0
很重要。使用具有 20 个名称的表的版本,而不仅仅是整数。
That was interesting.
My aim was to generate given number of levels with random number of child rows per each level in a properly linked hierarchical structure. Once this structure is ready it is easy to add extra info into it like file and folder names.
So, I wanted to generate a classic table for storing a tree:
Since we are dealing with recursion, recursive CTE seems a natural choice.
I will need a table of numbers. Numbers in the table should start from 1. There should be at least 20 numbers in the table:
MAX(LvlMax)
.Parameters for data generation should be stored in a table:
Note, that the query is pretty flexible and all parameters are separated into one place. You can add more levels if needed, just add an extra row of parameters.
To make such dynamic generation possible I had to remember the random number of rows for the next level, so I have an extra column
ChildRowCount
.Generating unique
IDs
is also somewhat tricky. I hard-coded the limit of 100 child rows per 1 parent row to guarantee thatIDs
don't repeat. This is what thatPOWER(100, CTE.Lvl)
is about. As a result there are large gaps inIDs
. That number could be aMAX(LvlMax)
, but I put constant 100 in the query for simplicity. The number of levels is not hard-coded, but is determined by@Intervals
.This formula
generates a random floating point number in the range
[0..1)
, which is then scaled to the required interval.The query logic is simple. It is recursive. First step generates a set of rows of the first level. Number of rows is determined by random number in
TOP
. Also, for each row there is a separate random number of child rows stored inChildRowCount
.Recursive part uses
CROSS APPLY
to generate given number of child rows per each parent row. I had to useWHERE Numbers.Number <= CTE.ChildRowCount
instead ofTOP(CTE.ChildRowCount)
, becauseTOP
is not allowed in recursive part of CTE. Didn't know about this limitation of SQL Server before.WHERE CTE.ChildRowCount IS NOT NULL
stops the recursion.SQL Fiddle
Result (there can be up to 20 + 20*10 + 200*5 = 1220 rows if you are lucky)
Generating full path instead of linked hierarchy
If we are interested only in the full path
N
levels deep, we can omitID
andParentID
from the CTE. If we have a list of possible names in the supplementary tableNames
, it is easy to pick them from this table in CTE. TheNames
table should have enough rows for each level: 20 for level 1, 10 for level 2, 5 for level 3; 20+10+5 = 35 in total. It not necessary to have different sets of rows for each level, but it is easy to set it up properly, so I did it.SQL Fiddle Here is the final query. I split the
FullPath
intoFilePath
andFileName
.Result
所以这就是我想出的。为了创建目录结构,我一直在为目录和文件寻找可用的“名称”。因为我无法
TOP(n)
在CROSS APPLY
s 中工作(我想我试图通过使用来自父级的值作为n
in来关联查询,TOP(n)
但后来它不是随机的),我决定创建一种“数字”该表将允许一个INNER JOIN
orWHERE
条件简单地通过随机化一个数字并将其指定为来生成一组n
元素WHERE table.Level = random_number
。诀窍是Level1只有1行,Level2只有2行,Level3只有3行,依此类推。因此, usingWHERE LevelID = 3
将得到 3 行,每行都有一个值,我可以将其用作目录名称。设置
这部分最初是内联指定的,作为 CTE 的一部分。但是为了可读性(这样你不需要滚动大量
INSERT
语句来获得真正查询的几行),我把它分解成一个本地临时表。主要查询
对于 1 级,我只是从中获取
[name]
值,sys.objects
因为那里总是有很多行。但是,如果我需要对名称进行更多控制,我可以扩展#Elements
表格以包含其他级别。适合生成每个文件的路径、名称和内容的查询
为了生成文件和文件内容的完整路径,我将 CTE 的主 SELECT 设置为另一个 CTE,并添加了一个新的主 SELECT,它提供了只需要进入文件的正确输出。
额外学分
While not part of the requirements stated in the question, the goal (which was mentioned) was to create files to test recursive File System functions with. So how do we take this result set of path names, file names, and file contents and do something with it? We just need two SQLCLR functions: one to create the folders and one to create the files.
In order to make this data functional, I modified the main
SELECT
of the CTE shown directly above as follows: