Non dimenticare che nella scienza un errore ha quasi sempre soluzioni diverse, ma qui insegneremo il più ottimale e il migliore.
Soluzione:
(Nota di O.P: la soluzione preferita è il quarto/ultimo blocco di codice)
XML mi sembra la scelta più ovvia di struttura dati da usare in questo caso.
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 far sì che SQL Server utilizzi valori diversi per top()
per ogni nodo è quello di rendere le sottoquery correlate. N1.N > 0
e N2.N > 0
.
Appiattire l'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 > 0
e N2.N > 0
è ancora importante.
Una versione che utilizza una tabella con 20 nomi da utilizzare al posto dei soli 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;
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 aggiungervi informazioni extra, come i nomi di file e cartelle.
Quindi, volevo generare una tabella classica per memorizzare 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 devono partire da 1. Ci devono essere 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. Se necessario, si possono aggiungere altri livelli, basta aggiungere una riga supplementare di parametri.
Per rendere possibile questa generazione dinamica, ho dovuto ricordare il numero casuale di righe per il livello successivo, quindi ho una colonna in più ChildRowCount
.
Generazione unicoIDs
è anch'esso un po' complicato. Ho codificato in modo rigido il limite di 100 righe figlio per 1 riga genitore per garantire che IDs
non si ripetano. Questo è ciò che POWER(100, CTE.Lvl)
si riferisce a questo. Di conseguenza, ci sono grandi lacune in IDs
. Quel numero potrebbe essere un MAX(LvlMax)
ma ho inserito la costante 100 nella query per semplicità. Il numero di livelli non è codificato in modo rigido, 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 poi scalato all'intervallo richiesto.
La logica della query è semplice. È ricorsiva. Il primo passo genera un insieme di righe del primo livello. Il numero di righe è determinato da un numero casuale in TOP
. Inoltre, per ogni riga c'è un numero casuale separato di righe figlie memorizzato in ChildRowCount
.
La parte ricorsiva utilizza CROSS APPLY
per generare un determinato numero di righe figlio per ogni riga genitore. Ho dovuto usare WHERE Numbers.Number <= CTE.ChildRowCount
invece di TOP(CTE.ChildRowCount)
perché TOP
non è consentito nella parte ricorsiva di CTE. Non sapevo di 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 (ci possono essere fino a 20 + 20*10 + 200*5 = 1220 righe se si è fortunati)
+---------+----------+-----+-------------------+
| 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 invece della gerarchia collegata
Se siamo interessati solo al percorso completo N
livelli di profondità, possiamo omettere ID
e ParentID
dal CTE. Se abbiamo un elenco di nomi possibili nella tabella supplementare Names
è facile selezionarli da questa tabella in CTE. La tabella Names
deve avere un numero sufficiente di 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 avere diverse serie di righe per ogni livello, ma è facile impostarle 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 i campi FullPath
in FilePath
e 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 01I | AAA 01 | I |
| AAA 01II | AAA 01 | II |
| AAA 02I | AAA 02 | I |
| AAA 02II | AAA 02 | II |
| AAA 02III | AAA 02 | III |
| AAA 02IV | AAA 02 | IV |
| AAA 02V | AAA 02 | V |
| AAA 03I | AAA 03 | I |
| AAA 03II | AAA 03 | II |
| AAA 03III | AAA 03 | III |
| AAA 04I | AAA 04 | I |
| AAA 04II | AAA 04 | II |
| AAA 04III | AAA 04 | III |
| AAA 04IV | AAA 04 | IV |
| BBB 01I | BBB 01 | I |
| BBB 01II | BBB 01 | II |
| CCC 01I | CCC 01 | I |
| CCC 01II | CCC 01 | II |
| CCC 01III | CCC 01 | III |
| CCC 01IV | CCC 01 | IV |
| CCC 01V | CCC 01 | V |
| CCC 02I | CCC 02 | I |
| CCC 03I | CCC 03 | I |
| CCC 03II | CCC 03 | II |
| CCC 04I | CCC 04 | I |
| CCC 04II | CCC 04 | II |
| CCC 05I | CCC 05 | I |
| CCC 05II | CCC 05 | II |
| CCC 05III | CCC 05 | III |
| CCC 06I | CCC 06 | I |
| CCC 06II | CCC 06 | II |
| CCC 06III | CCC 06 | III |
| CCC 06IV | CCC 06 | IV |
| CCC 07I | CCC 07 | I |
| CCC 07II | CCC 07 | II |
| CCC 07III | CCC 07 | III |
| CCC 07IV | CCC 07 | IV |
| CCC 08I | CCC 08 | I |
| CCC 08II | CCC 08 | II |
| CCC 08III | CCC 08 | III |
| CCC 09I | CCC 09 | I |
| CCC 09II | CCC 09 | II |
| CCC 09III | CCC 09 | III |
| CCC 09IV | CCC 09 | IV |
| CCC 10I | CCC 10 | I |
| CCC 10II | CCC 10 | II |
| CCC 10III | CCC 10 | III |
| DDD 01I | DDD 01 | I |
| DDD 01II | DDD 01 | II |
| DDD 01III | DDD 01 | III |
| DDD 01IV | DDD 01 | IV |
| DDD 02I | DDD 02 | I |
| DDD 03I | DDD 03 | I |
| DDD 03II | DDD 03 | II |
| DDD 03III | DDD 03 | III |
| DDD 03IV | DDD 03 | IV |
| DDD 04I | DDD 04 | I |
| DDD 04II | DDD 04 | II |
| DDD 04III | DDD 04 | III |
| DDD 05I | DDD 05 | I |
| DDD 06I | DDD 06 | I |
| DDD 06II | DDD 06 | II |
| DDD 06III | DDD 06 | III |
| DDD 07I | DDD 07 | I |
| DDD 07II | DDD 07 | II |
| DDD 08I | DDD 08 | I |
| DDD 08II | DDD 08 | II |
| DDD 08III | DDD 08 | III |
| DDD 09I | DDD 09 | I |
| DDD 09II | DDD 09 | II |
| DDD 10I | DDD 10 | I |
| DDD 10II | DDD 10 | II |
| DDD 10III | DDD 10 | III |
| DDD 10IV | DDD 10 | IV |
| DDD 10V | DDD 10 | V |
| EEE 01I | EEE 01 | I |
| EEE 01II | EEE 01 | II |
| FFF 01I | FFF 01 | I |
| FFF 02I | FFF 02 | I |
| FFF 02II | FFF 02 | II |
| FFF 03I | FFF 03 | I |
| FFF 03II | FFF 03 | II |
| FFF 03III | FFF 03 | III |
| FFF 03IV | FFF 03 | IV |
| FFF 03V | FFF 03 | V |
| FFF 04I | FFF 04 | I |
| FFF 04II | FFF 04 | II |
| FFF 04III | FFF 04 | III |
| FFF 04IV | FFF 04 | IV |
| FFF 05I | FFF 05 | I |
| FFF 06I | FFF 06 | I |
| FFF 07I | FFF 07 | I |
| FFF 07II | FFF 07 | II |
| FFF 07III | FFF 07 | III |
| GGG 01I | GGG 01 | I |
| GGG 01II | GGG 01 | II |
| GGG 01III | GGG 01 | III |
| GGG 02I | GGG 02 | I |
| GGG 03I | GGG 03 | I |
| GGG 03II | GGG 03 | II |
| GGG 03III | GGG 03 | III |
| GGG 04I | GGG 04 | I |
| GGG 04II | GGG 04 | II |
| HHH 01I | HHH 01 | I |
| HHH 01II | HHH 01 | II |
| HHH 01III | HHH 01 | III |
| HHH 02I | HHH 02 | I |
| HHH 02II | HHH 02 | II |
| HHH 02III | HHH 02 | III |
| HHH 02IV | HHH 02 | IV |
| HHH 02V | HHH 02 | V |
| HHH 03I | HHH 03 | I |
| HHH 03II | HHH 03 | II |
| HHH 03III | HHH 03 | III |
| HHH 03IV | HHH 03 | IV |
| HHH 03V | HHH 03 | V |
| HHH 04I | HHH 04 | I |
| HHH 04II | HHH 04 | II |
| HHH 04III | HHH 04 | III |
| HHH 04IV | HHH 04 | IV |
| HHH 04V | HHH 04 | V |
| HHH 05I | HHH 05 | I |
| HHH 05II | HHH 05 | II |
| HHH 05III | HHH 05 | III |
| HHH 05IV | HHH 05 | IV |
| HHH 05V | HHH 05 | V |
| HHH 06I | HHH 06 | I |
| HHH 07I | HHH 07 | I |
| HHH 07II | HHH 07 | II |
| HHH 07III | HHH 07 | III |
| HHH 08I | HHH 08 | I |
| HHH 08II | HHH 08 | II |
| HHH 08III | HHH 08 | III |
| HHH 08IV | HHH 08 | IV |
| HHH 08V | HHH 08 | V |
+-------------+----------+----------+
Ecco cosa ho ottenuto. Con l'obiettivo di creare una struttura di directory, stavo cercando dei "nomi" utilizzabili per le directory e i file. Poiché non sono riuscito a ottenere il nome TOP(n)
funziona nella cartella CROSS APPLY
(credo di aver tentato di mettere in relazione le query usando un valore del genitore come il nome n
nel campo TOP(n)
ma non era casuale), ho deciso di creare un tipo di tabella "numeri" che permettesse di inserire un valore INNER JOIN
o WHERE
di produrre un insieme di n
semplicemente randomizzando un numero e specificandolo come WHERE table.Level = random_number
. Il trucco è che c'è solo una riga per il livello 1, 2 righe per il livello 2, 3 righe per il livello 3 e così via. Quindi, utilizzando WHERE LevelID = 3
si otterranno 3 righe e ogni riga ha un valore che si può usare come nome di directory.
IMPOSTAZIONE
Questa parte era originariamente specificata in linea, come parte del CTE. Ma per motivi di leggibilità (in modo da non dover scorrere molti file INSERT
per arrivare alle poche righe della query vera e propria), l'ho suddivisa 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;
QUERY PRINCIPALE
Per il livello 1 ho semplicemente preso [name]
da sys.objects
poiché ci sono sempre molte righe lì. Ma se avessi bisogno di un maggiore controllo sui nomi, potrei semplicemente espandere il file #Elements
per contenere altri livelli.
;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 IL PERCORSO, IL NOME E IL CONTENUTO DI OGNI FILE
Per generare i percorsi completi dei file e il loro contenuto, ho fatto in modo che la SELECT principale della CTE fosse un'altra CTE e ho aggiunto una nuova SELECT principale che forniva gli output corretti che dovevano semplicemente andare nei file.
DECLARE @Template NVARCHAR(4000);
SET @Template = N'
R000000
I
{{Tag30}}
{{Tag40}}
{{Tag50}}
2
';
;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
Anche se non fa parte dei requisiti indicati nella domanda, l'obiettivo (che è stato menzionato) era quello di creare dei file con cui testare le funzioni ricorsive del File System. Quindi, come possiamo prendere questo insieme di risultati di nomi di percorsi, nomi di file e contenuti di file e farne qualcosa? Abbiamo bisogno di due funzioni SQLCLR: una per creare le cartelle e una per creare i file.
Per rendere funzionali questi dati, ho modificato la funzione principale SELECT
del CTE mostrato direttamente sopra, come segue:
SELECT SQL#.File_CreateDirectory(
N'C:StuffTestXmlFiles' + mn.FullPath) AS [CreateTheDirectory],
SQL#.File_WriteFile(
N'C:StuffTestXmlFiles' + 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;