ВыходВход

Меню сайта

Категории статей
Теория [1]
Древовидные структуры данных. Методология, описание
Использование в SQL DB [0]
Решения используемые в SQL DB
Использование в Perl [7]
Применение технологии Nested Sets в Perl программировании
Использование в PHP [2]
Применение технологии Nested Sets в PHP программировании

Меню пользователя

Поиск по статьям

Друзья сайта

Модуль Perl - 4. Заключительные методы
» Каталог статей » Использование в Perl
Модуль Perl - 4. Заключительные методы

Модуль Perl - 4. Заключительные методы

7. Проверка целостности дерева

Так как структура дерева использует неявное подчинение, существует вариант того что дерево может "разлететься", потерять организацию. Для этого используем проверки описанные в первой статье:

  • Левый ключ ВСЕГДА меньше правого;
  •     Наименьший левый ключ ВСЕГДА равен 1;
  •     Наибольший правый ключ ВСЕГДА равен двойному числу узлов;
  • Разница между правым и левым ключом ВСЕГДА нечетное число;
  • Если уровень узла нечетное число то тогда левый ключ ВСЕГДА нечетное число, то же самое и для четных чисел;
  • Ключи ВСЕГДА уникальны, вне зависимости от того правый он или левый;

Код:

sub check_tree {
# Получаем данные: объект 
    my ($self, $repair) = @_;
# Результат проверки
    my %data;
# Левый ключ ВСЕГДА меньше правого
    my $sql = 'SELECT '.($self->{'type'} eq 'M' ? 
                         $self->{'multi'}.' AS multi' : 'COUNT('.$self->{'id'}.') AS num').
              ' FROM '.$self->{'table'}.
              ' WHERE '.$self->{'left'}.' >= '.$self->{'right'}.
              ($self->{'type'} eq 'M' ? ' GROUP BY '.$self->{'multi'} : '');
    my $sth = $self->{'DBI'}->prepare($sql); $sth->execute(); 
    while (my $row = $sth->fetchrow_hashref()) {
        if ($self->{'type'} eq 'M') {$data{$$row{'multi'}} = 1}
        elsif ($$row{'num'} && $$row{'num'} > 0) {$data{'check'} = 'no'}
    }
    $sth->finish();
# Наименьший левый ключ ВСЕГДА равен 1
# Наибольший правый ключ ВСЕГДА равен двойному числу узлов
    $sql = 'SELECT '.($self->{'type'} eq 'M' ? $self->{'multi'}.' AS multi, ' : '').
               ' COUNT('.$self->{'id'}.') AS num, '.
               ' MIN('.$self->{'left'}.') AS lk, '.
               ' MAX('.$self->{'right'}.') AS rk'. 
           ' FROM '.$self->{'table'}.
           ($self->{'type'} eq 'M' ? ' GROUP BY '.$self->{'multi'} : '');
    $sth = $self->{'DBI'}->prepare($sql); $sth->execute(); 
    while (my $row = $sth->fetchrow_hashref()) {
        unless ($$row{'lk'} == 1 && $$row{'rk'} / $$row{'num'} == 2) {
            if ($self->{'type'} eq 'M') {$data{$$row{'multi'}} = 1} else {$data{'check'} = 'no'}
        }
    }
    $sth->finish();
# Разница между правым и левым ключом ВСЕГДА нечетное число
    $sql = 'SELECT '.($self->{'type'} eq 'M' ?
                      $self->{'multi'}.' AS multi, ' : 'COUNT('.$self->{'id'}.') AS num, ').
               ' MOD(('.$self->{'right'}.' - '.$self->{'left'}.'), 2) AS os'.
           ' FROM '.$self->{'table'}.
           ' GROUP BY '.$self->{'id'}.
           ' HAVING os = 0';
    $sth = $self->{'DBI'}->prepare($sql); $sth->execute(); 
    while (my $row = $sth->fetchrow_hashref()) {
        if ($self->{'type'} eq 'M') {$data{$$row{'multi'}} = 1}
        elsif ($$row{'num'} && $$row{'num'} > 0) {$data{'check'} = 'no'}
    }
    $sth->finish();
# Если уровень узла нечетное число то тогда левый ключ ВСЕГДА нечетное число,
# то же самое и для четных чисел
    $sql = 'SELECT '.($self->{'type'} eq 'M' ? 
                      $self->{'multi'}.' AS multi, ' : 'COUNT('.$self->{'id'}.') AS num, ').
           ' MOD(('.$self->{'left'}.' - '.$self->{'level'}.' + 2), 2) AS os'.
           ' FROM '.$self->{'table'}.
           ' GROUP BY '.$self->{'id'}.
           ' HAVING os = 1';
    $sth = $self->{'DBI'}->prepare($sql); $sth->execute(); 
    while (my $row = $sth->fetchrow_hashref()) {
        if ($self->{'type'} eq 'M') {$data{$$row{'multi'}} = 1}
        elsif ($$row{'num'} && $$row{'num'} > 0) {$data{'check'} = 'no'}
    }
    $sth->finish();
# Ключи ВСЕГДА уникальны, вне зависимости от того правый он или левый
    if ($self->{'type'} eq 'M') {
        my $sql = 'SELECT '.$self->{'multi'}.' AS multi'.
                  ' FROM '.$self->{'table'}.
                  ' GROUP BY '.$self->{'multi'};
        my $sth = $self->{'DBI'}->prepare($sql); $sth->execute(); 
        while (my $multi = $sth->fetchrow_hashref()) {
            my $sql = 'SELECT '.$self->{'left'}.' AS lk, '.$self->{'right'}.' AS rk'.
                      ' FROM '.$self->{'table'}.
                      ' WHERE '.$self->{'multi'}.' = '.$$multi{'multi'};
            my $sth = $self->{'DBI'}->prepare($sql); $sth->execute();
            my %check;
            while (my $row = $sth->fetchrow_hashref()) {
                if ($check{$$row{'lk'}}) {$data{$$multi{'multi'}} = 1} else {$check{$$row{'lk'}} = 1}
                if ($check{$$row{'rk'}}) {$data{$$multi{'multi'}} = 1} else {$check{$$row{'rk'}} = 1}
            }
            $sth->finish();
        }
        $sth->finish();
    } else {
        my $sql = 'SELECT '.$self->{'left'}.' AS lk, '.$self->{'right'}.' AS rk'.
                  ' FROM '.$self->{'table'};
        my $sth = $self->{'DBI'}->prepare($sql); $sth->execute();
        my %check;
        while (my $row = $sth->fetchrow_hashref()) {
            if ($check{$$row{'lk'}}) {$data{'check'} = 'no'} else {$check{$$row{'lk'}} = 1}
            if ($check{$$row{'rk'}}) {$data{'check'} = 'no'} else {$check{$$row{'rk'}} = 1}
        }
        $sth->finish();
    }
# Проверяем, найдены ли ошибки
    my $result = 'No error';
    if (%data && $repair eq 'repair') {$result = &repair_tree($self, %data)}
    elsif (%data && $repair ne 'repair') {$result = 'Found error! Not repaired!'}
    return $result
}

В процессе этого метода формируется хеш %data в котором перечислены либо идентификаторы деревьев (если мультидерево), либо один элемент для инициализации. Последняя проверка на уникальнось ключей, не такая, как описано в статье. Дело в том, что при использовании мультидеревьев а так же при большом количестве узлов, получается большая нагрузка на базу данных, что не совсем правильно.

В конце процедуры мы ссылаемся на другую процедуру repair_tree. Так как ручное исправление целостности дерева - дело неблагодарное, и муторное; а чаще всего сводится к простейшему обнулению структуры, напишем отдельную процедуру, в которой попытаемся, хотя бы частично её восстановить.

8. Принудительное восстановление дерева

Восстановление структуры дерева - простое "обнуление", во время которого, все узлы устанавливаются на первый уровень в соответствии со своими идентификаторами.

sub repair_tree {
# Получаем данные 
    my ($self, %multi) = @_;
# Обработка дерева
    if ($self->{'type'} eq 'M') {
        foreach my $class (keys %multi) {
            $self->{'DBI'}->do('SET @count1 := -1');
            $self->{'DBI'}->do('SET @count2 := 0');
            $self->{'DBI'}->do('UPDATE '.$self->{'table'}.
                ' SET '.$self->{'left'}.' = @count1 := @count1 + 2, '.
                        $self->{'right'}.' = @count2 := @count2 + 2, '.
                        $self->{'level'}.' = 1'.
                ' WHERE '.$self->{'multi'}.' = \''.$class.'\''.
                ' ORDER BY '.$self->{'id'})
        }
    } else {
        $self->{'DBI'}->do('SET @count1 := -1');
        $self->{'DBI'}->do('SET @count2 := 0');
        $self->{'DBI'}->do('UPDATE '.$self->{'table'}.
            ' SET '.$self->{'left'}.' = @count1 := @count1 + 2, '.
                    $self->{'right'}.' = @count2 := @count2 + 2, '.
                    $self->{'level'}.' = 1'.
            ' ORDER BY '.$self->{'id'})
    }
    return 'Repair OK!';
}

9. Заключение

В итоге получился модуль, который позволяет максимально упростить работу с деревьями Nested Sets. Сам модуль ближайшее время выложу на CPAN, а так его можно скачать здесь. Устанавливать его не нужно, просто положить в соответсвующую папку (в моем случае это MPM) а в скрипте подключиль дополнительный каталог баблиотеки (eq: use lib './../lib';). В процессе буду его модернизировать и обновновлять поэтому если интересно, следите за обновлениями.

Категория: Использование в Perl | Добавил: phoinix (2005-10-31) | Автор: Сергей Томулевич (aka Phoinix)
Просмотров: 2497 | Рейтинг: 5.0 |

Комментарии

 

Бесплатный конструктор сайтов - uCoz