Мне нужно создать некоторые тестовые данные, которые включают в себя иерархию. Я мог бы сделать это легко и сделать пару CROSS JOIN
шагов, но это дало бы мне структуру, которая была бы абсолютно однородной / без каких-либо изменений. Это не только кажется скучным, но отсутствие изменений в тестовых данных иногда маскирует проблемы, которые в противном случае были бы обнаружены. Итак, я хочу создать неоднородную иерархию, которая следует этим правилам:
- 3 уровня в глубину
- Уровень 1 случайным образом 5 - 20 узлов
- Уровень 2 - это 1-10 узлов, случайных для каждого узла уровня 1
- Уровень 3 - это 1 - 5 узлов, случайных для каждого узла уровня 2
- Все ветви будут 3 уровня глубиной. Равномерность по глубине в порядке на этом этапе.
- Могут перекрываться имена дочерних узлов на любом данном уровне (т.е. имена дочерних узлов не обязательно должны быть уникальными для всех узлов на одном уровне).
- Термин «случайный» определяется здесь как псевдослучайный, а не однозначно случайный. Это необходимо упомянуть, поскольку термин «случайный» часто используется для обозначения «случайного упорядочения данного набора, который не создает дубликатов». Я принимаю, что random = random, и если число дочерних элементов на каждый узел уровня 1 составляет всего 4, 7 и 8, даже в 20 узлах на уровне 1, у которых потенциальный разброс составляет 1-10 дочерних элементов на каждый из этих узлов, тогда это нормально, потому что это то, что случайное.
- Даже если это можно сделать с помощью вложенных
WHILE
циклов, предпочтительнее найти подход, основанный на множестве. Вообще говоря, генерация тестовых данных не имеет требований к эффективности, которые предъявлялись бы к производственному коду, но использование подхода на основе множеств, вероятно, будет более познавательным и поможет в будущем найти подходы к задачам на основе множеств. Таким образом,WHILE
циклы не исключаются, но могут использоваться, только если невозможен подход на основе множеств. - На основе набора = в идеале один запрос, независимо от CTE, APPLY и т. Д. Таким образом, использование существующей или встроенной таблицы чисел подойдет. Использование WHILE / CURSOR / процедурного подхода не будет работать. Я полагаю, что размещение частей данных во временных таблицах или табличных переменных нормально, только если все операции основаны на множествах, без циклов. Однако, как говорится, подход с одним запросом, вероятно, будет предпочтительнее, чем несколько запросов, если только не будет показано, что подход с несколькими запросами действительно лучше. Пожалуйста, также имейте в виду, что то, что составляет «лучше», обычно субъективно ;-). Также имейте в виду, что использование слова «как правило» в предыдущем предложении также субъективно.
- Подойдет любая версия и выпуск SQL Server (я полагаю, 2005 и новее).
- Только чистый T-SQL: ничего такого глупого в SQLCLR !! По крайней мере, с точки зрения генерации данных. Создание каталогов и файлов будет выполнено с использованием SQLCLR. Но здесь я просто сосредоточен на создании ценностей того, что нужно создавать.
- T-SQL TVF с несколькими утверждениями считаются процедурными, а не основанными на множествах, хотя снаружи они маскируют процедурный подход в наборе. Есть моменты, когда это абсолютно уместно. Это не один из тех времен. В том же духе скалярные функции T-SQL также не разрешены не только потому, что они также являются процедурными, но оптимизатор запросов иногда кэширует их значение и повторяет его так, что вывод не соответствует ожидаемому.
- Встроенные TVF-файлы T-SQL (также известные как iTVF) являются окей-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
источник
TOP(n)
работать корректно в течение 2CROSS APPLY
с. Не уверен, что я сделал по-другому / неправильно, так как я избавился от этого кода, как только я получил что-то еще работающее. Я опубликую это в ближайшее время, теперь, когда вы предоставили это обновление. И я убрал большинство моих комментариев выше.n
элементов через условие WHERE, и 2) у меня естьname
компонент, который более управляем, чем рандомизация имен каталогов и / или файлов ,@Elemets
чтобы получить различный набор имен для каждого уровня на выбор.Это было интересно.
Моя цель состояла в том, чтобы создать заданное количество уровней со случайным количеством дочерних строк на каждом уровне в правильно связанной иерархической структуре. Когда эта структура готова, в нее легко добавить дополнительную информацию, например, имена файлов и папок.
Итак, я хотел создать классическую таблицу для хранения дерева:
Поскольку мы имеем дело с рекурсией, рекурсивный CTE кажется естественным выбором.
Мне понадобится таблица чисел . Числа в таблице должны начинаться с 1. Там должно быть по крайней мере , 20 числа в таблице
MAX(LvlMax)
.Параметры для генерации данных должны храниться в таблице:
Обратите внимание, что запрос довольно гибкий и все параметры разделены в одном месте. При необходимости вы можете добавить больше уровней, просто добавьте дополнительный ряд параметров.
Чтобы такая динамическая генерация была возможной, мне пришлось запомнить случайное количество строк для следующего уровня, поэтому у меня есть дополнительный столбец
ChildRowCount
.Генерирование уникальности
IDs
также несколько сложно. Я жестко прописал ограничение в 100 дочерних строк на 1 родительскую строку, чтобы гарантировать, чтоIDs
они не повторятся. Вот о чем этоPOWER(100, CTE.Lvl)
. В результате есть большие пробелы вIDs
. Это число может бытьMAX(LvlMax)
, но я для простоты поставил константу 100 в запросе. Количество уровней не является жестко заданным, но определяется@Intervals
.Эта формула
генерирует случайное число с плавающей запятой в диапазоне
[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
Результат (если вам повезет, может быть до 20 + 20 * 10 + 200 * 5 = 1220 строк)
Генерация полного пути вместо связанной иерархии
Если нас интересуют только
N
глубокие уровни пути , мы можем опуститьID
иParentID
CTE. Если у нас есть список возможных имен в дополнительной таблицеNames
, их легко выбрать из этой таблицы в CTE. ВNames
таблице должно быть достаточно строк для каждого уровня: 20 для уровня 1, 10 для уровня 2, 5 для уровня 3; 20 + 10 + 5 = 35 всего. Нет необходимости иметь разные наборы строк для каждого уровня, но это легко настроить правильно, поэтому я сделал это.SQL Fiddle Вот последний запрос. Я разделил
FullPath
наFilePath
иFileName
.Результат
источник
INNER JOIN
с в финалеSELECT
. Наконец, можно ли присваивать имена / метки каждому узлу, чтобы они не были просто числами? Я уточню вопрос, чтобы уточнить оба эти момента.FullPath
наFilePath
иFileName
.Итак, вот что я придумала. С целью создания структуры каталогов я искал полезные «имена» для каталогов и файлов. Поскольку я не смог получить
TOP(n)
работу вCROSS APPLY
s (я думаю, что я пытался сопоставить запросы, используя значение из родительского элемента в качествеn
in,TOP(n)
но тогда это не было случайным), я решил создать тип «числа» таблица, которая позволяет условиюINNER JOIN
илиWHERE
производить наборn
элементов просто путем рандомизации числа и указания его какWHERE table.Level = random_number
. Хитрость в том, что есть только 1 строка для уровня 1, 2 строки для уровня 2, 3 строки для уровня 3 и так далее. Следовательно, использованиеWHERE LevelID = 3
даст мне 3 строки, и каждая строка имеет значение, которое я могу использовать в качестве имени каталога.НАСТРОИТЬ
Эта часть была первоначально указана как встроенная, как часть CTE. Но для удобства чтения (чтобы вам не нужно было пролистывать множество
INSERT
операторов, чтобы добраться до нескольких строк реального запроса), я разбил его на локальную временную таблицу.ОСНОВНОЙ ЗАПРОС
Для уровня 1 я просто выбрал
[name]
значения,sys.objects
потому что там всегда много строк. Но если бы мне нужно было больше контролировать имена, я мог бы просто расширить#Elements
таблицу, чтобы она содержала дополнительные уровни.ЗАПРОС, ПРИНЯТЫЙ ДЛЯ ПРОИЗВОДСТВА КАЖДОГО ФАЙЛА, НАИМЕНОВАНИЯ И СОДЕРЖАНИЯ
Чтобы сгенерировать полные пути к файлам и их содержимому, я сделал основной SELECT CTE просто еще одним CTE и добавил новый основной SELECT, который дал надлежащие выходные данные, которые просто должны были войти в файлы.
ДОПОЛНИТЕЛЬНЫЙ КРЕДИТ
Хотя это и не является частью требований, изложенных в вопросе, цель (которая была упомянута) состояла в создании файлов для тестирования рекурсивных функций файловой системы. Итак, как нам взять этот результирующий набор имен путей, имен файлов и содержимого файлов и что-то с этим сделать? Нам просто нужны две функции SQLCLR: одна для создания папок и одна для создания файлов.
Чтобы сделать эти данные функциональными, я изменил основную
SELECT
часть CTE, показанную непосредственно выше, следующим образом:источник