КМВ, Пятигорск
Ставропольский край • Ставрополь • КМВ • Пятигорск • Ессентуки • Кисловодск • Железноводск • Минводы • Георгиевск
Региональный информационный портал Кавминвод
---- Дерево каталогов Nested Sets (вложенные множества) ч.2 (1)
Забыли пароль?



  Сделать стартовой  Добавить в избранное     Поиск  Обратная связь  Карта сайта  Версия для печати

Спектакль «Казанова — Ars Vivendi». Сергей Безруков Концертный Зал им. Ф.И. Шаляпина, 20 апреля 2024 19:00 сб

 
Владимир Кузьмин
 
 
 



Надежда Кадышева
 


На первой полосе | Авто (мир автомобилей) | Hi-Tech | Деньги. Инвестиции. Кредиты | Работа и карьера | Семья и дом | Спорт, туризм и отдых | Я ль на свете всех милее ? | Мужикам всех стран... | Недвижимость | Наука и образование | Исследования и обзоры | Странности и причуды | ТОПы (супер-рейтинг) | С юмором по жизни...

01.03.2006

Дерево каталогов Nested Sets (вложенные множества) ч.2 (1)

В предыдущей статье мы рассмотрели теорию управления Nested Sets. Теперь попробуем собрать на её основе модуль Perl для работы.

Для начала, определим, сам объект:

package MyModule::NestedSets;
use strict; use warnings; use Carp;
require Exporter;
our @ISA = qw(Exporter);

sub new {
my $self = shift;
$self = {
id => 'id',
left => 'left_key',
right => 'right_key',
level => 'level',
table => undef,
DBI => undef,
};
bless $self;
return $self;
}

Где:

  • $self->{'id'} - имя поля идентификатора узла таблицы;
  • $self->{'left'} - имя поля левого ключа узла таблицы;
  • $self->{'right'} - имя поля правого ключа узла таблицы;
  • $self->{'level'} - имя поля уровня узла таблицы;
  • $self->{'table'} - имя таблицы;
  • $self->{'DBI'} - ссылка на объект DBI модуля - подключение к;

Пока все тривиально и просто, в объекте описаны имена полей и таблицы, в которой хранится наше дерево каталогов.

Теперь нужно определить какие методы мы будем применять к объекту.

  • создание узла;
  • удаление узла;
  • перемещение узла, подразделяющееся на:
    • установка узла в подчинение другому;
    • установка узла рядом с другим (впереди - за ним);
    • изменение уровня узла (выше, ниже на уровень);

Так же, существует реальная потребность в том, что бы хранить несколько деревьев в одной таблице (далее по тексту: "мультидерево"). Например, если существует два и более раздельных каталогов товаров. Для определения того, одно или несколько разных деревьев в таблице добавим в наш объект еще два свойства:

...
$self = {
...
type => 'N',
multi => 'class',
};
...

Где:

  • $self->{'type'} - Флаг определения статуса таблицы (N - одно дерево, M - несколько деревьев);
  • $self->{'multi'} - имя поля идентификатора дерева таблицы;

Объявление объекта можно производить так:

...
use MyModule::NestedSets;
my $nested = new
MyModule::NestedSets;
$nested->{'table'} = 'catalog_category';
$nested->{'type'} = 'M';
$nested->{'DBI'} = $dbh; # $dbh должен быть уже определен как класс DBI
...

Или, дабы упростить объявление:

...
use MyModule::NestedSets;
my $nested = new
MyModule::NestedSets {table=>'catalog_category', type=>'multi', DBI=>$dbh};
...

Но при этом в процедуре - new модуля, нужно дополнительно обработать данные:

sub new {
my ($self, $common) = @_;
$self = {
...
};
$self->{'type'} = $$common{'type'} && $$common{'type'} eq 'multi' ? 'M' : 'N';
$self->{'left'} = $$common{'left'} || $$common{'left'};
$self->{'right'} = $$common{'right'} || $$common{'right'};
$self->{'level'} = $$common{'level'} || $$common{'level'};
$self->{'multi'} = $$common{'multi'} || $$common{'multi'};
$self->{'table'} = $$common{'table'} || $$common{'table'};
$self->{'DBI'} = $$common{'DBI'} || $$common{'DBI'};
bless $self;
return $self;
}

Несмотря, на то, что возможно переопределение имен полей таблицы (правый - левый ключ, идентификатор узла и идентификатор дерева), я стараюсь использовать одни и те же имена для всех таблиц, что бы не запутаться, тогда определять, по сути, нужно будет только имя таблицы.

Сама таблица будет выглядеть так:

CREATE TABLE `catalog_category` (
`id` int(11) NOT NULL auto_increment,
`left_key` int(11) NOT NULL default '0',
`right_key` int(11) NOT NULL default '0',
`level` int(11) NOT NULL default '1',
`class` int(11) NOT NULL default '1',
`name` varchar(100),
...
`note` varchar(100),
PRIMARY KEY (`id`),
KEY `child` (`id`,`left_key`,`right_key`,`class`)
);

Соответственно, если в таблице будет только лишь одно дерево, то поле class - не нужно.

Теперь можно перейти непосредственно к методам нашего объекта:

1. Создание узла

Как показывает практика, иногда требуется создавать узел в начале списка, а иногда - в конце. Причем данный параметр может распространяться как на все дерево, так и непосредственно только на конкретную операцию создания (перемещения). Поэтому добавим еще одно свойство объекта, которое мы будем определять во время его объявления, а так же сделаем возможность указывать данный параметр, во время операции. Изменяем процедуру new модуля:

sub new {
...
$self = {
...
order => 'B', # T - (top) начало списка, B - (bottom) конец списка
};
...
$self->{'order'} = $$common{'order'} && $$common{'order'} eq 'top' ? 'T' : 'B';
...
}

Для того, что бы создать узел, нам нужны следующие данные:

  • подчинение (родитель) создаваемого узла;
  • идентификатор дерева, в котором создаем узел (если мультидерево)

Действия, которые мы должны произвести во время создания:

  • определить точку, где мы создаем узел;
  • создание "пустого" промежутка в дереве;
  • вставка нового узла в пустой подготовленный промежуток
sub insert_unit {
# Получаем объект, идентификатор родителя и идентификатор дерева
my ($self, %common)= @_;
# Инициализируем идентификатор дерева
my $catalog = $common{'tree'} || 1;
# Инициализируем идентификатор родителя
my $under = $common{'under'} || 'root';
# Определяем порядок создания (место в списке)
my $order = $common{'order'} || undef;
# Объявляем локальные переменные

my ($key, $level);
# Если родитель корень дерева
if ($under
eq 'root') {
# если вставка в конец списка левый ключ создаваемого выбирается как
# максимальный правый ключ дерева + 1, уровень узла - 1
if (($order
&& $order eq 'top') || ($self->{'order'} eq 'T')) {
$level = 1; $key = 1
} else {
my $sql = 'SELECT MAX('.$self->{'right'}.') + 1 FROM '.$self->{'table'}.
($self->{'type'} eq 'M' ? ' WHERE '.$self->{'multi'}.'= \''.$catalog.'\'' : '');
my $sth = $self->{'DBI'}->prepare($sql); $sth->execute();
$key = $sth->fetchrow_arrayref()->[0];
$sth->finish();
$level = 1;
$key = $key || 1
}
# Если родитель определен, то левый ключ создаваемого узла будет равным
# правому ключу родительского узла, уровень - родительский + 1

} else {
my $sql = 'SELECT '.$self->{'right'}.', '.$self->{'left'}.', '.$self->{'level'}.
($self->{'type'} eq 'M' ? ', '.$self->{'multi'} : '').
' FROM '.$self->{'table'}.' WHERE '.$self->{'id'}.' = \''.$under.'\'';
my $sth = $self->{'DBI'}->prepare($sql); $sth->execute();
my
$row = $sth->fetchrow_arrayref(); $sth->finish();
$key = ($order
&& $order eq 'top') || ($self->{'order'} eq 'T') ? $$row[1] + 1: $$row[0];
$level = $$row[2] + 1;
# Если у нас мультидерево, то переопределяем идентификатор дерева
# относительно родительского узла

$catalog = $$row[3] || undef;
}
# Обновляем ключи дерева для создания пустого промежутка
$self->{'DBI'}->do('UPDATE '.$self->{'table'}.' SET '.
$self->{'right'}.' = '.$self->{'right'}.' + 2, '.
$self->{'left'}.' = IF('.$self->{'left'}.' >= '.$key.', '.$self->{'left'}.
' + 2, '.$self->{'left'}.') WHERE '.$self->{'right'}.' >= '.$key.
($self->{'type'} eq 'M' ? ' AND '.$self->{'multi'}.'= \''.$catalog.'\'' : ''));
# Создаем новый узел
$self->{'DBI'}->do('INSERT INTO '.$self->{'table'}.' SET '.
$self->{'left'}.' = '.$key.', '.$self->{'right'}.' = '.$key.' + 1, '.
$self->{'level'}.' = '.$level.
($self->{'type'} eq 'M' ? ', '.$self->{'multi'}.'= \''.$catalog.'\'' : ''));
# Получаем идентификатор созданного узла и возвращаем его в качестве результата
my
$sth = $self->{'DBI'}->prepare('SELECT LAST_INSERT_ID()'); $sth->execute();
my
$id = $sth->fetchrow_arrayref()->[0];
$sth->finish();
return
$id
}

Вызов данного метода производится так:

...
my $under = ... ; # Определяем родителя
my $tree = ... ; # Определяем идентификатор дерева
...
use MyModule::NestedSets;
my $nested = new MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh};
my $new_unit = $nested->insert_unit(under=>$under, tree=>$tree, order=>'top');
...

2. Определение узла

Все дальнейшие действия мы производим уже над существующими узлами. В большинстве случаев мы обычно знаем, только лишь идентификатор редактируемого узла, но этих данных мало, поэтому, создадим метод определения узла:

sub select_unit {
# Получаем объект, идентификатор узла
my $self = shift;
$self->{'unit'}->{'id'} = shift;
# Производим выборку данных узла*
my $sql = 'SELECT '.$self->{'left'}.' AS lk, '.
$self->{'right'}.' AS rk, '.
$self->{'level'}.' AS lv '.
($self->{'type'} eq
'M' ? ', '.$self->{'multi'}.' AS cl' : '').
' FROM '.$self->{'table'}.
' WHERE '.$self->{'id'}.' = \''.$self->{'unit'}->{'id'}.'\'';
my $sth = $self->{'DBI'}->prepare($sql); $sth->execute();
my $row = $sth -> fetchrow_hashref();
$sth -> finish();
# Если узел существует, то передаем данные в объект
if ($row) {
$self->{'unit'}->{'left'} = $row->{'lk'};
$self->{'unit'}->{'right'} = $row->{'rk'};
$self->{'unit'}->{'level'} = $row->{'lv'};
$self->{'unit'}->{'multi'} = $row->{'cl'} if $row->{'cl'};
return $self
} else {croak("NestedSets failed: Your cann't select this unit, because unit is not exist!!!")}
}

Хочу обратить внимание на то, что всем полям при выборке мы объявляем псевдонимы, потому как имена полей в таблице могут быт разные. Полученные данные, мы сохраняем в тот же объект $self, поэтому, добавим в описание объекта дополнительные свойства:

sub new {
...
$self = {
...
unit => {
id
=> undef,
left => undef,
right => undef,
level => undef,
multi => undef,
},
};
...
}

Вызов данного метода производится так:

...
my $unit = ... # Определяем идентификатор узла
...
use MyModule::NestedSets;
my $nested = new
MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh};
$nested->select_unit($unit);
...

3. Удаление узла

Во время удаления узла нам нужны данные только удаляемого узла, для определения которых мы воспользуемся вышеописанной процедурой. Действия, которые мы будем производить:

  • определение данных узла;
  • удаление узла и ему подчиненных;
  • обновление ключей дерева для устранения промежутка
sub delete_unit {
# Получаем данные: объект и идентификатор удаляемого узла
my ($self, $unit) = @_;
# получаем параметры узла
if ($unit) {$self = &select_unit($self, $unit)}
elsif (!$self->{'unit'}->{'id'}) {croak("NestedSets failed: Your must first select unit, for detete it!!!")}
# Определяем смещение ключей после удаления
my $skew = $self->{'unit'}->{'right'} - $self->{'unit'}->{'left'} + 1;
# Удаляем узел
$self->{'DBI'}->do('DELETE FROM '.$self->{'table'}.' WHERE '.
$self->{'left'}.' >= '.$self->{'unit'}->{'left'}.
' AND '.$self->{'right'}.' <= '.$self->{'unit'}->{'right'}.
($self->{'type'} eq 'M' ?
' AND '.$self->{'multi'}.'= \''.$self->{'unit'}->{'multi'}.'\'' : '')
);
# Обновляем ключи дерева относительно смещения
$self->{'DBI'}->do('UPDATE '.$self->{'table'}.
' SET '.
$self->{'left'}.' = IF('.$self->{'left'}.' > '.$self->{'unit'}->{'left'}.', '.$self->{'left'}.' - '.$skew.', '.$self->{'left'}.'), '.
$self->{'right'}.' = '.$self->{'right'}.' - '.$skew.
' WHERE '.
$self->{'right'}.' > '.$self->{'unit'}->{'right'}.' AND '.
($self->{'type'} eq 'M' ?
$self->{'multi'}.'= \''.$self->{'unit_select'}->{'multi'}.'\'' : '')
);
return 1
}

Вызов данного метода производится так:

...
my $unit = ... # Определяем идентификатор узла
...
use MyModule::NestedSets;
my $nested = new
MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh};
$nested->delete_unit($unit);
...

или так:

...
my $unit = ... # Определяем идентификатор узла
...
use MyModule::NestedSets;
my $nested = new
MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh};
$nested->select_unit($unit);
$nested->delete_unit;
...

Ключевые слова программирование
Автор Томулевич Сергей (Phoinix)
Источник www.asit.ru

Возврат к списку







 
Ария
 










О проекте История Награды Связаться с нами Реклама ссылки

Проект "КМВ и Ставропольский край". © ООО ИП АЛЬФА КМВ 2004–2024

357500 Пятигорск, ул. Панагюриште, 16, корпус 2 тел.: (961)485-11-55, (928)348-08-05