Описание задачи
- Хранение иерархических данных (древовидных комментариев) в реляционной БД MySQL
- Простое добавление узлов/ветвей
- Выборка всего дерева одним запросом, с отсортированными в нужном порядке ветвями
В принципе, задача классическая, и сначала я её решил с помощью объединения Adjacency List и Materialized Path. Другими словами, в таблице добавлены колонки
id INT(11) parentid INT(11) mpath VARCHAR(255)
В mpath хранился полный путь ветки в дереве, например /1/3/4/5/8/9, где через знак "/" перечислялись ID комментария.
При таком подходе очень легко добавлять новые комментарии практически любого уровня вложенности. Выборка при этом производилась запросом:
SELECT * FROM messages WHERE postid=$postid ORDER BY mpath ASC
Проблема возникла при росте числа комментариев. Например, имеется дерево со следующими путями:
1 1.2 1.2.10 1.2.3 1.2.7 4 4.8 4.8.9 5 5.6
Здесь цифрами указаны ID, а порядок такой, какой бы он получился при использовании ORDER BY mpath ASC.
Комментарий 1.2.10 идёт до 1.2.3 и др, хотя был добавлен позже (судя по ID).
Решение задачи
Решение проблемы частично было описано здесь: http://habrahabr.ru/post/125729/. Идея — использовать не десятичные ID в пути, а кодировать в другую систему счисления, чтобы иметь меньше ограничений в длине пути. При этом, разделители в виде точек или других символов не нужны, так как поле будет использоваться только для сортировки.
Я использовал основание 95, это как раз число печатаемых символов в ASCII.
!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
Если для каждого значения в пути мы будем использовать фиксированную длину, получим следующий верхний порог ID:
1 — 95^1 — 1 = 94
2 — 95^2 — 1 = 9 024
3 — 95^3 — 1 = 857 374
4 — 95^4 — 1 = 81 450 624
5 — 95^5 — 1 = 7 737 809 374
5-ти символов вполне достаточно, чтобы не волноваться о максимальном количестве комментариев.
Максимальный уровень вложенности, при этом, для VARCHAR 255/5 = 51
Теоретически, можно брать не BASE95, а BASE256 (вместе с непечатаемыми символами), но хранить это всё бинарно в BLOB, правда тут я не уверен с производительностью при сортировке (надо проверить). Тогда уже 3 символами мы могли бы кодировать 16 777 215 вариантов, а 4 — 4 294 967 295.
Как это выглядит в коде
Приведу свой пример реализации всей этой теории.
// $mpath - хранит стандартный materialized path с разделителем "/" // При добавлении нового комментария: $db->Execute(" UPDATE messages SET mpath=?, bpath=?, depth=? WHERE id=? ", array( $mpath, bpath($mpath), $depth, $messid )); // . . . // константа с символами для кодирования define('BASE95', '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'); // . . . // функция bpath function bpath($mpath, $sep = '/', $max_len = 5) { $bpath = ''; $chunks = explode($sep, trim($mpath, $sep)); $zero = substr(BASE95, 0, 1); foreach($chunks as $chunk) $bpath .= str_pad(dec2base($chunk, 95, BASE95), $max_len, $zero, STR_PAD_LEFT); return $bpath; }
И далее выборка:
SELECT * FROM messages WHERE postid=$postid ORDER BY bpath ASC
Функция dec2base взята отсюда.
Такое вот решение, на мой взгляд очень простое. Если у вас также имеется в арсенале красивые решения, делитесь ими в комментариях.
ссылка на оригинал статьи http://habrahabr.ru/post/226741/
Добавить комментарий