Preciso criar alguns dados de teste que envolvam uma hierarquia. Eu poderia facilitar e fazer alguns CROSS JOIN
s, mas isso me daria uma estrutura completamente uniforme / sem nenhuma variação. Isso não apenas parece monótono, mas a falta de variação nos dados de teste às vezes mascara problemas que, de outra forma, seriam encontrados. Então, estou querendo gerar uma hierarquia não uniforme que siga estas regras:
- 3 níveis de profundidade
- O nível 1 é aleatório de 5 a 20 nós
- O nível 2 é de 1 a 10 nós, aleatório por cada nó do nível 1
- O nível 3 é de 1 a 5 nós, aleatório por cada nó do nível 2
- Todas as ramificações terão 3 níveis de profundidade. A uniformidade em profundidade está ok neste ponto.
- Pode haver sobreposição de nomes de nós filhos em qualquer nível (ou seja, nomes de nós filhos não precisam ser exclusivos em todos os nós do mesmo nível).
- O termo "aleatório" é definido aqui como sendo pseudo-aleatório, não exclusivamente aleatório. Isso precisa ser mencionado, pois o termo "aleatório" é frequentemente usado para significar "ordenação aleatória de um determinado conjunto que não produz duplicatas". Eu aceito que aleatório = aleatório e se o número de filhos por cada nó do Nível 1 for apenas 4, 7 e 8, mesmo em 20 nós no Nível 1 que tenha uma dispersão potencial de 1 a 10 filhos por cada um desses nós, então tudo bem, porque é isso que é aleatório.
- Mesmo que isso possa ser feito facilmente com
WHILE
loops aninhados, a preferência é encontrar uma abordagem baseada em conjunto. De um modo geral, a geração de dados de teste não tem os requisitos de eficiência que o código de produção teria, mas buscar uma abordagem baseada em conjunto provavelmente será mais educacional e ajudará no futuro a encontrar abordagens baseadas em conjunto para problemas. PortantoWHILE
, os loops não são descartados, mas só podem ser usados se nenhuma abordagem baseada em conjunto for possível. - Baseada em conjunto = idealmente uma única consulta, independentemente de CTEs, APPLYs, etc. Portanto, usar uma tabela de números inline ou existente é bom. Usar uma abordagem WHILE / CURSOR / procedural não funcionará. Suponho que preparar partes dos dados em tabelas temporárias ou variáveis de tabela seja bom, desde que as operações sejam todas baseadas em conjunto, sem loops. No entanto, dito isso, uma abordagem de consulta única provavelmente será preferida em relação a várias consultas, a menos que possa ser demonstrado que a abordagem de várias consultas é realmente melhor. Lembre-se também de que o que constitui "melhor" geralmente é subjetivo ;-). Lembre-se também de que o uso de "tipicamente" na frase anterior também é subjetivo.
- Qualquer versão e edição do SQL Server (2005 e mais recente, suponho) serve.
- Somente T-SQL puro: nada daquelas bobagens de SQLCLR!! Pelo menos em termos de geração de dados. A criação dos diretórios e arquivos será feita usando SQLCLR. Mas aqui estou apenas focando em gerar os valores do que criar.
- T-SQL Multi-statement TVF são considerados procedurais, não baseados em conjunto, embora por fora eles mascarem a abordagem procedimental em um conjunto. Há momentos em que isso é absolutamente apropriado. Este não é um daqueles momentos. Nessa mesma linha, as funções T-SQL Scalar também não são permitidas, não apenas porque também são processuais, mas o Query Optimizer às vezes armazena em cache seu valor e o repete de forma que a saída não seja a esperada.
- T-SQL Inline TVFs (também conhecidos como iTVFs) são okey-dokey, pois são baseados em conjunto e efetivamente o mesmo que usar
[ CROSS | OUTER ] APPLY
, que foi declarado acima como ok. - Execuções repetidas da(s) consulta(s) devem produzir resultados principalmente diferentes da execução anterior.
- Atualização de esclarecimento 1: o conjunto de resultados final deve ser expresso como tendo uma linha para cada nó distinto do Level3, tendo o caminho completo começando no Level1. Isso significa que os valores Level1 e Level2 serão necessariamente repetidos em uma ou mais linhas, exceto nos casos de haver apenas um único nó Level2 contendo apenas um único nó Level3.
- Atualização de esclarecimento 2: há uma preferência muito forte para que cada nó tenha um nome ou rótulo, e não apenas um número. Isso permitirá que os dados de teste resultantes sejam mais significativos e realistas.
Não tenho certeza se essas informações adicionais são importantes, mas apenas no caso de ajudar a ter algum contexto, os dados do teste estão relacionados à minha resposta a esta pergunta:
Importar arquivos XML para o SQL Server 2012
Embora não seja relevante neste ponto, o objetivo final de gerar essa hierarquia é criar uma estrutura de diretório para testar métodos recursivos do sistema de arquivos. Os níveis 1 e 2 serão diretórios e o nível 3 acabará sendo o nome do arquivo. Eu pesquisei (tanto aqui quanto nos Googles) e encontrei apenas uma referência para gerar uma hierarquia aleatória:
Linux: criar diretório aleatório/hierarquia de arquivos
Essa pergunta (no StackOverflow) é realmente muito próxima em termos de resultado desejado, pois também visa criar uma estrutura de diretório para teste. Mas essa pergunta (e as respostas) estão focadas em scripts de shell Linux/Unix e não tanto no mundo baseado em conjuntos em que vivemos.
Agora, sei gerar dados aleatórios, e já estou fazendo isso para criar o conteúdo dos arquivos para que eles também apresentem variações. A parte complicada aqui é que o número de elementos dentro de cada conjunto é aleatório, não um campo específico. E , o número de elementos dentro de cada nó precisa ser aleatório de outros nós nos mesmos níveis.
Exemplo de Hierarquia
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
Conjunto de resultados de exemplo descrevendo a hierarquia acima
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
( Nota do OP: a solução preferida é o 4º/último bloco de código)
XML me parece ser a escolha óbvia de estrutura de dados para usar aqui.
O truque para fazer o SQL Server usar valores diferentes
top()
para cada nó é fazer as subconsultas correlacionadas.N1.N > 0
eN2.N > 0
.Achatando o XML:
E uma versão totalmente sem XML.
Correlação
N1.N > 0
eN2.N > 0
ainda é importante.Uma versão usando uma tabela com 20 nomes para serem usados em vez de apenas números inteiros.
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
Então aqui está o que eu vim com. Com o objetivo de criar uma estrutura de diretórios, procurei "nomes" utilizáveis para os diretórios e arquivos. Como não consegui
TOP(n)
trabalhar noCROSS APPLY
s (acho que tentei correlacionar as consultas usando um valor do pai como on
noTOP(n)
, mas não foi aleatório), decidi criar um tipo de "números" tabela que permitiria que uma condiçãoINNER JOIN
ouWHERE
produzisse um conjunto den
elementos simplesmente randomizando um número e especificando-o comoWHERE table.Level = random_number
. O truque é que há apenas 1 linha para Level1, 2 linhas para Level2, 3 linhas para Level3 e assim por diante. Portanto, usandoWHERE LevelID = 3
me obterá 3 linhas e cada linha tem um valor que posso usar como um nome de diretório.CONFIGURAR
Esta parte foi originalmente especificada em linha, como parte do CTE. Mas, por uma questão de legibilidade (para que você não precise percorrer muitas
INSERT
instruções para chegar às poucas linhas da consulta real), dividi-a em uma tabela temporária local.CONSULTA PRINCIPAL
Para o nível 1, acabei
[name]
de pegar os valores,sys.objects
pois sempre há muitas linhas lá. Mas, se eu precisasse de mais controle sobre os nomes, poderia simplesmente expandir a#Elements
tabela para conter níveis adicionais.CONSULTA ADAPTADA PARA PRODUZIR O CAMINHO, NOME E CONTEÚDO DE CADA ARQUIVO
Para gerar os caminhos completos para os arquivos e o conteúdo do arquivo, fiz o SELECT principal do CTE apenas outro CTE e adicionei um novo SELECT principal que forneceu as saídas adequadas que simplesmente precisam ir para os arquivos.
CRÉDITO EXTRA
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: