Utwórz hierarchię wielu poziomów, w których każdy węzeł ma losową liczbę dzieci

16

Muszę utworzyć dane testowe, które obejmują hierarchię. Mogę to ułatwić i zrobić kilkaCROSS JOIN s, ale to dałoby mi strukturę, która jest całkowicie jednolita / bez żadnych zmian. To nie tylko wydaje się nudne, ale brak zmienności danych testowych czasami maskuje problemy, które w innym przypadku zostałyby odnalezione. Chcę więc wygenerować niejednorodną hierarchię, która będzie przestrzegać następujących zasad:

  • 3 poziomy głębokości
    • Poziom 1 to losowo 5–20 węzłów
    • Poziom 2 to 1-10 węzłów losowo na każdy węzeł poziomu 1
    • Poziom 3 to 1-5 węzłów losowo na każdy węzeł poziomu 2
  • Wszystkie gałęzie będą miały 3 poziomy głębokości. Jednorodność głębokości jest w tym momencie w porządku.
  • Nazwy węzłów podrzędnych mogą nakładać się na dowolnym poziomie (tzn. Nazwy węzłów podrzędnych nie muszą być unikalne we wszystkich węzłach na tym samym poziomie).
  • Termin „losowy” zdefiniowano tutaj jako pseudolosowy, a nie wyjątkowo losowy. Należy o tym wspomnieć, ponieważ termin „losowy” jest często używany w znaczeniu „losowego uporządkowania danego zestawu, który nie wytwarza duplikatów”. Akceptuję to losowe = losowe i jeśli liczba dzieci na każdy węzeł poziomu 1 wynosi tylko 4, 7 i 8, nawet w 20 węzłach na poziomie 1, który ma potencjalny rozkład 1 - 10 dzieci na każdy z tych węzłów, to jest w porządku, ponieważ właśnie tym jest losowość.
  • Chociaż można to zrobić dość łatwo za pomocą zagnieżdżonych WHILEpętli, preferowane jest znalezienie podejścia opartego na zestawie. Ogólnie rzecz biorąc, generowanie danych testowych nie ma wymagań dotyczących wydajności, jakie miałby kod produkcyjny, ale szukanie podejścia opartego na zestawie będzie prawdopodobnie bardziej edukacyjne i pomoże w przyszłości w znalezieniu opartych na zestawie podejść do problemów. Więc WHILEpętle nie są wykluczone-out, ale może być stosowany tylko wtedy, gdy nie ma podejście oparte zestaw jest możliwe.
  • Oparte na zestawie = idealnie pojedyncze zapytanie, niezależnie od CTE, ZASTOSUJ itp. Tak więc używanie istniejącej lub wbudowanej tabeli liczb jest w porządku. Zastosowanie PODCZAS / KURSOR / podejście proceduralne nie będzie działać. Przypuszczam, że przenoszenie części danych do tabel tymczasowych lub zmiennych tabel jest w porządku, o ile wszystkie operacje są oparte na zestawie, bez pętli. Jednak powiedziawszy to, podejście oparte na jednym zapytaniu będzie prawdopodobnie preferowane w stosunku do wielu zapytań, chyba że można wykazać, że podejście oparte na wielu zapytaniach jest w rzeczywistości lepsze. Należy również pamiętać, że to, co stanowi „lepszy”, jest zazwyczaj subiektywne ;-). Należy również pamiętać, że użycie słowa „typowo” w poprzednim zdaniu jest również subiektywne.
  • Każda wersja i edycja SQL Server (2005 i nowsze, jak sądzę) zrobi.
  • Tylko czysty T-SQL: nic z tego głupiego SQLCLR !! Przynajmniej w zakresie generowania danych. Tworzenie katalogów i plików zostanie wykonane za pomocą SQLCLR. Ale tutaj koncentruję się na generowaniu wartości tego, co stworzyć.
  • T-SQL Multi-statement TVF są uważane za proceduralne, a nie oparte na zestawie, mimo że na zewnątrz maskują podejście proceduralne w zestawie. Są chwile, kiedy jest to absolutnie właściwe. To nie jest jeden z tych czasów. Wzdłuż tych samych wierszy funkcje skalarne T-SQL również nie są dozwolone, nie tylko dlatego, że są również proceduralne, ale Optymalizator zapytań czasami buforuje swoją wartość i powtarza ją tak, że wynik nie jest zgodny z oczekiwaniami.
  • Inline TVF T-SQL (aka iTVF) są w porządku, ponieważ są oparte na zestawach i faktycznie są takie same jak w użyciu [ CROSS | OUTER ] APPLY, co stwierdzono powyżej jako prawidłowe.
  • Powtarzające się wykonanie zapytania (zapytań) powinno dawać głównie inny wynik niż poprzednie uruchomienie.
  • Wyjaśnienie Aktualizacja 1: Ostateczny zestaw wyników powinien być wyrażony jako posiadający jeden wiersz dla każdego odrębnego węzła poziomu 3, z pełną ścieżką rozpoczynającą się od poziomu 1. Oznacza to, że wartości Poziom 1 i Poziom 2 będą koniecznie powtarzać się w jednym lub większej liczbie wierszy, z wyjątkiem przypadków istnienia tylko jednego węzła Poziom 2 zawierającego tylko jeden węzeł Poziom 3.
  • Wyjaśnienie Aktualizacja 2: Istnieje bardzo silna preferencja dla każdego węzła mającego nazwę lub etykietę, a nie tylko liczbę. Dzięki temu otrzymane dane testowe będą bardziej znaczące i realistyczne.

Nie jestem pewien, czy te dodatkowe informacje mają znaczenie, ale na wypadek, gdyby pomogło to w pewnym kontekście, dane testowe odnoszą się do mojej odpowiedzi na to pytanie:

Importuj pliki XML do SQL Server 2012

Chociaż nie jest to w tym momencie istotne, celem generowania tej hierarchii jest utworzenie struktury katalogów w celu przetestowania metod rekurencyjnych systemów plików. Poziomy 1 i 2 będą katalogami, a poziom 3 ostatecznie będzie nazwą pliku. Szukałem (zarówno tutaj, jak i przez Google) i znalazłem tylko jedno odniesienie do generowania losowej hierarchii:

Linux: utwórz losową hierarchię katalogów / plików

To pytanie (na StackOverflow) jest właściwie dość zbliżone pod względem pożądanego wyniku, ponieważ ma to również na celu stworzenie struktury katalogów do testowania. Ale to pytanie (i odpowiedzi) koncentruje się na skryptach powłoki Linux / Unix, a nie na świecie opartym na zestawie, w którym żyjemy.

Teraz wiem, jak generować losowe dane i już to robię, aby utworzyć zawartość plików, aby mogły one również wyświetlać odmiany. Problem polega na tym, że liczba elementów w każdym zestawie jest losowa, a nie konkretne pole. I liczba elementów w każdym węźle musi być losowa z innych węzłów na tych samych poziomach.

Przykładowa hierarchia

     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

Przykładowy zestaw wyników opisujący powyższą hierarchię

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
Solomon Rutzky
źródło

Odpowiedzi:

9

( Uwaga OP: preferowanym rozwiązaniem jest czwarty / ostatni blok kodu)

Wydaje mi się, że XML jest oczywistym wyborem struktury danych do zastosowania tutaj.

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');

Sztuczka, aby SQL Server używał różnych wartości top()dla każdego węzła, polega na skorelowaniu zapytań podrzędnych. N1.N > 0a N2.N > 0.

Wyrównanie 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);

I wersja całkowicie pozbawiona 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;

Korelacja N1.N > 0i N2.N > 0jest nadal ważna.

Wersja korzystająca z tabeli zawierającej 20 nazw zamiast samych liczb całkowitych.

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;
Mikael Eriksson
źródło
1
Nowa wersja bardziej mi się podoba. Jest to prawie to samo, co wymyśliłem przy pierwszej próbie, ale z jakiegoś powodu nie mogłem TOP(n)poprawnie działać w ciągu 2 CROSS APPLYsekund. Nie jestem pewien, co zrobiłem inaczej / niepoprawnie, odkąd pozbyłem się tego kodu, gdy dostałem coś innego do roboty. Wkrótce opublikuję tę aktualizację. I wyczyściłem większość moich komentarzy powyżej.
Solomon Rutzky
Właśnie opublikowałem swoją wersję. Główne różnice to: 1) ponieważ nie mogłem uruchomić TOP (n) do pracy, poszedłem z pobieraniem nelementów poprzez warunek WHERE, i 2) mam namekomponent, który jest bardziej kontrolowany niż losowe nazwy katalogów i / lub plików .
Solomon Rutzky
Przepraszam, że nie było mnie tak długo, ale byłem szalenie zajęty. Mimo to zastanawiałem się nad tym i nie mogę się zdecydować między moją odpowiedzią a wersją inną niż XML. Podoba mi się twoja prostota i elastyczność, ale potrzebuję możliwości zwracania nazw w celu utworzenia struktury folderów, którą ma moja. Potem zdałem sobie sprawę, że kazałem Vladowi zaktualizować jego tabelę odnośników i DOŁĄCZ do niej, aby uzyskać idealny wynik. Jeśli więc nie jest niewłaściwe, możesz zaktualizować swój, aby zawierał to samo wyszukiwanie? Wtedy wszystkie 3 odpowiedzi dałyby równoważny wynik (idealny do porównania wszystkich 3), a ja zaakceptowałbym twoją. Czy to w porządku?
Solomon Rutzky
1
@srutzky Zaktualizowałem odpowiedź. To było dawno temu, więc mam nadzieję, że dobrze to zrozumiałem i czego szukasz. Możesz oczywiście dodać kolumnę poziomu, @Elemetsaby uzyskać inny zestaw nazw dla każdego poziomu do wyboru.
Mikael Eriksson
1
@srutzky bez obaw. Cieszę się, że odpowiedź była dla ciebie pomocna.
Mikael Eriksson
6

To było interesujące.

Moim celem było wygenerowanie określonej liczby poziomów z losową liczbą rzędów potomnych na każdy poziom w odpowiednio powiązanej strukturze hierarchicznej. Po przygotowaniu tej struktury łatwo jest dodać do niej dodatkowe informacje, takie jak nazwy plików i folderów.

Chciałem więc wygenerować klasyczny stół do przechowywania drzewa:

ID int NOT NULL
ParentID int NULL
Lvl int NOT NULL

Ponieważ mamy do czynienia z rekurencją, rekurencyjna CTE wydaje się naturalnym wyborem.

Potrzebuję tabeli liczb . Liczby w tabeli powinien zacząć od 1. Nie powinno być co najmniej 20 numerów w tabeli: 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;

Parametry do generowania danych powinny być przechowywane w tabeli:

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);

Zauważ, że zapytanie jest dość elastyczne, a wszystkie parametry są rozdzielone w jednym miejscu. W razie potrzeby możesz dodać więcej poziomów, po prostu dodaj dodatkowy wiersz parametrów.

Aby możliwe było takie dynamiczne generowanie, musiałem pamiętać losową liczbę wierszy na następny poziom, więc mam dodatkową kolumnę ChildRowCount.

Generowanie unikalnego IDs jest również nieco trudne. Zakodowałem na stałe limit 100 wierszy podrzędnych na 1 wiersz nadrzędny, aby zagwarantować, że IDssię nie powtórzą. O to właśnie POWER(100, CTE.Lvl)chodzi. W rezultacie występują duże luki IDs. Ta liczba może być a MAX(LvlMax), ale dla uproszczenia wstawiłem stałą 100. Liczba poziomów nie jest zakodowana na stałe, ale zależy od @Intervals.

Ta formuła

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

generuje losową liczbę zmiennoprzecinkową w zakresie [0..1), która jest następnie skalowana do wymaganego przedziału.

Logika zapytań jest prosta. Jest rekurencyjny. Pierwszy krok generuje zestaw wierszy pierwszego poziomu. Liczba wierszy zależy od liczby losowej w TOP. Ponadto dla każdego wiersza jest przechowywana osobna losowa liczba wierszy podrzędnych ChildRowCount.

Część rekurencyjna używa CROSS APPLYdo generowania określonej liczby wierszy potomnych na każdy wiersz macierzysty. Musiałem użyć WHERE Numbers.Number <= CTE.ChildRowCountzamiast TOP(CTE.ChildRowCount), ponieważ TOPnie jest dozwolone w rekurencyjnej części CTE. Nie wiedziałem wcześniej o tym ograniczeniu SQL Server.

WHERE CTE.ChildRowCount IS NOT NULL zatrzymuje rekurencję.

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;

Wynik (możesz mieć do 20 + 20 * 10 + 200 * 5 = 1220 wierszy, jeśli masz szczęście)

+---------+----------+-----+-------------------+
|   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              |
+---------+----------+-----+-------------------+

Generowanie pełnej ścieżki zamiast połączonej hierarchii

Jeśli jesteśmy zainteresowani tylko Ngłębokimi poziomami pełnej ścieżki , możemy pominąć IDi ParentIDz CTE. Jeśli mamy listę możliwych nazw w tabeli dodatkowej Names, łatwo jest je wybrać z tej tabeli w CTE. NamesTabela powinna mieć wystarczającą liczbę wierszy na każdym poziomie: 20 do poziomu 1, poziomu 2 10, 5 na poziomie 3; 20 + 10 + 5 = 35 ogółem. Nie trzeba mieć różnych zestawów wierszy dla każdego poziomu, ale łatwo go poprawnie skonfigurować, więc to zrobiłem.

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 Oto ostatnie zapytanie. Podzieliłem FullPathna FilePathi 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;

Wynik

+-------------+----------+----------+
|  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        |
+-------------+----------+----------+
Vladimir Baranov
źródło
Ciekawe podejście :). Lubię to. Dla kompletności, czy możesz dodać zapytanie, aby wypełnić tabelę Numbers (z SQL Fiddle), czy po prostu dołączyć to wstawienie jako część CTE? Wtedy łatwiej jest po prostu skopiować i wkleić. W przypadku tej odpowiedzi, czy wynik końcowy można wyrazić jako każdy wiersz będący pełną ścieżką od poziomu 1 do poziomu 3 dla wszystkich wartości poziomu 3? Myślę, że zajęłoby to tylko 2 INNER JOINsekundy w finale SELECT. Wreszcie, czy nazwy / etykiety można przypisać do każdego węzła, aby nie były to tylko liczby? Zaktualizuję pytanie, aby wyjaśnić oba te punkty.
Solomon Rutzky,
Skąd pochodzą te nazwy / etykiety? Czy powinienem mieć tabelę „Nazwy”, która ma 20 wierszy i wybrać z niej nazwę? W ten sposób na każdym poziomie pojawiałby się ten sam zestaw nazw. A może każdy poziom ma swój własny zestaw nazw?
Vladimir Baranov,
Myślę, że nazwy mogą pochodzić z tabeli (temp, real lub zmienna) lub wbudowanej w ramach CTE. Pierwotnie umieściłem je w CTE, ale następnie przeniosłem je do lokalnej tabeli tymczasowej, aby główna część zapytania była bardziej czytelna tutaj. Myślę, że dzięki posiadanej strukturze łatwo byłoby mieć osobne dla każdego poziomu. Ale jeśli byłby to tylko jeden zestaw 20, który również wystarczyłby, po prostu zapewnia nieco mniejszą zmienność danych testowych. Jedynym prawdziwym wymogiem jest to, że żadna nazwa nie powtarza się w węźle, ponieważ spowodowałoby to błąd podczas próby utworzenia katalogów lub plików :).
Solomon Rutzky,
1
@srutzky, dodałem drugi wariant.
Vladimir Baranov
1
@srutzky, podzieliłem się FullPathna FilePathi FileName.
Vladimir Baranov
4

Oto co wymyśliłem. W celu stworzenia struktury katalogów szukałem użytecznych „nazw” dla katalogów i plików. Ponieważ nie udało mi się zdobyćTOP(n) poprawnego działania CROSS APPLY(myślę, że próbowałem skorelować zapytania przy użyciu wartości nadrzędnej jako nw, TOP(n)ale nie było to przypadkowe), postanowiłem utworzyć rodzaj „liczb” stół, który pozwala osobie INNER JOINlub WHEREwarunku do produkcji zestawu nelementów po prostu wylosowaniu liczby i określając go jako WHERE table.Level = random_number. Sztuka polega na tym, że istnieje tylko 1 wiersz dla poziomu 1, 2 wiersze dla poziomu 2, 3 rzędy dla poziomu 3 i tak dalej. Dlatego za pomocąWHERE LevelID = 3 przyniesie mi 3 wiersze, a każdy wiersz ma wartość, której mogę użyć jako nazwy katalogu.

USTAWIAĆ

Ta część została pierwotnie określona jako część CTE. Ale ze względu na czytelność (abyś nie musiał przewijać wielu INSERTinstrukcji, aby przejść do kilku wierszy prawdziwego zapytania), podzieliłem go na lokalną tabelę tymczasową.

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;

GŁÓWNE ZAPYTANIE

Dla poziomu 1 właśnie złapałem [name] wartości, sys.objectsponieważ zawsze jest tam dużo wierszy. Ale gdybym potrzebował większej kontroli nad nazwami, mógłbym po prostu rozszerzyć #Elementstabelę o dodatkowe poziomy.

;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);

ZAPYTANIE DOSTOSOWANE DO PRODUKCJI KAŻDEJ ŚCIEŻKI PLIKU, NAZWY I TREŚCI

Aby wygenerować pełne ścieżki do plików i zawartości pliku, dokonałem głównego WYBORU CTE tylko innym CTE i dodałem nowy główny WYBÓR, który dał odpowiednie dane wyjściowe, które po prostu muszą przejść do plików.

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;

DODATKOWY KREDYT

Chociaż nie jest to część wymagań określonych w pytaniu, celem (o którym wspomniano) było utworzenie plików do testowania funkcji rekurencyjnych systemów plików. Jak więc wziąć ten zestaw wyników nazw ścieżek, nazw plików i zawartości plików i zrobić coś z tym? Potrzebujemy tylko dwóch funkcji SQLCLR: jednej do tworzenia folderów i jednej do tworzenia plików.

Aby te dane były funkcjonalne, zmodyfikowałem główną SELECTCTE pokazaną bezpośrednio powyżej w następujący sposób:

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;
Solomon Rutzky
źródło