Когда родитель назначается случайным образом из ранее вставленных строк, нет контроля над высотой дерева (количеством уровней) и уровнем заполнения уровней, что может быть нежелательным в некоторых сценариях.
Возможно, более удобно заполнить дерево уровнем данных по уровню.
Вспомогательная таблица функция берется для создания порядковые номера, используя крестик Ицик присоединился метод КТР (см, например, here об этом)
create function ftItziksCJCTE
(
@cnt int
)
returns table as
return
(
WITH
E00(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E00 a, E00 b),
E04(N) AS (SELECT 1 FROM E02 a, E02 b),
E08(N) AS (SELECT 1 FROM E04 a, E04 b),
E16(N) AS (SELECT 1 FROM E08 a, E08 b),
E32(N) AS (SELECT 1 FROM E16 a, E16 b),
E(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
select N from E where N <= @cnt
)
Простая таблица для контроля распределения элементов в дереве:
create table #TreeLevels
(
LevelNo int identity(1, 1) not NULL,
MinElements int not NULL,
MaxElements int not NULL,
primary key clustered (LevelNo)
)
Распределение образцов:
insert into #TreeLevels values (7, 10)
insert into #TreeLevels values (70, 100)
insert into #TreeLevels values (700, 1000)
Даст нам что-то вроде 7 до 10 элементов с ParentID = NULL, каждый из которых имеет что-то вроде 70 до 100 элементов и т.д. С общим числом элементов 343000 до 1000000.
Или другого распределения:
insert into #TreeLevels values (1, 1)
insert into #TreeLevels values (9, 15)
insert into #TreeLevels values (10, 12)
insert into #TreeLevels values (9, 15)
insert into #TreeLevels values (10, 12)
insert into #TreeLevels values (9, 15)
insert into #TreeLevels values (10, 12)
Значение будет одним корневым элементом с чем-то между 9 и 15 дочерних элементов, каждый из которых имеет что-то вроде 10 до 12 элементов и т.д.
Затем дерево может заполняться уровень по уровню:
declare @levelNo int, @eMin int, @eMax int
create table #Inserted (ID int not NULL, primary key nonclustered (ID))
create table #Inserted2 (ID int not NULL, primary key nonclustered (ID))
set @levelNo = 1
while 1=1
begin
select @eMin = MinElements, @eMax = MaxElements from #TreeLevels where LevelNo = @levelNo
if @@ROWCOUNT = 0
break
if @levelNo = 1
begin
insert into TestTree (ParentID)
output inserted.ID into #Inserted (ID)
select NULL from ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0))
end
else
begin
if exists (select 1 from #Inserted)
begin
insert into TestTree (ParentID)
output inserted.ID into #Inserted2 (ID)
select
I.ID
from
#Inserted I
cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0)) F
truncate table #Inserted
end
else
begin
insert into TestTree (ParentID)
output inserted.ID into #Inserted (ID)
select
I.ID
from
#Inserted2 I
cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0)) F
truncate table #Inserted2
end
end
set @levelNo = @levelNo + 1
end
Однако нет никакого контроля над точным количеством элементов, которое будет содержать дерево, а узлы листьев находятся только на последнем уровне. Было бы неплохо иметь дополнительный уровень контроля уровня (процент узлов на том же уровне, который будет иметь детей).
create table #TreeLevels
(
LevelNo int identity(1, 1) not NULL,
MinElements int not NULL,
MaxElements int not NULL,
PopulatedPct float NULL,
primary key clustered (LevelNo)
)
распределение образца:
insert into #TreeLevels values (1, 1, NULL)
insert into #TreeLevels values (9, 15, NULL)
insert into #TreeLevels values (10, 12, NULL)
insert into #TreeLevels values (9, 15, 80)
insert into #TreeLevels values (10, 12, 65)
insert into #TreeLevels values (9, 15, 35)
insert into #TreeLevels values (10, 12, NULL)
NULL, для PopulatedPct процента рассматривается как 100%. PopulatedPct контролирует популяцию на следующем уровне и должен быть взят с предыдущего уровня во время цикла. Также он не имеет значения для последней строки в #TreeLevels.
Теперь мы можем циклически контролировать уровни, учитывающие PopulationPct.
declare @levelNo int, @eMin int, @eMax int
create table #Inserted (ID int not NULL, primary key nonclustered (ID))
create table #Inserted2 (ID int not NULL, primary key nonclustered (ID))
set @levelNo = 1
while 1=1
begin
select @eMin = MinElements, @eMax = MaxElements from #TreeLevels where LevelNo = @levelNo
if @@ROWCOUNT = 0
break
if @levelNo = 1
begin
insert into TestTree (ParentID)
output inserted.ID into #Inserted (ID)
select NULL from ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0))
end
else
begin
declare @pct float
select @pct = PopulatedPct from #TreeLevels where LevelNo = @levelNo - 1
if exists (select 1 from #Inserted)
begin
if (@pct is NULL)
insert into TestTree (ParentID)
output inserted.ID into #Inserted2 (ID)
select
I.ID
from
#Inserted I
cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0)) F
else
insert into TestTree (ParentID)
output inserted.ID into #Inserted2 (ID)
select
I.ID
from
(select top (@pct) PERCENT ID from #Inserted order by rand(checksum(newid()))) I
cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0)) F
truncate table #Inserted
end
else
begin
if (@pct is NULL)
insert into TestTree (ParentID)
output inserted.ID into #Inserted (ID)
select
I.ID
from
#Inserted2 I
cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0)) F
else
insert into TestTree (ParentID)
output inserted.ID into #Inserted (ID)
select
I.ID
from
(select top (@pct) PERCENT ID from #Inserted2 order by rand(checksum(newid()))) I
cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0)) F
truncate table #Inserted2
end
end
set @levelNo = @levelNo + 1
end
По-прежнему отсутствует контроль над точным количеством элементов, но достигается лучший контроль над формой дерева.
«SELECT TOP 1 ID FROM Test2 ORDER BY NEWID()« <- не будет очень эффективным ... –
@MitchWheat: есть ли более быстрый способ получить случайную строку из таблицы? – OzrenTkalcecKrznaric
Да: http://mitch-wheat.blogspot.com.au/2011/08/t-sql-generating-random-numbers-random.html –