GetInfo.Ru – Компьютерная библиотека
Последние поступления
Как выбрать систему управления базы данных
Базы данных03/09/14
Этапы загрузки UNIX (в схеме)
Unix27/03/12
Gatewall Antispam: тотальный контроль электронной почты
Спам21/04/11
Мастер-класс: создаем Интернет-магазин (Часть 1)
Обзоры ПО20/04/11
CorelDRAW Graphics Suite X5: Что нового?
Обзоры ПО20/07/10
Добавить статью
Самые читаемые материалы
Дерево каталогов NESTED SETS (вложенные множества) и управление им(140986)
Дерево каталогов NESTED SETS (вложенные множества) и управление им. Часть 2(24543)
SQL сервер PostgreSQL(22914)
Теоретические основы реляционных баз данных(21627)
Введение в Базы данных(17508)
Всего статей: 793Всего авторов: 364Подразделов: 47Добавлено за сутки: 0
Статьи  СТАТЬИ Форум  ФОРУМ Рейтинг  РЕЙТИНГ Поиск  ПОИСК Контакты  КОНТАКТЫ
» Главная » Базы данных » Дерево каталогов NESTED SETS (вложенные множества) и управление им. Часть 2

Дерево каталогов NESTED SETS (вложенные множества) и управление им. Часть 2


Сергей Томулевич
phoinix@asit.ru

Страницы: [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ]

В предыдущей статье мы рассмотрели теорию управления 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!!!")}
}

 
28.01.2005
Версия для печати Версия для печати Запомнить ссылку Запомнить ссылку
Ваша оценка:  1   2   3   4   5     

 О проектеПерепечаткаАвторамПартнерыО нас пишут
Наверх
©2003—2007. GETINFO.RU. ВСЕ ПРАВА ЗАЩИЩЕНЫ.