Crea una gerarchia di più livelli in cui ogni nodo ha un numero casuale di figli


16

Devo creare alcuni dati di test che coinvolgono una gerarchia. Potrei renderlo semplice e fare un paio di CROSS JOINs, ma questo mi darebbe una struttura completamente uniforme / senza alcuna variazione. Ciò non solo sembra noioso, ma la mancanza di variazione nei dati dei test talvolta maschera i problemi che altrimenti verrebbero trovati. Quindi, desidero generare una gerarchia non uniforme che segue queste regole:

  • 3 livelli di profondità
    • Il livello 1 è casuale tra 5 e 20 nodi
    • Il livello 2 è 1 - 10 nodi, casuali per ogni nodo del livello 1
    • Il livello 3 è 1 - 5 nodi, casuali per ogni nodo del livello 2
  • Tutti i rami avranno 3 livelli di profondità. L'uniformità in profondità va bene a questo punto.
  • È possibile che si verifichino sovrapposizioni nei nomi dei nodi figlio su un determinato livello (vale a dire che i nomi dei nodi figlio non devono essere univoci in tutti i nodi sullo stesso livello).
  • Il termine "casuale" è definito qui come pseudo-casuale, non unicamente casuale. Questo deve essere menzionato poiché il termine "casuale" è spesso usato per indicare "ordinamento casuale di un determinato insieme che non produce duplicati". Accetto random = random e se il numero di figli per ogni nodo di Livello 1 è solo 4, 7 e 8, anche attraverso 20 nodi al Livello 1 che ha una diffusione potenziale di 1 - 10 figli per ciascuno di quei nodi, allora va bene, perché questo è ciò che è casuale.
  • Anche se questo può essere fatto abbastanza facilmente con i WHILEloop nidificati , la preferenza è trovare un approccio basato su set. In generale, la generazione di dati di test non ha i requisiti di efficienza che avrebbe il codice di produzione, ma girare per un approccio basato sul set sarà probabilmente più istruttivo e aiuterà in futuro a trovare approcci basati sul set ai problemi. Quindi i WHILEloop non sono esclusi, ma possono essere utilizzati solo se non è possibile un approccio basato su set.
  • Basato su set = idealmente una singola query, indipendentemente da CTE, APPLY, ecc. Quindi l'uso di una tabella numerica esistente o incorporata va bene. L'uso di un approccio WHILE / CURSOR / procedurale non funzionerà. Suppongo che le porzioni di gestione temporanea dei dati in tabelle temporanee o variabili di tabella siano corrette, purché le operazioni siano tutte basate su set, nessun loop. Tuttavia, detto ciò, un approccio a query singola sarà probabilmente preferito su più query, a meno che non si possa dimostrare che l'approccio a più query è effettivamente migliore. Tieni inoltre presente che ciò che costituisce "migliore" è in genere soggettivo ;-). Tieni inoltre presente che anche l'uso di "tipicamente" nella frase precedente è soggettivo.
  • Qualsiasi versione ed edizione di SQL Server (2005 e più recenti, suppongo) andrà bene.
  • Solo puro T-SQL: nessuna di quelle sciocche cose SQLCLR !! Almeno in termini di generazione dei dati. La creazione delle directory e dei file verrà eseguita utilizzando SQLCLR. Ma qui mi sto solo concentrando sulla generazione dei valori di cosa creare.
  • Le TVF multi-statement T-SQL sono considerate procedurali, non basate su insiemi, anche se all'esterno mascherano l'approccio procedurale in un insieme. Ci sono momenti in cui ciò è assolutamente appropriato. Questa non è una di quelle volte. Sulla stessa linea, anche le funzioni scalari T-SQL non sono consentite, non solo perché sono anche procedurali, ma lo Strumento per ottimizzare le query talvolta memorizza nella cache il loro valore e lo ripete in modo tale che l'output non sia come previsto.
  • I TVF in linea T-SQL (noti anche come iTVF) sono okey-dokey in quanto sono basati su set ed effettivamente gli stessi dell'uso [ CROSS | OUTER ] APPLY, che è stato dichiarato sopra come ok.
  • Le esecuzioni ripetute della query dovrebbero produrre risultati per lo più diversi dall'esecuzione precedente.
  • Chiarimento Aggiornamento 1: il set di risultati finali deve essere espresso come con una riga per ciascun nodo distinto di Level3, con il percorso completo che inizia a Level1. Ciò significa che i valori Level1 e Level2 si ripeteranno necessariamente su una o più righe, tranne nei casi in cui vi sia un solo nodo Level2 contenente solo un singolo nodo Level3.
  • Chiarimento Aggiornamento 2: esiste una preferenza molto forte per ogni nodo con un nome o un'etichetta e non solo un numero. Ciò consentirà che i dati dei test risultanti siano più significativi e realistici.

Non sono sicuro che queste informazioni aggiuntive siano importanti, ma nel caso in cui aiuti ad avere un certo contesto, i dati del test si riferiscono alla mia risposta a questa domanda:

Importare file XML in SQL Server 2012

Sebbene non sia rilevante a questo punto, l'obiettivo finale di generare questa gerarchia è creare una struttura di directory per testare i metodi di file system ricorsivi. I livelli 1 e 2 saranno directory e il livello 3 finirà per essere il nome del file. Ho cercato (sia qui che tramite Google) e ho trovato solo un riferimento per generare una gerarchia casuale:

Linux: crea una gerarchia casuale di directory / file

Quella domanda (su StackOverflow) è in realtà abbastanza vicina in termini di risultato desiderato poiché cerca anche di creare una struttura di directory per i test. Ma quella domanda (e le risposte) sono focalizzate sullo scripting di shell Linux / Unix e non tanto sul mondo basato su set in cui viviamo.

Ora so come generare dati casuali e lo sto già facendo per creare il contenuto dei file in modo che possano mostrare anche le variazioni. La parte difficile qui è che il numero di elementi all'interno di ogni set è casuale, non un campo particolare. Inoltre , il numero di elementi all'interno di ciascun nodo deve essere casuale da altri nodi su quegli stessi livelli.

Gerarchia di esempio

     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

Set di risultati di esempio Descrivendo la Gerarchia sopra

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

Risposte:


9

( Nota dell'OP: la soluzione preferita è il quarto / ultimo blocco di codice)

XML mi sembra la scelta ovvia della struttura dei dati da utilizzare qui.

with N as
(
  select T.N
  from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),
              (12),(13),(14),(15),(16),(17),(18),(19),(20)) as T(N)
)

select top(5 + abs(checksum(newid())) % 15)
  N1.N as '@Value',
  (
  select top(1 + abs(checksum(newid())) % 10)
    N2.N as '@Value',
    (
    select top(1 + abs(checksum(newid())) % 5)
      N3.N as '@Value'
    from N as N3
    where N2.N > 0
    for xml path('Level3'), type
    )
  from N as N2
  where N1.N > 0
  for xml path('Level2'), type
  )
from N as N1
for xml path('Level1'), root('Root');

Il trucco per fare in modo che SQL Server utilizzi valori diversi top()per ciascun nodo consiste nel rendere correlate le query secondarie. N1.N > 0e N2.N > 0.

Flatteing dell'XML:

declare @X xml;

with N as
(
  select T.N
  from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),
              (12),(13),(14),(15),(16),(17),(18),(19),(20)) as T(N)
)
select @X  = (
             select top(5 + abs(checksum(newid())) % 15)
               N1.N as '@Value',
               (
               select top(1 + abs(checksum(newid())) % 10)
                 N2.N as '@Value',
                 (
                 select top(1 + abs(checksum(newid())) % 5)
                   N3.N as '@Value'
                 from N as N3
                 where N2.N > 0
                 for xml path('Level3'), type
                 )
               from N as N2
               where N1.N > 0
               for xml path('Level2'), type
               )
             from N as N1
             for xml path('Level1')
             );


select L1.X.value('@Value', 'varchar(10)')+'\'+
       L2.X.value('@Value', 'varchar(10)')+'\'+
       L3.X.value('@Value', 'varchar(10)')
from @X.nodes('/Level1') as L1(X)
  cross apply L1.X.nodes('Level2') as L2(X)
  cross apply L2.X.nodes('Level3') as L3(X);

E una versione totalmente priva di XML.

with N as
(
  select T.N
  from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),
              (12),(13),(14),(15),(16),(17),(18),(19),(20)) as T(N)
)
select cast(N1.N as varchar(10))+'\'+
       cast(N2.N as varchar(10))+'\'+
       cast(N3.N as varchar(10))
from (
     select top(5 + abs(checksum(newid())) % 15)
       N.N
     from N
     ) as N1
  cross apply
     (
     select top(1 + abs(checksum(newid())) % 10)
       N.N
     from N
     where N1.N > 0
     ) as N2
  cross apply
     (
     select top(1 + abs(checksum(newid())) % 5)
       N.N
     from N
     where N2.N > 0
     ) as N3;

Correlazione N1.N > 0ed N2.N > 0è ancora importante.

Una versione che utilizza una tabella con 20 nomi da utilizzare anziché solo numeri interi.

declare @Elements table
(
  Name nvarchar(50) not null
);

insert into @Elements(Name)
select top(20) C.name 
from sys.columns as C
group by C.name;

select N1.Name + N'\' + N2.Name + N'\' + N3.Name
from (
     select top(5 + abs(checksum(newid())) % 15)
       E.Name
     from @Elements as E
     ) as N1
  cross apply
     (
     select top(1 + abs(checksum(newid())) % 10)
       E.Name
     from @Elements as E
     where N1.Name > ''
     ) as N2
  cross apply
     (
     select top(1 + abs(checksum(newid())) % 5)
       E.Name
     from @Elements as E
     where N2.Name > ''
     ) as N3;

1
Mi piace di più la nuova versione. È quasi la stessa cosa che mi è venuta in mente nel mio primo tentativo, ma per qualche motivo non sono riuscito a farlo TOP(n)funzionare correttamente nei 2 CROSS APPLYsecondi. Non sono sicuro di ciò che ho fatto in modo diverso / errato da quando mi sono liberato di quel codice una volta che ho funzionato qualcos'altro. Lo posterò presto, ora che hai fornito questo aggiornamento. E ho ripulito la maggior parte dei miei commenti sopra.
Solomon Rutzky,

Ho appena pubblicato la mia versione. Le differenze principali sono: 1) poiché non sono riuscito a far funzionare TOP (n), sono andato con il recupero di nelementi tramite una condizione WHERE, e 2) ho il namecomponente che è più controllato di randomizzare directory e / o nomi di file .
Solomon Rutzky,

Mi dispiace essere via per così tanto tempo, ma sono stato pazzo impegnato. Tuttavia, ci ho pensato e non posso decidere tra la mia risposta e la tua versione non XML. Mi piace la tua semplicità e flessibilità, ma ho bisogno della possibilità di restituire nomi da utilizzare per creare una struttura di cartelle, che la mia ha. Poi mi sono reso conto di avere Vlad che aggiornava il suo per avere una tabella di ricerca e UNISCITI ad esso per ottenere il risultato ideale. Quindi, se non è inappropriato chiedere, potresti aggiornare il tuo per includere la stessa ricerca? Quindi tutte e 3 le risposte darebbero un risultato equivalente (ideale per confrontare tutte e 3), e accetterei la tua. È ok?
Solomon Rutzky,

1
@srutzky Ho aggiornato la risposta. È stato un po 'di tempo fa, quindi spero di aver capito bene e cosa stavi cercando. Puoi ovviamente aggiungere una colonna di livello @Elemetsper ottenere un diverso set di nomi per ogni livello tra cui scegliere.
Mikael Eriksson,

1
@srutzky non preoccuparti. Sono contento che la risposta ti sia stata utile.
Mikael Eriksson,

6

È stato interessante

Il mio obiettivo era quello di generare un determinato numero di livelli con un numero casuale di righe figlio per ogni livello in una struttura gerarchica correttamente collegata. Una volta che questa struttura è pronta, è facile aggiungere informazioni extra come i nomi di file e cartelle.

Quindi, volevo generare una tabella classica per la memorizzazione di un albero:

ID int NOT NULL
ParentID int NULL
Lvl int NOT NULL

Poiché abbiamo a che fare con la ricorsione, la CTE ricorsiva sembra una scelta naturale.

Avrò bisogno di una tabella di numeri . I numeri nella tabella dovrebbero partono da 1. Ci dovrebbe essere di almeno 20 numeri nella tabella: MAX(LvlMax).

CREATE TABLE [dbo].[Numbers](
    [Number] [int] NOT NULL,
CONSTRAINT [PK_Numbers] PRIMARY KEY CLUSTERED 
(
    [Number] ASC
));

INSERT INTO Numbers(Number)
SELECT TOP(1000)
    ROW_NUMBER() OVER(ORDER BY S.object_id)  AS Number
FROM
    sys.all_objects AS S
ORDER BY Number;

I parametri per la generazione dei dati devono essere memorizzati in una tabella:

DECLARE @Intervals TABLE (Lvl int, LvlMin int, LvlMax int);
INSERT INTO @Intervals (Lvl, LvlMin, LvlMax) VALUES
(1, 5, 20),
(2, 1, 10),
(3, 1, 5);

Si noti che la query è piuttosto flessibile e tutti i parametri sono separati in un unico posto. Puoi aggiungere più livelli se necessario, basta aggiungere una riga aggiuntiva di parametri.

Per rendere possibile una generazione così dinamica, ho dovuto ricordare il numero casuale di righe per il livello successivo, quindi ho una colonna aggiuntiva ChildRowCount.

Generare unico IDs è anche un po 'complicato. Ho codificato il limite di 100 righe figlio per 1 riga padre per garantire che IDsnon si ripetano. Ecco di cosa POWER(100, CTE.Lvl)si tratta. Di conseguenza ci sono grandi lacune IDs. Quel numero potrebbe essere un MAX(LvlMax), ma ho inserito la costante 100 nella query per semplicità. Il numero di livelli non è codificato, ma è determinato da @Intervals.

Questa formula

CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5

genera un numero casuale in virgola mobile nell'intervallo [0..1), che viene quindi ridimensionato all'intervallo richiesto.

La logica della query è semplice. È ricorsivo. Il primo passaggio genera un insieme di righe del primo livello. Il numero di righe è determinato da un numero casuale in TOP. Inoltre, per ogni riga è memorizzato un numero casuale separato di righe figlio ChildRowCount.

La parte ricorsiva utilizza CROSS APPLYper generare un determinato numero di righe figlio per ogni riga padre. Ho dovuto usare WHERE Numbers.Number <= CTE.ChildRowCountinvece di TOP(CTE.ChildRowCount), perché TOPnon è consentito nella parte ricorsiva di CTE. Prima non conoscevo questa limitazione di SQL Server.

WHERE CTE.ChildRowCount IS NOT NULL interrompe la ricorsione.

SQL Fiddle

WITH
CTE
AS
(
    SELECT 
        TOP(CAST(
            (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 
            (
                1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = 1)
                  - (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 1)
            )
            + (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 1)
            AS int))
        Numbers.Number AS ID
        ,NULL AS ParentID
        ,1 AS Lvl
        ,CAST(
            (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 
            (
                1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = 2)
                  - (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 2)
            )
            + (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 2)
            AS int) AS ChildRowCount
    FROM Numbers
    ORDER BY Numbers.Number

    UNION ALL

    SELECT
        CA.Number + CTE.ID * POWER(100, CTE.Lvl) AS ID
        ,CTE.ID AS ParentID
        ,CTE.Lvl + 1 AS Lvl
        ,CA.ChildRowCount
    FROM
        CTE
        CROSS APPLY
        (
            SELECT
                Numbers.Number
                ,CAST(
                    (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 
                    (
                    1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
                      - (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
                    )
                    + (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
                    AS int) AS ChildRowCount
            FROM Numbers
            WHERE Numbers.Number <= CTE.ChildRowCount
        ) AS CA
    WHERE
        CTE.ChildRowCount IS NOT NULL
)
SELECT *
FROM CTE
ORDER BY Lvl, ParentID, ID;

Risultato (possono esserci fino a 20 + 20 * 10 + 200 * 5 = 1220 righe se sei fortunato)

+---------+----------+-----+-------------------+
|   ID    | ParentID | Lvl | ChildRowCount     |
+---------+----------+-----+-------------------+
|       1 | NULL     |   1 | 3                 |
|       2 | NULL     |   1 | 1                 |
|       3 | NULL     |   1 | 6                 |
|       4 | NULL     |   1 | 5                 |
|       5 | NULL     |   1 | 3                 |
|       6 | NULL     |   1 | 7                 |
|       7 | NULL     |   1 | 1                 |
|       8 | NULL     |   1 | 6                 |
|     101 | 1        |   2 | 3                 |
|     102 | 1        |   2 | 5                 |
|     103 | 1        |   2 | 1                 |
|     201 | 2        |   2 | 5                 |
|     301 | 3        |   2 | 4                 |
|     302 | 3        |   2 | 5                 |
|     303 | 3        |   2 | 1                 |
|     304 | 3        |   2 | 2                 |
|     305 | 3        |   2 | 4                 |
|     306 | 3        |   2 | 3                 |
|     401 | 4        |   2 | 3                 |
|     402 | 4        |   2 | 1                 |
|     403 | 4        |   2 | 2                 |
|     404 | 4        |   2 | 2                 |
|     405 | 4        |   2 | 4                 |
|     501 | 5        |   2 | 1                 |
|     502 | 5        |   2 | 3                 |
|     503 | 5        |   2 | 5                 |
|     601 | 6        |   2 | 2                 |
|     602 | 6        |   2 | 5                 |
|     603 | 6        |   2 | 3                 |
|     604 | 6        |   2 | 3                 |
|     605 | 6        |   2 | 4                 |
|     606 | 6        |   2 | 5                 |
|     607 | 6        |   2 | 4                 |
|     701 | 7        |   2 | 2                 |
|     801 | 8        |   2 | 2                 |
|     802 | 8        |   2 | 3                 |
|     803 | 8        |   2 | 3                 |
|     804 | 8        |   2 | 3                 |
|     805 | 8        |   2 | 5                 |
|     806 | 8        |   2 | 2                 |
| 1010001 | 101      |   3 | NULL              |
| 1010002 | 101      |   3 | NULL              |
| 1010003 | 101      |   3 | NULL              |
| 1020001 | 102      |   3 | NULL              |
| 1020002 | 102      |   3 | NULL              |
| 1020003 | 102      |   3 | NULL              |
| 1020004 | 102      |   3 | NULL              |
| 1020005 | 102      |   3 | NULL              |
| 1030001 | 103      |   3 | NULL              |
| 2010001 | 201      |   3 | NULL              |
| 2010002 | 201      |   3 | NULL              |
| 2010003 | 201      |   3 | NULL              |
| 2010004 | 201      |   3 | NULL              |
| 2010005 | 201      |   3 | NULL              |
| 3010001 | 301      |   3 | NULL              |
| 3010002 | 301      |   3 | NULL              |
| 3010003 | 301      |   3 | NULL              |
| 3010004 | 301      |   3 | NULL              |
| 3020001 | 302      |   3 | NULL              |
| 3020002 | 302      |   3 | NULL              |
| 3020003 | 302      |   3 | NULL              |
| 3020004 | 302      |   3 | NULL              |
| 3020005 | 302      |   3 | NULL              |
| 3030001 | 303      |   3 | NULL              |
| 3040001 | 304      |   3 | NULL              |
| 3040002 | 304      |   3 | NULL              |
| 3050001 | 305      |   3 | NULL              |
| 3050002 | 305      |   3 | NULL              |
| 3050003 | 305      |   3 | NULL              |
| 3050004 | 305      |   3 | NULL              |
| 3060001 | 306      |   3 | NULL              |
| 3060002 | 306      |   3 | NULL              |
| 3060003 | 306      |   3 | NULL              |
| 4010001 | 401      |   3 | NULL              |
| 4010002 | 401      |   3 | NULL              |
| 4010003 | 401      |   3 | NULL              |
| 4020001 | 402      |   3 | NULL              |
| 4030001 | 403      |   3 | NULL              |
| 4030002 | 403      |   3 | NULL              |
| 4040001 | 404      |   3 | NULL              |
| 4040002 | 404      |   3 | NULL              |
| 4050001 | 405      |   3 | NULL              |
| 4050002 | 405      |   3 | NULL              |
| 4050003 | 405      |   3 | NULL              |
| 4050004 | 405      |   3 | NULL              |
| 5010001 | 501      |   3 | NULL              |
| 5020001 | 502      |   3 | NULL              |
| 5020002 | 502      |   3 | NULL              |
| 5020003 | 502      |   3 | NULL              |
| 5030001 | 503      |   3 | NULL              |
| 5030002 | 503      |   3 | NULL              |
| 5030003 | 503      |   3 | NULL              |
| 5030004 | 503      |   3 | NULL              |
| 5030005 | 503      |   3 | NULL              |
| 6010001 | 601      |   3 | NULL              |
| 6010002 | 601      |   3 | NULL              |
| 6020001 | 602      |   3 | NULL              |
| 6020002 | 602      |   3 | NULL              |
| 6020003 | 602      |   3 | NULL              |
| 6020004 | 602      |   3 | NULL              |
| 6020005 | 602      |   3 | NULL              |
| 6030001 | 603      |   3 | NULL              |
| 6030002 | 603      |   3 | NULL              |
| 6030003 | 603      |   3 | NULL              |
| 6040001 | 604      |   3 | NULL              |
| 6040002 | 604      |   3 | NULL              |
| 6040003 | 604      |   3 | NULL              |
| 6050001 | 605      |   3 | NULL              |
| 6050002 | 605      |   3 | NULL              |
| 6050003 | 605      |   3 | NULL              |
| 6050004 | 605      |   3 | NULL              |
| 6060001 | 606      |   3 | NULL              |
| 6060002 | 606      |   3 | NULL              |
| 6060003 | 606      |   3 | NULL              |
| 6060004 | 606      |   3 | NULL              |
| 6060005 | 606      |   3 | NULL              |
| 6070001 | 607      |   3 | NULL              |
| 6070002 | 607      |   3 | NULL              |
| 6070003 | 607      |   3 | NULL              |
| 6070004 | 607      |   3 | NULL              |
| 7010001 | 701      |   3 | NULL              |
| 7010002 | 701      |   3 | NULL              |
| 8010001 | 801      |   3 | NULL              |
| 8010002 | 801      |   3 | NULL              |
| 8020001 | 802      |   3 | NULL              |
| 8020002 | 802      |   3 | NULL              |
| 8020003 | 802      |   3 | NULL              |
| 8030001 | 803      |   3 | NULL              |
| 8030002 | 803      |   3 | NULL              |
| 8030003 | 803      |   3 | NULL              |
| 8040001 | 804      |   3 | NULL              |
| 8040002 | 804      |   3 | NULL              |
| 8040003 | 804      |   3 | NULL              |
| 8050001 | 805      |   3 | NULL              |
| 8050002 | 805      |   3 | NULL              |
| 8050003 | 805      |   3 | NULL              |
| 8050004 | 805      |   3 | NULL              |
| 8050005 | 805      |   3 | NULL              |
| 8060001 | 806      |   3 | NULL              |
| 8060002 | 806      |   3 | NULL              |
+---------+----------+-----+-------------------+

Generazione del percorso completo anziché della gerarchia collegata

Se siamo interessati solo ai Nlivelli del percorso completo in profondità, possiamo omettere IDe ParentIDdal CTE. Se abbiamo un elenco di possibili nomi nella tabella supplementare Names, è facile sceglierli da questa tabella in CTE. La Namestabella dovrebbe avere abbastanza righe per ogni livello: 20 per il livello 1, 10 per il livello 2, 5 per il livello 3; 20 + 10 + 5 = 35 in totale. Non è necessario disporre di diversi set di righe per ogni livello, ma è facile configurarlo correttamente, quindi l'ho fatto.

DECLARE @Names TABLE (Lvl int, Name nvarchar(4000), SeqNumber int);

-- First level: AAA, BBB, CCC, etc.
INSERT INTO @Names (Lvl, Name, SeqNumber)
SELECT 1, REPLICATE(CHAR(Number+64), 3) AS Name, Number AS SeqNumber
FROM Numbers
WHERE Number <= 20;

-- Second level: 001, 002, 003, etc.
INSERT INTO @Names (Lvl, Name, SeqNumber)
SELECT 2, REPLACE(STR(Number, 3), ' ', '0') AS Name, Number AS SeqNumber
FROM Numbers
WHERE Number <= 10;

-- Third level: I, II, III, IV, V
INSERT INTO @Names (Lvl, Name, SeqNumber) VALUES
(3, 'I',   1),
(3, 'II',  2),
(3, 'III', 3),
(3, 'IV',  4),
(3, 'V',   5);

SQL Fiddle Ecco la query finale. Ho diviso FullPathin FilePathe FileName.

WITH
CTE
AS
(
    SELECT 
        TOP(CAST(
            (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 
            (
                1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = 1)
                  - (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 1)
            )
            + (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 1)
            AS int))

        1 AS Lvl
        ,CAST(
            (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 
            (
                1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = 2)
                  - (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 2)
            )
            + (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = 2)
            AS int) AS ChildRowCount
        ,N.Name AS FullPath
        ,N.Name AS [FilePath]
        ,CAST(N'' AS nvarchar(4000)) AS [FileName]
    FROM
        Numbers
        INNER JOIN @Names AS N ON 
            N.SeqNumber = Numbers.Number AND N.Lvl = 1
    ORDER BY Numbers.Number

    UNION ALL

    SELECT
        CTE.Lvl + 1 AS Lvl
        ,CA.ChildRowCount
        ,CTE.FullPath + '\' + CA.Name AS FullPath

        ,CASE WHEN CA.ChildRowCount IS NOT NULL 
            THEN CTE.FullPath + '\' + CA.Name
            ELSE CTE.FullPath END AS [FilePath]

        ,CASE WHEN CA.ChildRowCount IS NULL 
            THEN CA.Name
            ELSE N'' END AS [FileName]
    FROM
        CTE
        CROSS APPLY
        (
            SELECT
                Numbers.Number
                ,CAST(
                    (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) * 
                    (
                    1 + (SELECT I.LvlMax FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
                      - (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
                    )
                    + (SELECT I.LvlMin FROM @Intervals AS I WHERE I.Lvl = CTE.Lvl + 2)
                    AS int) AS ChildRowCount
                ,N.Name
            FROM
                Numbers
                INNER JOIN @Names AS N ON 
                    N.SeqNumber = Numbers.Number AND N.Lvl = CTE.Lvl + 1
            WHERE Numbers.Number <= CTE.ChildRowCount
        ) AS CA
    WHERE
        CTE.ChildRowCount IS NOT NULL
)
SELECT
    CTE.FullPath
    ,CTE.[FilePath]
    ,CTE.[FileName]
FROM CTE
WHERE CTE.ChildRowCount IS NULL
ORDER BY FullPath;

Risultato

+-------------+----------+----------+
|  FullPath   | FilePath | FileName |
+-------------+----------+----------+
| AAA\001\I   | AAA\001  | I        |
| AAA\001\II  | AAA\001  | II       |
| AAA\002\I   | AAA\002  | I        |
| AAA\002\II  | AAA\002  | II       |
| AAA\002\III | AAA\002  | III      |
| AAA\002\IV  | AAA\002  | IV       |
| AAA\002\V   | AAA\002  | V        |
| AAA\003\I   | AAA\003  | I        |
| AAA\003\II  | AAA\003  | II       |
| AAA\003\III | AAA\003  | III      |
| AAA\004\I   | AAA\004  | I        |
| AAA\004\II  | AAA\004  | II       |
| AAA\004\III | AAA\004  | III      |
| AAA\004\IV  | AAA\004  | IV       |
| BBB\001\I   | BBB\001  | I        |
| BBB\001\II  | BBB\001  | II       |
| CCC\001\I   | CCC\001  | I        |
| CCC\001\II  | CCC\001  | II       |
| CCC\001\III | CCC\001  | III      |
| CCC\001\IV  | CCC\001  | IV       |
| CCC\001\V   | CCC\001  | V        |
| CCC\002\I   | CCC\002  | I        |
| CCC\003\I   | CCC\003  | I        |
| CCC\003\II  | CCC\003  | II       |
| CCC\004\I   | CCC\004  | I        |
| CCC\004\II  | CCC\004  | II       |
| CCC\005\I   | CCC\005  | I        |
| CCC\005\II  | CCC\005  | II       |
| CCC\005\III | CCC\005  | III      |
| CCC\006\I   | CCC\006  | I        |
| CCC\006\II  | CCC\006  | II       |
| CCC\006\III | CCC\006  | III      |
| CCC\006\IV  | CCC\006  | IV       |
| CCC\007\I   | CCC\007  | I        |
| CCC\007\II  | CCC\007  | II       |
| CCC\007\III | CCC\007  | III      |
| CCC\007\IV  | CCC\007  | IV       |
| CCC\008\I   | CCC\008  | I        |
| CCC\008\II  | CCC\008  | II       |
| CCC\008\III | CCC\008  | III      |
| CCC\009\I   | CCC\009  | I        |
| CCC\009\II  | CCC\009  | II       |
| CCC\009\III | CCC\009  | III      |
| CCC\009\IV  | CCC\009  | IV       |
| CCC\010\I   | CCC\010  | I        |
| CCC\010\II  | CCC\010  | II       |
| CCC\010\III | CCC\010  | III      |
| DDD\001\I   | DDD\001  | I        |
| DDD\001\II  | DDD\001  | II       |
| DDD\001\III | DDD\001  | III      |
| DDD\001\IV  | DDD\001  | IV       |
| DDD\002\I   | DDD\002  | I        |
| DDD\003\I   | DDD\003  | I        |
| DDD\003\II  | DDD\003  | II       |
| DDD\003\III | DDD\003  | III      |
| DDD\003\IV  | DDD\003  | IV       |
| DDD\004\I   | DDD\004  | I        |
| DDD\004\II  | DDD\004  | II       |
| DDD\004\III | DDD\004  | III      |
| DDD\005\I   | DDD\005  | I        |
| DDD\006\I   | DDD\006  | I        |
| DDD\006\II  | DDD\006  | II       |
| DDD\006\III | DDD\006  | III      |
| DDD\007\I   | DDD\007  | I        |
| DDD\007\II  | DDD\007  | II       |
| DDD\008\I   | DDD\008  | I        |
| DDD\008\II  | DDD\008  | II       |
| DDD\008\III | DDD\008  | III      |
| DDD\009\I   | DDD\009  | I        |
| DDD\009\II  | DDD\009  | II       |
| DDD\010\I   | DDD\010  | I        |
| DDD\010\II  | DDD\010  | II       |
| DDD\010\III | DDD\010  | III      |
| DDD\010\IV  | DDD\010  | IV       |
| DDD\010\V   | DDD\010  | V        |
| EEE\001\I   | EEE\001  | I        |
| EEE\001\II  | EEE\001  | II       |
| FFF\001\I   | FFF\001  | I        |
| FFF\002\I   | FFF\002  | I        |
| FFF\002\II  | FFF\002  | II       |
| FFF\003\I   | FFF\003  | I        |
| FFF\003\II  | FFF\003  | II       |
| FFF\003\III | FFF\003  | III      |
| FFF\003\IV  | FFF\003  | IV       |
| FFF\003\V   | FFF\003  | V        |
| FFF\004\I   | FFF\004  | I        |
| FFF\004\II  | FFF\004  | II       |
| FFF\004\III | FFF\004  | III      |
| FFF\004\IV  | FFF\004  | IV       |
| FFF\005\I   | FFF\005  | I        |
| FFF\006\I   | FFF\006  | I        |
| FFF\007\I   | FFF\007  | I        |
| FFF\007\II  | FFF\007  | II       |
| FFF\007\III | FFF\007  | III      |
| GGG\001\I   | GGG\001  | I        |
| GGG\001\II  | GGG\001  | II       |
| GGG\001\III | GGG\001  | III      |
| GGG\002\I   | GGG\002  | I        |
| GGG\003\I   | GGG\003  | I        |
| GGG\003\II  | GGG\003  | II       |
| GGG\003\III | GGG\003  | III      |
| GGG\004\I   | GGG\004  | I        |
| GGG\004\II  | GGG\004  | II       |
| HHH\001\I   | HHH\001  | I        |
| HHH\001\II  | HHH\001  | II       |
| HHH\001\III | HHH\001  | III      |
| HHH\002\I   | HHH\002  | I        |
| HHH\002\II  | HHH\002  | II       |
| HHH\002\III | HHH\002  | III      |
| HHH\002\IV  | HHH\002  | IV       |
| HHH\002\V   | HHH\002  | V        |
| HHH\003\I   | HHH\003  | I        |
| HHH\003\II  | HHH\003  | II       |
| HHH\003\III | HHH\003  | III      |
| HHH\003\IV  | HHH\003  | IV       |
| HHH\003\V   | HHH\003  | V        |
| HHH\004\I   | HHH\004  | I        |
| HHH\004\II  | HHH\004  | II       |
| HHH\004\III | HHH\004  | III      |
| HHH\004\IV  | HHH\004  | IV       |
| HHH\004\V   | HHH\004  | V        |
| HHH\005\I   | HHH\005  | I        |
| HHH\005\II  | HHH\005  | II       |
| HHH\005\III | HHH\005  | III      |
| HHH\005\IV  | HHH\005  | IV       |
| HHH\005\V   | HHH\005  | V        |
| HHH\006\I   | HHH\006  | I        |
| HHH\007\I   | HHH\007  | I        |
| HHH\007\II  | HHH\007  | II       |
| HHH\007\III | HHH\007  | III      |
| HHH\008\I   | HHH\008  | I        |
| HHH\008\II  | HHH\008  | II       |
| HHH\008\III | HHH\008  | III      |
| HHH\008\IV  | HHH\008  | IV       |
| HHH\008\V   | HHH\008  | V        |
+-------------+----------+----------+

Approccio interessante :). Mi piace. Per completezza, puoi aggiungere la query per popolare la tabella Numbers (dal SQL Fiddle) o includerla semplicemente come parte del CTE? Quindi è più facile per qualcuno semplicemente copiare e incollare. Per questa risposta, l'output finale può essere espresso come ogni riga essendo un percorso completo da Level1 fino a Level3 per tutti i valori di Level3? Penso che occorrerebbero solo 2 INNER JOINsecondi in finale SELECT. Infine, è possibile assegnare nomi / etichette a ciascun nodo in modo che non siano solo numeri? Aggiornerò la domanda per chiarire entrambi questi punti.
Solomon Rutzky,

Da dove vengono questi nomi / etichette? Dovrei avere una tabella 'Nomi', che ha 20 righe e scegliere un nome da essa? Quindi lo stesso insieme di nomi apparirebbe su ogni livello. O ogni livello dovrebbe avere un proprio set di nomi separato?
Vladimir Baranov,

Penso che i nomi possano derivare da una tabella (temp, reale o variabile) o in linea come parte del CTE. Inizialmente li ho inseriti nel CTE ma poi li ho spostati in una tabella temporanea locale in modo che la parte principale della query fosse più leggibile qui. Penso che con la struttura che hai, sarebbe abbastanza facile avere un livello separato per livello. Ma se fosse solo un set di 20 che sarebbe anche sufficiente, fornirebbe solo una variazione leggermente inferiore nei dati di test. L'unico vero requisito è che nessun nome si ripeta all'interno di un nodo, poiché ciò provocherebbe un errore durante il tentativo di creare directory o file :).
Solomon Rutzky,

1
@srutzky, ho aggiunto una seconda variante.
Vladimir Baranov,

1
@srutzky, mi sono diviso FullPathin FilePathe FileName.
Vladimir Baranov,

4

Quindi ecco cosa mi è venuto in mente. Con l'obiettivo di creare una struttura di directory, stavo cercando "nomi" utilizzabili per le directory e i file. Dato che non ero in grado di ottenere il TOP(n)funzionamento in CROSS APPLYs (penso di aver tentato di correlare le query utilizzando un valore dal genitore come nin in TOP(n)ma poi non era casuale), ho deciso di creare un tipo di "numeri" tabella che consentirebbe una condizione INNER JOINo WHEREper produrre un insieme di nelementi semplicemente randomizzando un numero e specificandolo come WHERE table.Level = random_number. Il trucco è che c'è solo 1 riga per Level1, 2 righe per Level2, 3 righe per Level3 e così via. Quindi, usando WHERE LevelID = 3otterrò 3 righe e ogni riga ha un valore che posso usare come nome di directory.

IMPOSTARE

Questa parte era originariamente specificata in linea, come parte del CTE. Ma per motivi di leggibilità (in modo che non sia necessario scorrere molte INSERTistruzioni per arrivare alle poche righe della query reale), l'ho suddiviso in una tabella temporanea locale.

IF (OBJECT_ID(N'tempdb..#Elements') IS NULL)
BEGIN
  PRINT 'Creating #Elements table...';
  CREATE TABLE #Elements (
     ElementLevel TINYINT NOT NULL,
     LevelName NVARCHAR(50) NOT NULL
                         );

  PRINT 'Populating #Elements table...';
  INSERT INTO #Elements (ElementLevel, LevelName)
    SELECT tmp.[Level], tmp.[Name]
    FROM (
                  SELECT 1,  N'Ella'
       UNION ALL  SELECT 2,  N'Itchy'
       UNION ALL  SELECT 2,  N'Scratchy'
       UNION ALL  SELECT 3,  N'Moe'
       UNION ALL  SELECT 3,  N'Larry'
       UNION ALL  SELECT 3,  N'Curly'
       UNION ALL  SELECT 4,  N'Ian'
       UNION ALL  SELECT 4,  N'Stephen'
       UNION ALL  SELECT 4,  N'Peter'
       UNION ALL  SELECT 4,  N'Bernard'
       UNION ALL  SELECT 5,  N'Michigan'
       UNION ALL  SELECT 5,  N'Erie'
       UNION ALL  SELECT 5,  N'Huron'
       UNION ALL  SELECT 5,  N'Ontario'
       UNION ALL  SELECT 5,  N'Superior'
       UNION ALL  SELECT 6,  N'White'
       UNION ALL  SELECT 6,  N'Orange'
       UNION ALL  SELECT 6,  N'Blonde'
       UNION ALL  SELECT 6,  N'Pink'
       UNION ALL  SELECT 6,  N'Blue'
       UNION ALL  SELECT 6,  N'Brown'
       UNION ALL  SELECT 7,  N'Asia'
       UNION ALL  SELECT 7,  N'Africa'
       UNION ALL  SELECT 7,  N'North America'
       UNION ALL  SELECT 7,  N'South America'
       UNION ALL  SELECT 7,  N'Antarctica'
       UNION ALL  SELECT 7,  N'Europe'
       UNION ALL  SELECT 7,  N'Australia'
       UNION ALL  SELECT 8,  N'AA'
       UNION ALL  SELECT 8,  N'BB'
       UNION ALL  SELECT 8,  N'CC'
       UNION ALL  SELECT 8,  N'DD'
       UNION ALL  SELECT 8,  N'EE'
       UNION ALL  SELECT 8,  N'FF'
       UNION ALL  SELECT 8,  N'GG'
       UNION ALL  SELECT 8,  N'HH'
       UNION ALL  SELECT 9,  N'I'
       UNION ALL  SELECT 9,  N'II'
       UNION ALL  SELECT 9,  N'III'
       UNION ALL  SELECT 9,  N'IV'
       UNION ALL  SELECT 9,  N'V'
       UNION ALL  SELECT 9,  N'VI'
       UNION ALL  SELECT 9,  N'VII'
       UNION ALL  SELECT 9,  N'VIII'
       UNION ALL  SELECT 9,  N'IX'
       UNION ALL  SELECT 10, N'Million'
       UNION ALL  SELECT 10, N'Billion'
       UNION ALL  SELECT 10, N'Trillion'
       UNION ALL  SELECT 10, N'Quadrillion'
       UNION ALL  SELECT 10, N'Quintillion'
       UNION ALL  SELECT 10, N'Sestillion'
       UNION ALL  SELECT 10, N'Sextillion'
       UNION ALL  SELECT 10, N'Octillion'
       UNION ALL  SELECT 10, N'Nonillion'
       UNION ALL  SELECT 10, N'Decillion'
     ) tmp([Level], [Name]);
END;

DOMANDA PRINCIPALE

Per il livello 1 ho appena preso i [name]valori sys.objectsperché ci sono sempre molte righe lì. Ma, se avessi bisogno di un maggiore controllo sui nomi, potrei semplicemente espandere la #Elementstabella per contenere livelli aggiuntivi.

;WITH topdir(Level1, Randy) AS
(
    SELECT TOP ( (CONVERT(INT, CRYPT_GEN_RANDOM(1)) % 20) + 5 ) so.[name],
                ( (CONVERT(INT, CRYPT_GEN_RANDOM(1)) % 10) + 1 )
    FROM sys.objects so
    ORDER BY CRYPT_GEN_RANDOM(8) ASC
)
SELECT  td.Level1, tmp1.Level2, tmp2.Level3
FROM    topdir td
CROSS APPLY (SELECT help.LevelName, (CONVERT(INT, CRYPT_GEN_RANDOM(1)) % 5) + 1
            FROM #Elements help
            WHERE help.ElementLevel = td.Randy
            ) tmp1 (Level2, Bandy)
CROSS APPLY (SELECT help.LevelName
            FROM #Elements help
            WHERE help.ElementLevel = tmp1.Bandy
            ) tmp2 (Level3);

QUERY ADATTATA PER PRODURRE OGNI PERCORSO, NOME E CONTENUTO DEL FILE

Al fine di generare i percorsi completi per i file e il contenuto del file, ho creato il SELECT principale del CTE solo un altro CTE e ho aggiunto un nuovo SELECT principale che ha fornito gli output corretti che devono semplicemente essere inseriti nei file.

DECLARE @Template NVARCHAR(4000);
SET @Template = N'<?xml version="1.0" encoding="ISO-8859-1"?>
<ns0:P4131 xmlns:ns0="http://switching/xi">
<R000000>
    <R00000010>R000000</R00000010>
    <R00000020>I</R00000020>
    <R00000030>{{Tag30}}</R00000030>
    <R00000040>{{Tag40}}</R00000040>
    <R00000050>{{Tag50}}</R00000050>
    <R00000060>2</R00000060>
</R000000>
</ns0:P4131>
';


;WITH topdir(Level1, Thing1) AS
(
    SELECT TOP ( (CONVERT(INT, CRYPT_GEN_RANDOM(1)) % 20) + 5 ) so.[name],
                ( (CONVERT(INT, CRYPT_GEN_RANDOM(1)) % 10) + 1 )
    FROM sys.objects so
    ORDER BY CRYPT_GEN_RANDOM(8) ASC
), main AS
(
   SELECT  td.Level1, tmp1.Level2, tmp2.Level3,
           td.Level1 + N'\' + tmp1.Level2 AS [FullPath],
           RIGHT('000' + CONVERT(VARCHAR(10),
                          (CONVERT(INT, CRYPT_GEN_RANDOM(2)) % 9999) + 1), 4) AS [R30],
           RIGHT('000' + CONVERT(VARCHAR(10),
                          (CONVERT(INT, CRYPT_GEN_RANDOM(2)) % 500) + 100), 4) AS [R50],
           ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS [RowNum]
   FROM    topdir td
   CROSS APPLY (SELECT help.LevelName, (CONVERT(INT, CRYPT_GEN_RANDOM(1)) % 5) + 1
                FROM #Elements help
                WHERE help.ElementLevel = td.Thing1
               ) tmp1 (Level2, Thing2)
   CROSS APPLY (SELECT help.LevelName
                FROM #Elements help
                WHERE help.ElementLevel = tmp1.Thing2
               ) tmp2 (Level3)
)
SELECT  mn.FullPath,
        mn.Level3 + N'.xml' AS [FileName],
        REPLACE(
            REPLACE(
                REPLACE(
                    @Template,
                    N'{{Tag30}}',
                    mn.R30),
                N'{{Tag40}}',
                mn.RowNum),
            N'{{Tag50}}',
            mn.R50) AS [Contents]
FROM    main mn;

CREDITO EXTRA

Pur non facendo parte dei requisiti indicati nella domanda, l'obiettivo (che è stato menzionato) era quello di creare file per testare le funzioni ricorsive del file system. Quindi, come possiamo prendere questo set di risultati di nomi di percorso, nomi di file e contenuto del file e fare qualcosa con esso? Abbiamo solo bisogno di due funzioni SQLCLR: una per creare le cartelle e una per creare i file.

Al fine di rendere funzionali questi dati, ho modificato il principale SELECTdel CTE mostrato direttamente sopra come segue:

SELECT  SQL#.File_CreateDirectory(
            N'C:\Stuff\TestXmlFiles\' + mn.FullPath) AS [CreateTheDirectory],
        SQL#.File_WriteFile(
            N'C:\Stuff\TestXmlFiles\' + mn.FullPath + N'\' + mn.Level3 + N'.xml',
            REPLACE(
                REPLACE(
                    REPLACE(
                        @Template,
                        N'{{Tag30}}',
                        mn.R30),
                    N'{{Tag40}}',
                    mn.RowNum),
                N'{{Tag50}}',
                mn.R50), -- @FileData
            0, -- @AppendData
            '' -- @FileEncoding
                            ) AS [WriteTheFile]
FROM    main mn;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.