Створіть ієрархію декількох рівнів, де кожен вузол має випадкову кількість дітей


16

Мені потрібно створити деякі тестові дані, які передбачають ієрархію. Я міг би зробити це легко і зробити пару CROSS JOINс, але це дало б мені структуру, яка є повністю однорідною / без будь-яких змін. Це не тільки здається тьмяним, але відсутність змін у тестових даних іноді маскує проблеми, які інакше були б знайдені. Отже, я хочу створити неоднорідну ієрархію, яка відповідає цим правилам:

  • 3 рівні глибокі
    • Рівень 1 - випадковим чином 5 - 20 вузлів
    • Рівень 2 - це 1 - 10 вузлів, випадково на кожен вузол 1 рівня
    • Рівень 3 - це 1 - 5 вузлів, випадкових для кожного вузла рівня 2
  • Всі гілки будуть на 3 рівні глибиною. Рівномірність у глибині в цьому моменті нормальна.
  • Імена дочірніх вузлів можуть бути перекриті на будь-якому заданому рівні (тобто імена дочірніх вузлів не повинні бути унікальними для всіх вузлів на одному рівні).
  • Термін "випадковий" тут визначається як псевдовипадковий, а не однозначно випадковий. Це потрібно зазначити, оскільки термін "випадковий" часто використовується для позначення "випадкового впорядкування заданого набору, який не дає дублікатів". Я приймаю це випадкове = випадкове, і якщо кількість дітей у кожному вузлі рівня 1 становить лише 4, 7 та 8, навіть через 20 вузлів на рівні 1, що потенційне поширення становить 1 - 10 дітей на кожен із цих вузлів, то це нормально, тому що це випадковість.
  • Незважаючи на те, що це можна зробити досить легко за допомогою вкладених WHILEциклів, перевага - знайти підхід на основі набору. Взагалі кажучи, генерування тестових даних не вимагає вимог до ефективності, які матиме виробничий код, але зйомка для підходу, заснованого на наборі, швидше за все буде більш навчальною та допоможе у пошуку в майбутньому підходів до проблем. Таким чином, WHILEпетлі не виключаються, але їх можна використовувати лише в тому випадку, якщо підхід, заснований на наборі, неможливий.
  • На основі набору = в ідеалі один запит, незалежно від CTE, APPLY тощо. Отже, добре використовувати існуючу або вбудовану таблицю цифр. Використання процедурного підходу WHILE / CURSOR / не буде працювати. Я припускаю, що послідовно розміщувати частини даних у тимчасових таблицях або змінних таблиць, до тих пір, поки всі операції на основі набору, без циклів. Однак, якщо говорити, підхід із одним запитом, ймовірно, буде надавати перевагу над декількома запитами, якщо тільки не буде показано, що підхід із декількома запитами насправді кращий. Будь ласка, майте на увазі, що те, що є "кращим", як правило, є суб'єктивним ;-). Також майте на увазі, що використання "типово" в попередньому реченні також є суб'єктивним.
  • Будь-яка версія та видання SQL Server (я думаю, 2005 та новіші) будуть.
  • Тільки чистий T-SQL: жоден з цих дурних SQLCLR речей !! Принаймні з точки зору генерації даних. Створення каталогів та файлів буде здійснюватися за допомогою SQLCLR. Але тут я просто зосереджуюсь на генеруванні цінностей того, що потрібно створити.
  • T-SQL Multi-statement TVF вважається процедурним, не заснованим на множинах, хоча зовні вони маскують процедурний підхід у наборі. Бувають випадки, коли це абсолютно доречно. Це не один із тих часів. У рамках цих же ліній функцій сканування T-SQL також не дозволено не лише тому, що вони також є процедурними, але Оптимізатор запитів іноді кешує їх значення і повторює його таким чином, що вихід не буде таким, як очікувалося.
  • TF-SQL Inline TVFs (він же iTVFs) є okey-dokey, оскільки вони засновані на наборі, і фактично такий же, як і використання [ CROSS | OUTER ] APPLY, про що було сказано вище, що це нормально.
  • Повторне виконання запиту (ів) має давати здебільшого різний результат від попереднього запуску.
  • Оновлення уточнення 1: Кінцевий набір результатів повинен бути виражений як один рядок для кожного окремого вузла рівня 3, який має повний шлях, починаючи з рівня 1. Це означає, що значення Level1 та Level2 обов'язково повторяться через один або кілька рядків, за винятком випадків, коли існує лише один вузол Level2, що містить лише один вузол Level3.
  • Оновлення роз'яснення 2: Є дуже сильний перевага для кожного вузла, який має ім'я або мітку, а не лише число. Це дозволить отримати отримані дані тесту більш значущі та реалістичні.

Я не впевнений, що ця додаткова інформація має значення, але на всякий випадок, якщо це допомагає мати певний контекст, дані тесту стосуються моєї відповіді на це питання:

Імпортуйте XML-файли в SQL Server 2012

Хоча на даний момент це не актуально, кінцевою метою створення цієї ієрархії є створення структури каталогів для тестування методів рекурсивної файлової системи. Рівні 1 і 2 будуть каталогіми, а рівень 3 в кінцевому підсумку буде ім'ям файлу. Я шукав навколо (і тут, і через Google) і знайшов лише одне посилання на створення випадкової ієрархії:

Linux: створити випадкову ієрархію каталогу / файлів

Це запитання (на StackOverflow) насправді досить близьке з точки зору бажаного результату, оскільки воно також прагне створити структуру каталогів для тестування. Але це питання (і відповіді) орієнтовано на сценарії оболонок Linux / Unix, а не стільки на основі світу, в якому ми живемо.

Тепер я знаю, як генерувати випадкові дані, і вже роблю це, щоб створити вміст файлів, щоб вони також могли показувати варіанти. Тут складно, що кількість елементів у кожному наборі є випадковою, а не конкретним полем. І кількість елементів у кожному вузлі має бути випадковою від інших вузлів на тих самих рівнях.

Приклад Ієрархія

     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

Приклад набору результатів Опис ієрархії вище

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

Відповіді:


9

( Примітка ОП: кращим рішенням є четвертий / остаточний блок коду)

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

Трюк, щоб змусити SQL Server використовувати різні значення top()для кожного вузла, полягає в тому, щоб зробити підзапроси співвіднесеними. N1.N > 0і N2.N > 0.

Згладжування 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);

І версія абсолютно недійсна у 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;

Кореляція N1.N > 0і N2.N > 0досі важлива.

Версія, що використовує таблицю з 20 іменами, яку слід використовувати замість просто цілих чисел.

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

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

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

1
Мені більше подобається нова версія. Це майже те саме, що я придумав у своїй першій спробі, але чомусь я не зміг змусити TOP(n)правильно працювати протягом 2 CROSS APPLYс. Не впевнений, що я зробив інакше / неправильно, оскільки я позбувся цього коду, коли я щось інший працював. Я опублікую це незабаром, тепер, коли ви надали це оновлення. І я очистив більшість своїх коментарів вище.
Соломон Руцький

Я щойно опублікував свою версію. Основні відмінності: 1) оскільки я не міг змусити TOP (n) працювати, я перейшов до отримання nелементів через умову WHERE, і 2) у мене є nameкомпонент, який більш керований, ніж рандомізація імен каталогів та / або імен файлів .
Соломон Руцький

Вибачте, що відсутня так довго, але я божеволіла зайнята. Все-таки я думав над цим і не можу визначитися між моєю відповіддю та вашою не-XML-версією. Мені подобається ваша простота та гнучкість, але мені потрібна здатність повертати імена, щоб використовувати для створення структури папок, яку має моя. Тоді я зрозумів, що Влад оновив його, щоб мати таблицю пошуку і ПРИЄДНАЙТЕСЬ до нього, щоб дати ідеальний вихід. Тож, якщо не доцільно запитати, чи можете ви оновити свій, щоб включити той самий пошук? Тоді всі 3 відповіді дадуть рівноцінні результати (ідеально підходить для порівняння всіх 3), і я б прийняв ваш. Це нормально?
Соломон Руцький

1
@srutzky Я оновив відповідь. Був якийсь час тому, сподіваюся, що я зрозумів це правильно і що ви там, де шукаєте. Звичайно, ви можете додати стовпчик рівня, @Elemetsщоб отримати різний набір імен для кожного рівня, на який можна вибрати.
Мікаел Ерікссон

1
@srutzky не хвилюйся. Я радий, що відповідь була для вас корисною.
Мікаель Ерікссон

6

Це було цікаво.

Моєю метою було генерувати задану кількість рівнів із випадковою кількістю дочірніх рядків на кожному рівні в правильно пов'язаній ієрархічній структурі. Як тільки ця структура готова, можна легко додати в неї додаткову інформацію, як імена файлів і папок.

Отже, я хотів створити класичну таблицю для зберігання дерева:

ID int NOT NULL
ParentID int NULL
Lvl int NOT NULL

Оскільки ми маємо справу з рекурсією, рекурсивний CTE видається природним вибором.

Мені знадобиться таблиця цифр . Числа в таблиці повинні починатися з 1. Там повинно бути по крайней мере , 20 числа в таблиці 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;

Параметри для генерації даних слід зберігати в таблиці:

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

Зауважте, що запит досить гнучкий, і всі параметри розділені на одне місце. Ви можете додати більше рівнів, якщо потрібно, просто додайте додатковий рядок параметрів.

Щоб зробити таке динамічне покоління можливим, я повинен був запам'ятати випадкову кількість рядків для наступного рівня, тому у мене є додатковий стовпець ChildRowCount.

Створення унікального IDs також дещо складне. Я жорстко кодував обмеження в 100 дочірніх рядків на 1 батьківський ряд, щоб гарантувати, що IDsце не повториться. Про це POWER(100, CTE.Lvl)йдеться. В результаті виникають великі прогалини IDs. Це число може бути рівним MAX(LvlMax), але я ставлю постійні 100 у запиті для простоти. Кількість рівнів не жорстко закодована, але визначається @Intervals.

Ця формула

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

генерує випадкове число з плаваючою точкою в діапазоні [0..1), яке потім масштабується до необхідного інтервалу.

Логіка запиту проста. Він є рекурсивним. Перший крок генерує набір рядків першого рівня. Кількість рядків визначається випадковим числом у TOP. Також для кожного ряду є окрема випадкова кількість дочірніх рядків, що зберігаються в ChildRowCount.

Рекурсивна частина використовує CROSS APPLYдля генерування заданої кількості дочірніх рядків для кожного батьківського рядка. Мені довелося використовувати WHERE Numbers.Number <= CTE.ChildRowCountзамість TOP(CTE.ChildRowCount), оскільки TOPце не дозволено в рекурсивній частині CTE. Раніше не знали про це обмеження SQL Server.

WHERE CTE.ChildRowCount IS NOT NULL зупиняє рекурсію.

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;

Результат (якщо пощастить, може бути до 20 + 20 * 10 + 200 * 5 = 1220 рядків)

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

Створення повного шляху замість пов'язаної ієрархії

Якщо нас цікавлять лише Nглибини повного шляху , ми можемо опустити IDі ParentIDз CTE. Якщо в додатковій таблиці є список можливих імен Names, їх легко вибрати з цієї таблиці в CTE. У Namesтаблиці повинно бути достатньо рядків для кожного рівня: 20 для рівня 1, 10 для рівня 2, 5 для рівня 3; 20 + 10 + 5 = 35 загалом. Не обов’язково мати різні набори рядків для кожного рівня, але легко його правильно встановити, тому я це зробив.

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 Скрипка Ось остаточний запит. Я розділив FullPathна FilePathі 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;

Результат

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

Цікавий підхід :). Мені це подобається. Для повноти ви можете додати запит для заповнення таблиці Numbers (з SQL Fiddle) або просто включити цей рядок як частину CTE? Тоді комусь легше просто скопіювати та вставити. Чи можна в цій відповіді виражати підсумковий результат, коли кожен рядок є повним шляхом від рівня 1 до рівня 3 для всіх значень рівня 3? Я думаю, що це займе всього 2 INNER JOINс у фіналі SELECT. Нарешті, чи можуть імена / мітки присвоювати кожному вузлу, щоб вони не були просто числами? Я оновлю питання, щоб уточнити обидва ці пункти.
Соломон Руцький

Звідки беруться ці назви / етикетки? Чи повинен я мати таблицю "Імена", яка містить 20 рядків і вибрати назву з неї? Таким чином, однаковий набір з'явиться на кожному рівні. Або кожен рівень має свій окремий набір імен?
Володимир Баранов

Я думаю, що назви можуть походити з таблиці (тимчасової, реальної чи змінної) або вбудованої як частини CTE. Спочатку я помістив їх у CTE, але потім перемістив їх до локальної темп-таблиці, щоб основна частина запиту була більш читаною тут. Я думаю, що у вас є така структура, щоб було досить просто мати окремий рівень. Але якщо цього було б достатньо лише одного набору з 20, це просто забезпечить дещо менші зміни в тестових даних. Єдиною справжньою вимогою є те, що жодне ім'я не повторюється у вузлі, оскільки це буде помилка при спробі створення каталогів або файлів :).
Соломон Руцький

1
@srutzky, я додав другий варіант.
Володимир Баранов

1
@srutzky, я розбився FullPathна FilePathі FileName.
Володимир Баранов

4

Тож ось що я придумав. З метою створення структури каталогів я шукав корисні "імена" для каталогів та файлів. Оскільки мені не вдалося отримати TOP(n)роботу в CROSS APPLYs (я думаю, що я спробував співвіднести запити, використовуючи значення батьківського значення як nв, TOP(n)але тоді це було не випадково), я вирішив створити тип "цифр" таблиця, яка дозволила б INNER JOINабо WHEREумові створити набір nелементів, просто рандомізувавши число та вказавши його як WHERE table.Level = random_number. Хитрість полягає в тому, що є лише 1 рядок для рівня 1, 2 рядки для рівня 2, 3 рядки для рівня 3 тощо. Отже, за допомогою WHERE LevelID = 3я отримаю 3 рядки, і кожен рядок має значення, яке я можу використовувати як ім'я каталогу.

НАСТРОЙКА

Ця частина була спочатку вказана в рядку, як частина CTE. Але задля читабельності (щоб вам не потрібно прокручувати безліч INSERTвисловлювань, щоб потрапити до кількох рядків реального запиту), я розбив це до місцевої тимчасової таблиці.

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;

ОСНОВНИЙ ЗАПИТ

Для першого рівня я просто схопив [name]значення, sys.objectsоскільки там завжди багато рядків. Але, якщо мені знадобиться більше контролю над іменами, я можу просто розширити #Elementsтаблицю, щоб вона містила додаткові рівні.

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

ЗАПИТАННЯ, ПОДАЄТЬСЯ ДЛЯ ВИЗНАЧЕННЯ КОЖНОГО ФАЙЛУ ПАТ, ІМЕНІ ТА ЗМІСТ

Для того, щоб генерувати повний шлях до файлів та вмісту файлу, я зробив основний SELECT CTE просто ще один CTE і додав новий головний SELECT, який дав належні виходи, які просто потрібно перейти у файли.

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;

ДОПОМОГА КРЕДИТ

Хоча це не є частиною вимог, зазначених у питанні, мета (про яку згадувалося) полягала у створенні файлів для тестування рекурсивних функцій Файлової системи. То як ми можемо взяти цей набір результатів назви шляхів, імен файлів та вмісту файлів і зробити щось із цим? Нам просто потрібні дві функції SQLCLR: одна для створення папок і одна для створення файлів.

Для того, щоб зробити ці дані функціональними, я змінив основні SELECTCTE, показані вище, наступним чином:

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;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.