Быстрый реляционный метод хранения древовидных данных (например, многопоточные комментарии к статьям) - PullRequest
15 голосов
/ 11 мая 2009

У меня есть cms, в которой хранятся комментарии к статьям. Эти комментарии могут быть как потоковыми, так и не потоковыми. Хотя технически они совпадают только с тем, что столбец ответа остается пустым, когда он не содержит потоков. Мое приложение работает на sqlLite, MySQL и pgsql, поэтому мне нужен довольно стандартный SQL.

У меня есть таблица комментариев

comment_id
article_id
user_id
comment
timestamp
thread (this is the reply column)

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

Если комментарии не являются потоками, я могу легко заказать по метке времени.

Если они с резьбой, я сортирую вот так

ORDER BY SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))

Как вы можете видеть из ORDER BY, комментирующие запросы никогда не будут использовать индекс, так как индексы на основе функций только действительно живут в Oracle. Помогите мне быстро прояснить страницы с комментариями.

Ответы [ 6 ]

19 голосов
/ 11 мая 2009

Мне действительно нравится, как Drupal решает эту проблему. Он присваивает идентификатор потока каждому комментарию. Этот идентификатор начинается с 1 для первого комментария. Если к этому комментарию добавляется ответ, ему присваивается идентификатор 1.1. Ответ на комментарий 1.1 дается с идентификатором потока 1.1.1. Родственнику комментария 1.1 присваивается идентификатор потока 1.2. Вы поняли идею. Вычисление этих идентификаторов потоков можно легко выполнить одним запросом при добавлении комментария.

Когда поток выводится, все комментарии, принадлежащие потоку, выбираются в одном запросе, сортируясь по идентификатору потока. Это дает вам темы в порядке возрастания. Кроме того, используя идентификатор потока, вы можете найти уровень вложенности каждого комментария и сделать для него соответствующий отступ.

1
1.1
1.1.1
1.2
1.2.1

Есть несколько вопросов, которые нужно разобраться:

  • Если один компонент идентификатора потока увеличивается до 2 цифр, сортировка по идентификатору потока не приведет к ожидаемому порядку. Простое решение состоит в том, что все компоненты идентификатора потока дополняются нулями до одинаковой ширины.
  • Сортировка по номеру нисходящего потока не приводит к ожидаемому убыванию.

Drupal решает первую проблему более сложным способом, используя систему нумерации vancode. Что касается второй проблемы, она решается путем добавления обратной косой черты (чей код ASCII выше цифр) к идентификаторам потоков при сортировке по убыванию. Вы можете найти более подробную информацию об этой реализации, проверив исходный код модуля comments (см. Большой комментарий перед функцией comment_get_thread).

4 голосов
/ 07 апреля 2013

Я знаю, что ответ немного запоздал, но для дерева данных используйте таблицу закрытия http://www.slideshare.net/billkarwin/models-for-hierarchical-data

Описывает 4 метода:

  • Список соответствия (простой родительский внешний ключ)
  • Перечисление пути (стратегия Drupal, упомянутая в принятом ответе)
  • Вложенные множества
  • Таблица закрытия (хранение фактов предка / потомка в отдельном отношении [таблица] с возможным столбцом расстояния)

Последний вариант имеет преимущества простых операций CRUD по сравнению с остальными. Стоимость - это пространство, которое в худшем случае составляет размер O (n ^ 2) в узлах числового дерева, но на практике, вероятно, не так уж и плохо

2 голосов
/ 11 мая 2009

К сожалению, методы чистого SQL сделать это довольно медленно.

NESTED SETS, предложенный @Marc W, довольно элегантен, но может потребовать обновления всего дерева, если ветви вашего дерева достигают диапазонов, что может быть довольно медленным.

См. Эту статью в моем блоге о том, как сделать это быстро в MySQL:

Вам нужно будет создать функцию:

CREATE FUNCTION hierarchy_connect_by_parent_eq_prior_id(value INT) RETURNS INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN
        DECLARE _id INT;
        DECLARE _parent INT;
        DECLARE _next INT;
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET @id = NULL;

        SET _parent = @id;
        SET _id = -1;

        IF @id IS NULL THEN
                RETURN NULL;
        END IF;

        LOOP
                SELECT  MIN(id)
                INTO    @id
                FROM    t_hierarchy
                WHERE   parent = _parent
                        AND id > _id;
                IF @id IS NOT NULL OR _parent = @start_with THEN
                        SET @level = @level + 1;
                        RETURN @id;
                END IF;
                SET @level := @level - 1;
                SELECT  id, parent
                INTO    _id, _parent
                FROM    t_hierarchy
                WHERE   id = _parent;
        END LOOP;
END

и используйте его в запросе, подобном следующему:

SELECT  hi.*
FROM    (
        SELECT  hierarchy_connect_by_parent_eq_prior_id(id) AS id, @level AS level
        FROM    (
                SELECT  @start_with := 0,
                        @id := @start_with,
                        @level := 0
                ) vars, t_hierarchy
        WHERE   @id IS NOT NULL
        ) ho
JOIN    t_hierarchy hi
ON      hi.id = ho.id

Это, конечно, MySQL специфично, но очень быстро.

Если вы хотите, чтобы он был переносимым между PostgreSQL и MySQL, вы можете использовать PostgreSQL contrib для CONNECT BY и заключить запрос в хранимую процедуру с одинаковым именем для обеих систем.

2 голосов
/ 11 мая 2009

У вас есть выбор между моделями смежности и вложенного множества. Статья Управление иерархическими данными в MySQL представляет собой хорошее введение.

Теоретическое обсуждение см. В Celko Деревья и иерархии .

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

create Tablename (
  RecordID integer not null default 0 auto_increment,
  ParentID integer default null references RecordID,
  ...
)

Затем вы можете использовать рекурсивное общее табличное выражение для отображения многопоточного представления. Пример доступен здесь .

2 голосов
/ 11 мая 2009

Я просто сделал это сам, на самом деле! Я использовал модель вложенного набора для представления иерархических данных в реляционной базе данных.

Управление иерархическими данными в MySQL было для меня чистым золотом. Вложенные множества - это вторая модель, описанная в этой статье.

0 голосов
/ 11 мая 2009

На самом деле, это должен быть баланс между чтением и записью.

Если у вас все в порядке с обновлением группы строк при каждой вставке, то вложенный набор (или его эквивалент) даст вам легкое и быстрое чтение.

Кроме этого, простой FK на родительском элементе даст вам очень простую вставку, но вполне может быть кошмаром для поиска.

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

...