Buat hierarki beberapa level di mana setiap node memiliki jumlah anak secara acak

16

Saya perlu membuat beberapa data uji yang melibatkan hierarki. Saya bisa membuatnya mudah dan melakukan beberapa CROSS JOINs, tetapi itu akan memberi saya struktur yang benar-benar seragam / tanpa variasi apa pun. Itu tidak hanya tampak membosankan, tetapi kurangnya variasi dalam data pengujian terkadang menutupi masalah yang seharusnya ditemukan. Jadi, saya ingin membuat hierarki yang tidak seragam yang mengikuti aturan ini:

  • 3 level
    • Level 1 secara acak 5 - 20 node
    • Level 2 adalah 1 - 10 node, acak per setiap node Level 1
    • Level 3 adalah 1 - 5 node, acak per setiap node Level 2
  • Semua cabang akan memiliki 3 level. Keseragaman secara mendalam ok pada saat ini.
  • Mungkin ada tumpang tindih dalam nama-nama simpul anak pada tingkat tertentu (mis. Nama-nama simpul anak tidak perlu unik di semua simpul pada tingkat yang sama).
  • Istilah "acak" didefinisikan di sini sebagai pseudo-acak, bukan acak unik. Ini perlu disebutkan karena istilah "acak" sering digunakan untuk berarti "pemesanan acak dari set yang diberikan yang tidak menghasilkan duplikat". Saya menerima bahwa acak = acak dan jika jumlah anak per setiap node Level 1 hanya 4, 7, dan 8, bahkan di 20 node pada Level 1 yang memiliki potensi penyebaran 1 - 10 anak per masing-masing node, maka itu baik-baik saja, karena itulah yang acak.
  • Meskipun ini dapat dilakukan dengan cukup mudah dengan WHILEloop bersarang , preferensi adalah untuk menemukan pendekatan berbasis set. Secara umum, menghasilkan data uji tidak memiliki persyaratan untuk efisiensi yang akan dimiliki kode Produksi, tetapi memotret untuk pendekatan berbasis set kemungkinan akan lebih mendidik dan membantu di masa depan dengan menemukan pendekatan berbasis set untuk masalah. Jadi WHILEloop tidak dikesampingkan, tetapi hanya dapat digunakan jika tidak ada pendekatan berbasis set yang dimungkinkan.
  • Set-based = idealnya satu permintaan, terlepas dari CTE, BERLAKU, dll. Jadi, menggunakan tabel angka yang ada atau sebaris tidak masalah. Menggunakan pendekatan WHILE / CURSOR / prosedural tidak akan berfungsi. Saya kira staging bagian dari data ke dalam tabel temp atau variabel tabel baik-baik saja, asalkan semua operasi berbasis set, tidak ada loop. Namun, yang dikatakan, pendekatan kueri tunggal mungkin akan lebih disukai daripada beberapa kueri, kecuali jika dapat ditunjukkan bahwa pendekatan multi-kueri sebenarnya lebih baik. Harap juga diingat bahwa apa yang merupakan "lebih baik" biasanya subjektif ;-). Harap juga diingat bahwa penggunaan "biasanya" dalam kalimat sebelumnya juga subjektif.
  • Setiap versi dan edisi SQL Server (2005 dan yang lebih baru, saya kira) akan dilakukan.
  • Hanya murni T-SQL: tidak ada yang SQLCLR konyol !! Setidaknya dalam hal menghasilkan data. Membuat direktori dan file akan dilakukan menggunakan SQLCLR. Tapi di sini saya hanya fokus pada menghasilkan nilai-nilai apa yang harus dibuat.
  • T-SQL Multi-statement TVF dianggap prosedural, tidak berbasis set, meskipun di luar mereka menutupi pendekatan prosedural dalam set. Ada saat-saat itu sangat tepat. Ini bukan salah satu dari waktu itu. Sejalan dengan itu, fungsi T-SQL Scalar juga tidak diperbolehkan, tidak hanya karena mereka juga prosedural, tetapi Query Optimizer kadang-kadang menyimpan nilainya dan mengulanginya sedemikian rupa sehingga hasilnya tidak seperti yang diharapkan.
  • T-SQL Inline TVFs (alias iTVFs) adalah okey-dokey karena mereka berbasis-set, dan secara efektif sama dengan menggunakan [ CROSS | OUTER ] APPLY, yang dinyatakan di atas sebagai ok.
  • Eksekusi berulang dari query (ies) harus menghasilkan sebagian besar hasil yang berbeda dari jalankan sebelumnya.
  • Pembaruan Klarifikasi 1: Kumpulan hasil akhir harus dinyatakan sebagai memiliki satu baris untuk setiap simpul Level3 yang berbeda, yang memiliki lintasan penuh mulai dari Level1. Ini berarti bahwa nilai Level1 dan Level2 perlu mengulangi satu atau lebih baris, kecuali dalam kasus hanya ada satu simpul Level2 tunggal yang hanya mengandung satu simpul Level3 tunggal.
  • Pembaruan Klarifikasi 2: Ada preferensi yang sangat kuat untuk setiap node yang memiliki nama atau label, dan bukan hanya angka. Ini akan memungkinkan data uji yang dihasilkan menjadi lebih bermakna dan realistis.

Saya tidak yakin apakah info tambahan ini penting, tetapi untuk berjaga-jaga jika ada konteks, data pengujian terkait dengan jawaban saya pada pertanyaan ini:

Impor file XML ke SQL Server 2012

Meskipun tidak relevan pada titik ini, tujuan akhir menghasilkan hierarki ini adalah membuat struktur direktori untuk menguji metode sistem file rekursif. Level 1 dan 2 akan menjadi direktori, dan Level 3 pada akhirnya akan menjadi nama file. Saya telah mencari-cari (baik di sini maupun melalui Google) dan hanya menemukan satu referensi untuk menghasilkan hierarki acak:

Linux: buat hirarki direktori / file acak

Pertanyaan itu (pada StackOverflow) sebenarnya cukup dekat dalam hal hasil yang diinginkan karena itu juga berusaha membuat struktur direktori untuk pengujian. Tetapi pertanyaan itu (dan jawabannya) terfokus pada scripting Linux / Unix shell dan bukan pada dunia berbasis set yang kita tinggali.

Sekarang, saya tahu cara membuat data acak, dan saya sudah melakukannya untuk membuat konten file sehingga mereka juga dapat menunjukkan variasi. Bagian yang sulit di sini adalah bahwa jumlah elemen dalam setiap set adalah acak, bukan bidang tertentu. Dan , jumlah elemen dalam setiap node harus acak dari node lain pada level yang sama.

Contoh Hirarki

     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

Kumpulan Hasil Contoh Menggambarkan Hirarki Di Atas

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
sumber

Jawaban:

9

( Catatan OP: solusi yang disukai adalah blok kode ke-4 / terakhir)

Menurut saya XML adalah pilihan struktur data yang jelas untuk digunakan di sini.

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

Trik untuk membuat SQL Server menggunakan nilai yang berbeda top()untuk setiap node adalah membuat sub-kueri berkorelasi. N1.N > 0dan N2.N > 0.

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

Dan versi yang sama sekali batal dari 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;

Korelasi N1.N > 0dan N2.N > 0masih penting.

Versi menggunakan tabel dengan 20 nama yang akan digunakan, bukan hanya bilangan bulat.

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
sumber
1
Saya lebih suka versi baru. Ini hampir sama dengan yang saya lakukan pada upaya pertama saya, tetapi untuk beberapa alasan saya tidak bisa menjalankannya TOP(n)dengan benar dalam 2 CROSS APPLYdetik. Tidak yakin apa yang saya lakukan berbeda / salah karena saya menyingkirkan kode itu begitu saya berhasil mengerjakan sesuatu. Saya akan memposting itu segera, sekarang setelah Anda memberikan pembaruan ini. Dan saya telah membersihkan sebagian besar komentar saya di atas.
Solomon Rutzky
Saya baru saja memposting versi saya. Perbedaan utama adalah: 1) karena saya tidak bisa mendapatkan TOP (n) untuk bekerja, saya pergi dengan mendapatkan nelemen melalui kondisi WHERE, dan 2) Saya memiliki namekomponen yang lebih terkontrol daripada pengacakan direktori dan / atau nama file .
Solomon Rutzky
Maaf sudah pergi begitu lama, tapi aku sudah gila sibuk. Namun, saya telah memikirkan hal ini dan tidak dapat memutuskan antara jawaban saya dan versi non-XML Anda. Saya suka kesederhanaan & fleksibilitas Anda, tetapi perlu kemampuan untuk mengembalikan nama yang akan digunakan untuk membuat struktur folder, yang saya miliki. Kemudian saya menyadari bahwa saya telah memperbarui Vlad untuk memiliki tabel pencarian dan BERGABUNG dengannya untuk memberikan hasil yang ideal. Jadi, jika tidak pantas untuk bertanya, bisakah Anda memperbarui milik Anda untuk menyertakan pencarian yang sama? Kemudian ketiga jawaban akan memberikan hasil yang setara (ideal untuk membandingkan ketiga jawaban), dan saya akan menerima jawaban Anda. Apakah itu oke?
Solomon Rutzky
1
@srutzky Saya telah memperbarui jawabannya. Beberapa saat yang lalu, jadi saya harap ini benar dan apa yang Anda cari. Anda tentu saja dapat menambahkan kolom level @Elemetsuntuk mendapatkan serangkaian nama yang berbeda untuk setiap level yang dapat dipilih.
Mikael Eriksson
1
@srutzky jangan khawatir. Saya senang jawabannya sangat membantu bagi Anda.
Mikael Eriksson
6

Itu menarik.

Tujuan saya adalah untuk menghasilkan jumlah level tertentu dengan jumlah acak baris anak per setiap level dalam struktur hierarki yang terhubung dengan benar. Setelah struktur ini siap, mudah untuk menambahkan info tambahan ke dalamnya seperti nama file dan folder.

Jadi, saya ingin membuat tabel klasik untuk menyimpan pohon:

ID int NOT NULL
ParentID int NULL
Lvl int NOT NULL

Karena kita berurusan dengan rekursi, CTE rekursif tampaknya merupakan pilihan alami.

Saya akan membutuhkan daftar angka . Angka dalam tabel harus dimulai dari 1. Harus ada setidaknya 20 angka dalam tabel: 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;

Parameter untuk pembuatan data harus disimpan dalam tabel:

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

Perhatikan, bahwa kueri cukup fleksibel dan semua parameter dipisahkan menjadi satu tempat. Anda dapat menambahkan lebih banyak level jika perlu, cukup tambahkan baris parameter tambahan.

Untuk memungkinkan pembangkitan dinamis seperti itu, saya harus mengingat jumlah baris acak untuk level berikutnya, jadi saya memiliki kolom tambahan ChildRowCount.

Membangkitkan keunikan IDs juga agak rumit. Saya mengkodekan batas 100 baris anak per 1 baris orang tua untuk menjamin IDstidak mengulangi. Ini tentang apa POWER(100, CTE.Lvl). Akibatnya ada celah besar di IDs. Angka itu bisa menjadi MAX(LvlMax), tetapi saya menempatkan 100 konstan dalam query untuk kesederhanaan. Jumlah level bukan kode-keras, tetapi ditentukan oleh @Intervals.

Rumus ini

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

menghasilkan angka floating point acak dalam rentang [0..1), yang kemudian diskalakan ke interval yang diperlukan.

Logika kueri itu sederhana. Itu rekursif. Langkah pertama menghasilkan satu set baris dari level pertama. Jumlah baris ditentukan oleh nomor acak di TOP. Juga, untuk setiap baris ada sejumlah acak baris anak yang disimpan ChildRowCount.

Bagian rekursif digunakan CROSS APPLYuntuk menghasilkan jumlah baris anak tertentu per setiap baris induk. Saya harus menggunakan WHERE Numbers.Number <= CTE.ChildRowCountbukan TOP(CTE.ChildRowCount), karena TOPtidak diperbolehkan di bagian CTE rekursif. Tidak tahu tentang batasan SQL Server ini sebelumnya.

WHERE CTE.ChildRowCount IS NOT NULL menghentikan rekursi.

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;

Hasil (bisa ada hingga 20 + 20 * 10 + 200 * 5 = 1220 baris jika Anda beruntung)

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

Membuat jalur lengkap alih-alih hierarki yang ditautkan

Jika kita hanya tertarik pada Nlevel path lengkap yang dalam, kita dapat menghilangkan IDdan ParentIDdari CTE. Jika kami memiliki daftar kemungkinan nama dalam tabel tambahan Names, mudah untuk memilihnya dari tabel ini dalam CTE. The Namestabel harus memiliki cukup baris untuk setiap tingkat: 20 untuk level 1, 10 untuk level 2, 5 untuk tingkat 3; 20 + 10 + 5 = 35 total. Tidak perlu memiliki rangkaian baris yang berbeda untuk setiap level, tetapi mudah untuk mengaturnya dengan benar, jadi saya melakukannya.

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 Berikut ini adalah permintaan terakhir. Saya membagi FullPathmenjadi FilePathdan 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;

Hasil

+-------------+----------+----------+
|  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
sumber
Pendekatan yang menarik :). Saya suka itu. Demi kelengkapan, dapatkah Anda menambahkan kueri untuk mengisi tabel Angka (dari SQL Fiddle), atau cukup sertakan inline sebagai bagian dari CTE? Maka lebih mudah bagi seseorang untuk hanya menyalin dan menempel. Untuk jawaban ini, dapatkah hasil akhir diekspresikan karena setiap baris menjadi jalur lengkap dari Level1 ke Level3 untuk semua nilai Level3? Saya pikir hanya butuh 2 INNER JOINdetik di final SELECT. Akhirnya, dapatkah nama / label ditugaskan untuk setiap node sehingga mereka bukan hanya angka? Saya akan memperbarui pertanyaan untuk memperjelas kedua poin ini.
Solomon Rutzky
Dari mana nama / label ini berasal? Haruskah saya memiliki tabel 'Nama', yang memiliki 20 baris dan memilih nama darinya? Dengan demikian serangkaian nama yang sama akan muncul di setiap level. Atau haruskah masing-masing level memiliki seperangkat nama tersendiri?
Vladimir Baranov
Saya berpikir bahwa nama dapat berasal dari tabel (temp, real, atau variabel) atau sebaris sebagai bagian dari CTE. Saya awalnya menempatkan mereka di CTE tetapi kemudian memindahkannya ke tabel temp lokal sehingga bagian utama dari kueri akan lebih mudah dibaca di sini. Saya pikir dengan struktur yang Anda miliki, akan cukup mudah untuk memiliki tingkat yang terpisah. Tetapi jika itu hanya satu set 20 yang juga cukup, itu hanya memberikan variasi yang sedikit kurang dalam data uji. Satu-satunya persyaratan yang benar adalah bahwa tidak ada nama yang diulang dalam sebuah node karena akan membuat kesalahan ketika mencoba membuat direktori atau file :)
Solomon Rutzky
1
@rutzky, saya menambahkan varian kedua.
Vladimir Baranov
1
@rutzky, saya berpisah FullPathmenjadi FilePathdan FileName.
Vladimir Baranov
4

Jadi inilah yang saya pikirkan. Dengan tujuan membuat struktur direktori, saya mencari "nama" yang dapat digunakan untuk direktori dan file. Karena saya tidak bisa mendapatkan TOP(n)pekerjaan di CROSS APPLYs (saya pikir saya berusaha untuk mengkorelasikan kueri dengan menggunakan nilai dari orang tua sebagai ndalam TOP(n)tetapi kemudian itu tidak acak), saya memutuskan untuk membuat jenis "angka" tabel yang akan memungkinkan suatu INNER JOINatau WHEREkondisi untuk menghasilkan satu set nelemen hanya dengan mengacak angka dan menetapkannya sebagai WHERE table.Level = random_number. Caranya adalah hanya ada 1 baris untuk Level1, 2 baris untuk Level2, 3 baris untuk Level3, dan seterusnya. Oleh karena itu, menggunakan WHERE LevelID = 3akan memberi saya 3 baris, dan setiap baris memiliki nilai yang dapat saya gunakan sebagai nama direktori.

MEMPERSIAPKAN

Bagian ini awalnya ditentukan sebaris, sebagai bagian dari CTE. Tetapi demi keterbacaan (sehingga Anda tidak perlu menelusuri banyak INSERTpernyataan untuk sampai ke beberapa baris dari permintaan sebenarnya), saya memecahnya menjadi tabel sementara lokal.

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 UTAMA

Untuk Level 1 saya baru saja mengambil [name]nilai sys.objectskarena selalu ada banyak baris di sana. Tetapi, jika saya perlu lebih banyak kontrol atas nama-nama, saya hanya bisa memperluas #Elementstabel untuk memuat level tambahan.

;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 DISESUAIKAN UNTUK MENGHASILKAN SETIAP JALUR, NAMA, dan ISI FILE

Untuk menghasilkan path lengkap untuk file dan isi file, saya membuat SELECT utama dari CTE hanya CTE lain dan menambahkan SELECT utama baru yang memberikan output yang tepat yang hanya perlu masuk ke 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;

KREDIT TAMBAHAN

Meskipun bukan bagian dari persyaratan yang dinyatakan dalam pertanyaan, tujuannya (yang disebutkan) adalah membuat file untuk menguji fungsi Sistem File rekursif dengan. Jadi bagaimana kita mengambil set nama path, nama file, dan konten file hasil ini dan melakukan sesuatu dengannya? Kami hanya membutuhkan dua fungsi SQLCLR: satu untuk membuat folder dan satu lagi untuk membuat file.

Untuk membuat data ini berfungsi, saya memodifikasi utama SELECTCTE yang ditunjukkan langsung di atas sebagai berikut:

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
sumber