![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Иерархические структуры [MSSQL]
Хаванагил © (2007-03-22 14:58:00)например, структура подразделений в организации..
организация, в ней отделы, в отделах - сектора и т.д.. как это хранить в базе?
если в виде записей, которые ссылаются на записи той же таблицы - можно, но тогда ссылка должна мочь быть null (для самого верхнего уровня, над которым уже ничего нет), но тогда каскадное удаление не получится (поправьте если всё же его можно устроить, у меня соответствующие опции в properties связи были не активные) и придется использовать рекурсию.. то же самое с введением таблицы связи (подразделение, вышестоящее подразделение)..
так вот...
есть ли способ чтобы без рекурсии обойтись?
azamatufa (2007-04-11 11:56:00) [1]Q - это ADOQuery. При помощи него делаю запросы (в частности, определяю есть ли дети)
Jan1 (2007-04-11 10:08:00) [2]
> 1. Close
нет это не убирай.
Jan1 (2007-04-11 10:07:00) [3]
> Мои мысли такие: собрать все необходимые для удаления ID
> и одним запросом их удалить.
лишнее. да и на MS SQL не сработает :)
> Q.Close;
> Q.SQL.Clear;
> Q.SQL.Add('SELECT * FROM Dep WHERE ParentID = ' + IntToStr(ID));
>
> Q.Open;
> Result := Q.RecordCount > 0;
1. Close, Clear убрать.
2. Используй параметры.
3. Проверку на пустой датасет лучше делать через IsEmpty.
> while not Q.Eof do begin
> Make_Del_List(Q.FieldByName('ID').AsInteger);
1. Что такое Q?
2. Где next?
Сергей М. (2007-04-11 08:33:00) [4]
> но тогда ссылка должна мочь быть null (для самого верхнего
> уровня, над которым уже ничего нет)
Кто сказал, что "должна" ?
Что мешает сделать эту ссылку, например, равной первичному ключу этой же записи ?
azamatufa (2007-04-11 07:42:00) [5]Люди добрые! Здравствуйте! Я к Вам за помощью. Тут было сказано, что надо полюбить рекурсию. Вот я хочу начать это дело.
В общем имеется традиционная база - структура отделов организации.
ID Name ParentID
1 Экономики 0
2 Финансов 0
3 подЭконом 1
4 подФин 2
.........
Хочу написать рекурсию, которая бы удаляла Node и всех его детей и внуков и правнуков ....
Мои мысли такие: собрать все необходимые для удаления ID и одним запросом их удалить.
Вот попытка сбора ID
// -----------------------------
procedure TFormTree.Make_Del_List(ID:integer);
begin
if HasChild(ID) then begin
while not Q.Eof do begin
Make_Del_List(Q.FieldByName('ID').AsInteger);
end;
end;
List.Add(IntToStr(ID));
end;
// -------- есть ли дети ----------------------
function TFormTree.HasChild(ID:integer):boolean;
begin
Q.Close;
Q.SQL.Clear;
Q.SQL.Add('SELECT * FROM Dep WHERE ParentID = ' + IntToStr(ID));
Q.Open;
Result := Q.RecordCount > 0;
end;
но она не так работает.. порой пропускает детей... если у одного детя есть внук..... вот (((
ANB (2007-03-26 13:15:00) [6]
> Bless © (23.03.07 18:11) [36]
Похоже были организованы деревья в первых версиях 1С (до 6-ки точно).
Ничего - работоспособный вариант, но проблемы с количеством уровней.
И ограничение на количество цифр в ID ветки дерева.
Тока Cnt там нафиг не нужно.
Я в клиппере отказался от этой идеи - обычные парентовые деревья намного гибче.
MsGuns (2007-03-24 14:00:00) [7]Да тут критиковать-то нечего..
Для "классической" древесины с макс.вложенностью 5-6 можно использовать, правда будут хитромудрые запросы, которые к тому же будут тормозить.
Для неограниченных деревьев и, тем более, графов, неприемлима
Bless (2007-03-23 18:11:00) [8]можно для хранения данных использовать строковое поле.
Например, пусть есть такое дерево
Организация
Отдел1
Отдел2
Подотдел21
Отдел3
Тогда таблица будет иметь поля
create table departments(
name varchar(100), название отдела
hcode varchar(50), иерархический код этой записи
cnt int количество непосредственных подузлов(т.е. дети детей не считаются)
Содержимое таблицы:
Организация '00', 3
Отдел1 '0000', 0
Отдел2 '0001', 1
Подотдел21 '000100', 0
Отдел3 '0002', 0
Поля hcode и cnt обслуживают иерархические связи.
hcode - иерархический код записи
Поле cnt - это количество уже существующих у узла потомков.
Нужно для расчета hcode у новых потомков.
На каждый уровень иерархии выделяем два символа (можно больше, дело хозяйское).
И символы в моем примере ограничены цифрами (что не обязательно),
т.е. максимальное количество детей (именно детей, т.е. НЕПОСРЕДСТВЕННЫХ потомков)
у каждого родителя не будет превышать 100 штук.
hcode нового узла формируется как: код родителя + "свой код".
"Свой код" формируется на основании поля cnt. Например, если у родительской записи
hcode = '1234', cnt = 15, то у нового потомка hcode='123416'.
Думаю, идея понятна.
Имеет недостатки.
В частности, вставка-удаление записи затрагивает еще одну запись (родительскую, ведь надо cnt у родителя уменьшить/увеличить). Соответственно, чтобы обеспечить корректность этих операций, нужно приложить усилия по написанию дополнительного кода.
Вероятно, не годится для многотысячных иерархий.
Зато наверное все "деревянные" задачи решаются без рекурсий.
Выбрать ВСЕХ потомков записи с hcode=@hcode:
SET @len=LEN(@hcode)
SELECT * FROM departments WHERE hcode LIKE @hcode+'%' AND LEN(hcode)>@len
Удаление всех потомков
BEGIN TRAN
DELETE FROM departments
WHERE hcode LIKE '@hcode+'%' AND LEN(hcode)>@len
SELECT @err = @@ERROR, @rc=@@ROWCOUNT
IF @err<>0 GOTO FAIL_EXIT
UPDATE departments SET cnt=cnt-@rc WHERE hcode=@hcode
SET @err = @@ERROR
IF @err<>0 GOTO FAIL_EXIT
COMMIT TRAN
RETURN 0
FAIL_EXIT:
ROLLBACK TRAN
RETURN @err
Заранее приношу извинение за возможные опечатки, писал с головы, нигде не проверял.
В общем-то, больше хотелось бы услышать аргументированную критику подобного подхода от людей плотно занимающихся деревьями. Хотя если кому пригодится - тоже хорошо.
Jan (2007-03-23 12:49:00) [9]
> Ega23 © (22.03.07 16:39) [22]
можно проще и быстрее.
declare @ParentID [uniqueidentifier]
set @ParentID = 'bla-bla-bla'
declare @Stack table ([id] [uniqueidentifier])
declare @Result table ([id] [uniqueidentifier])
insert into @Stack
select [ID] from [TreeTable]
where [ParentID] = @ParentID
while (exists(select * from @Stack))
begin
set @ParentID = (select top 1 [id] from @Stack)
insert into @Stack
select [ID] from [TreeTable]
where [ParentID] = @ParentID
insert into @Result
select @ParentID
delete from @Stack
where [id] = @ParentID
end
select * from @Result
Jan (2007-03-23 11:40:00) [10]
> Ega23 © (22.03.07 16:39) [22]
идея классная, но зачем здесь:
@TreeLevel?
@Type?
Ega23 (2007-03-22 19:13:00) [11]
> Использование CTE в 2005 обходит это ограничение.
Мы только-только щупать ExpressEdition начали...
Empleado (2007-03-22 18:51:00) [12]
> MSSQL НЕ ПОДДЕРЖИВАЕТ стек вызовов более 32
Использование CTE в 2005 обходит это ограничение.
Ega23 (2007-03-22 17:30:00) [13]
> до боли мой код напоминает... ) ну sniknik, так sniknik
Да, блин, не помню я. Может и твой. Если твой - сейчас комент добавлю (последнее время уже взял за правило - если не мой код, то комент, чей)
clickmaker (2007-03-22 17:21:00) [14]
> [29] Reindeer Moss Eater © (22.03.07 17:19)
чтобы понять рекурсию, надо сначала понять рекурсию...
Reindeer Moss Eater (2007-03-22 17:19:00) [15]Полюбить рекурсию и жить спокойно до старости.
clickmaker (2007-03-22 17:13:00) [16]
> Идею, вроде, sniknik как-то подкинул
до боли мой код напоминает... ) ну sniknik, так sniknik
Хаванагил (2007-03-22 17:03:00) [17]
> Ega23 © (22.03.07 16:44) [26]
да, 32 вполне хватит! у меня где-то 6-8 уровней в дереве етом выходит..
спасибо всем, очень интересно! )
Ega23 (2007-03-22 16:44:00) [18]Да, вот ещё что. MSSQL НЕ ПОДДЕРЖИВАЕТ стек вызовов более 32.
Хотя я для своих задач так и не смог придумать такого дерева, где уровень вложенность был бы больше 32...
Хаванагил (2007-03-22 16:41:00) [19]
> Ega23 © (22.03.07 16:39) [22]
ууу...
спасибо, сохраню пока - дома разберусь))
ANB (2007-03-22 16:40:00) [20]
> Но рекурсией она быть не перестанет. Кто бы её не крутил.
> .. :)
Вообще то на клиппере я реализовал обход дерева без рекурсий (было много вложенностей и проблемы со стеком).
Так что не факт, что оракл тоже рекурсией пользуется.