Evgenii Legotckoi
Evgenii Legotckoi25. September 2016 13:37

Materialisierter Pfad in PostgreSQL

In relationalen Datenbanken ist das Speichern von Informationen in Form von Baumstrukturen eine Aufgabe mit zusätzlichen Overhead-Kosten. Solche zusätzlichen Kosten können sowohl eine Zunahme der Anzahl von Anfragen als auch eine Zunahme der Informationsmenge sein, die der Organisation der Datenstruktur dient.

Gängige Ansätze zum Organisieren von Baumstrukturen sind:

Name Beschreibung
Adjazenzliste Adjazenzliste Die Organisation der Datenstruktur besteht darin, dass jedes Objekt Informationen über das Elternobjekt speichert, dh es gibt ein zusätzliches Feld in der Tabellenzeile, das die ID des Objekts angibt, in das dieses Objekt geschachtelt ist.
Verschachtelter Satz Verschachtelter Satz Verschachtelte Sets speichern nicht nur Informationen über die sogenannten linken und rechten Schlüssel, sondern auch die Verschachtelungsebene. Diese Variante der Organisation der Datenstruktur ist leicht zu lesen, aber schwieriger zu ändern.
Materialisierter Pfad Materialisierter Pfad Die Idee hinter dieser Datenstruktur besteht darin, dass jeder Datensatz den vollständigen Pfad zum Wurzelelement des Baums speichert.

Die Betrachtung dieser Datenstrukturen wurde durch die Notwendigkeit veranlasst, Kommentarunterstützung für Artikel auf der Site zu implementieren.


Adjazenzliste schien mir eine etwas umständliche Lösung für die Organisation von Kommentaren zu sein, da es Probleme beim Auffinden von übergeordneten Objekten und beim Erstellen einer Baumstruktur geben kann, obwohl Sie mit dem Ansatz selbst Elemente ziemlich schnell löschen und hinzufügen können.

Nested Set ist eine ziemlich umständliche Methode, um Kommentare auf einer kleinen Site zu organisieren, und selbst wenn Sie nach großen Ressourcen wie Habr suchen, gibt es nicht so oft 5000 Kommentare unter den Artikeln. Verwirrt hat mich auch die Tatsache, dass es notwendig sein wird, ein verstecktes Wurzelelement hinzuzufügen, den sogenannten ersten leeren Kommentar im Artikel, aus dem der gesamte Baum aufgebaut wird. Warum unnötige Entitäten produzieren? - Obwohl ich mich irren kann. Nun, das letzte Argument "dagegen" war die Notwendigkeit, die Schlüssel beim Hinzufügen neuer Kommentare neu zu berechnen. Im Allgemeinen wird diese Art der Speicherung von Kommentaren trotz der Vorteile dieser Struktur im Rahmen dieser Site und ihres aktuellen Zustands zu Spatzen, die aus einer Kanone feuern.

Aber Materialized Path schien genau das zu sein, was Sie brauchen. Jeder Kommentar enthält den vollständigen Pfad. Und gleichzeitig können unter dem Artikel mehrere Kommentarbäume organisiert werden. Das heißt, jeder Kommentar, der sich auf der ersten Ebene befindet, wird automatisch als Wurzel für seinen Baum betrachtet. Und wenn Sie einen neuen Kommentar hinzufügen, müssen Sie den vollständigen Pfad des zukünftigen übergeordneten Elements übernehmen und ihm nur die ID des neuen Kommentars hinzufügen. Darüber hinaus kann in Bezug auf eine bestimmte Datenbank, und wir sprechen hier von PostgreSQL , der Pfadspeicher als Array von ganzzahligen Werten dargestellt werden, da PostgreSQL Arrays unterstützt (Je mehr ich übrigens Lesen Sie mehr über die Arbeit mit PostgreSQL im Gegensatz zu MySQL , je mehr ich es mag). Und auch beim Abrufen von Kommentarbäumen können Sie nach diesen Arrays sortieren, wodurch Sie automatisch alle Kommentare in chronologischer Reihenfolge unter Beibehaltung der Baumstruktur und dann in der [Django]-Vorlage (https://evileg.com/ru /knowledge/django/ ) bilden die Seite in einer regelmäßigen Schleife ohne Rekursionen, da alle Kommentare bereits durch die Datenbank richtig sortiert werden.

Verwendung in PostgreSQL

Stellen wir uns nun vor, wir haben eine bestimmte abstrakte Seite, die mehrere Kommentare mit der folgenden Struktur enthält:

id | name
-------------------
1  | comment_1
2  |   comment_1_1
3  |     comment_1_1_1
4  |   comment_1_2
5  | comment_2
6  |     comment_2_1
7  |     comment_2_2
8  | comment_3

Als Ergebnis wird die Struktur des Kommentars durch die folgende Anfrage gebildet:

CREATE TABLE comments (
  id serial primary key,
  path integer[] not null,
  content varchar(200) not null
);

Diese Struktur verfügt über eine eindeutige autoinkrementierende Kennung, die auch als eine Art chronologische Kennung angesehen werden kann, da theoretisch ein Kommentar mit einer späteren Zeit nicht vor einem Kommentar mit einer niedrigeren ID in der Datenbank erscheinen kann.

Lassen Sie uns die Datenbank mit Kommentaren füllen:

INSERT INTO comments (path, content) VALUES 
  ('{1}', 'comment_1'),
  ('{1,2}', 'comment_1_1'),
  ('{1,2,3}', 'comment_1_1_1'),
  ('{1,4}', 'comment_1_2'),
  ('{5}', 'comment_2'),
  ('{5,6}', 'comment_2_1'),
  ('{5,7}', 'comment_2_2'),
  ('{8}', 'comment_3');

Als Ergebnis erhalten wir folgende Tabelle:

id | path    | content       
-------------------------
1  | {1}     | comment_1    
2  | {1,2}   | comment_1_1  
3  | {1,2,3} | comment_1_1_1
4  | {1,4}   | comment_1_2
5  | {5}     | comment_2  
6  | {5,6}   | comment_2_1
7  | {5,7}   | comment_2_2
8  | {8}     | comment_3  

Kinder finden

Um untergeordnete Elemente zu finden, müssen Sie den Array-Überlappungsoperator && verwenden. Mit diesem Operator finden wir Elemente, die die ID des übergeordneten Elements in ihrem Pfad enthalten. Wählen wir alle Kinder für das Element mit der ID = 1 aus.

Darüber hinaus können wir alle Elemente in aufsteigender Reihenfolge der Verschachtelung, dh der Pfadlänge, sortieren.

In der Abfrage unten : id = 1 .

SELECT * FROM comments
WHERE path && ARRAY[:id]
ORDER BY array_length(path, 1);

Die Ausgabe wird die folgende Ausgabe sein:

 id |  path   |    content    
----+---------+---------------
  1 | {1}     | comment_1
  2 | {1,2}   | comment_1_1
  4 | {1,4}   | comment_1_2
  3 | {1,2,3} | comment_1_1_1
(4 rows)

Übergeordnete Elemente finden

Um eine Liste aller übergeordneten Elemente zu erhalten, wählen Sie alle Elemente aus, die Teil des Pfads des aktuellen Elements sind. Zum Beispiel erhalten wir eine Liste der übergeordneten Elemente eines Kommentars mit ID = 3 , d. h. Kommentar \ _1 \ _1 \ _1.

In der Abfrage unten : id = 3.

SELECT * FROM comments
WHERE id != :id 
AND array[id] && (
  SELECT path 
  FROM comments 
  WHERE id = :id
)
ORDER BY path;

Als Ergebnis erhalten wir folgende Ausgabe:

 id | path  |   content   
----+-------+-------------
  1 | {1}   | comment_1
  2 | {1,2} | comment_1_1
(2 rows)

Elemente hinzufügen

Beim Erstellen eines neuen Datensatzes müssen wir einen Pfad entlang des aktuellen Pfads des übergeordneten Elements bilden, dem die ID des neuen Elements hinzugefügt wird. Fügen wir einen neuen Datensatz mit der ID des übergeordneten Elements gleich 4 hinzu.

In der folgenden Abfrage unten : id = 4.

INSERT INTO comments (content, path)
values (
  'comment_1_2_3', 
  (SELECT path FROM comments WHERE id = :id) || (select currval('comments_id_seq')::integer)
);

Der Operator || verkettet den Pfad des Elternelements mit der ID des neuen Elements. Gleichzeitig müssen wir einen neuen ID-Wert aus der Sequenz comments \ _id \ _seq abrufen, die beim Erstellen der Tabelle automatisch erstellt wird, da der Pfad ein Array vom Typ Integer [ ] und currval geben den Typ biginteger zurück.

Wir erhalten folgende Tabelle:

 id |  path   |    content    
----+---------+---------------
  1 | {1}     | comment_1
  2 | {1,2}   | comment_1_1
  3 | {1,2,3} | comment_1_1_1
  4 | {1,4}   | comment_1_2
  5 | {5}     | comment_2
  6 | {5,6}   | comment_2_1
  7 | {5,7}   | comment_2_2
  8 | {8}     | comment_3
  9 | {1,4,9} | comment_1_2_3
(9 rows)

Ein Element löschen

Wir löschen ein Element mit dem Löschen aller untergeordneten Elemente, was auch durch die Überschneidung von Arrays implementiert wird.

Entfernen wir beispielsweise das Element mit der ID gleich 5. ( : id = 5 )

DELETE FROM comments
WHERE path && ARRAY[:id];

Als Ergebnis sieht die Tabelle so aus:

 id |  path   |    content    
----+---------+---------------
  1 | {1}     | comment_1
  2 | {1,2}   | comment_1_1
  3 | {1,2,3} | comment_1_1_1
  4 | {1,4}   | comment_1_2
  8 | {8}     | comment_3
  9 | {1,4,9} | comment_1_2_3
(6 rows)

Element verschieben

Zum Schluss verschieben wir eines der Elemente zusammen mit den Nachkommen in ein anderes Element. Da es nicht viel Auswahl gibt, verschieben wir das Element mit der ID = 2 in das Element mit der ID gleich 8.

Um ein Element in ein anderes zu verschieben, ist es im Allgemeinen erforderlich, den Teil des Pfads, der dem aktuellen Elternteil des gegebenen Elements und allen seinen Kindern entspricht, durch den Pfad zu ersetzen, der dem neuen Elternteil entspricht.

Der einzige knifflige Teil ist, dass das Abrufen der Elementebene das Schreiben einer gespeicherten Prozedur erfordert, die uns hilft, den Umzug in einer einzigen Anforderung durchzuführen. Nennen wir es level und übergeben die ID des Elements, das dorthin verschoben werden soll.

CREATE OR REPLACE FUNCTION level(element_id integer) RETURNS integer AS
$$
DECLARE result integer;
BEGIN
SELECT array_length(path, 1) FROM comments WHERE comments.id = element_id INTO result;
RETURN result;
END
$$ LANGUAGE plpgsql;

Nach Registrierung der gespeicherten Prozedur verwenden wir diese bei der Datenübermittlungsanfrage.

Diese Anfrage enthält:

  • : new \ _parent \ _id ist die eindeutige Kennung des zukünftigen Elternteils (Gleich 8);
  • : mobile \ _id ist die eindeutige Kennung des beweglichen Gegenstands (Gleich 2).
UPDATE comments
SET path = (
  SELECT path
  FROM comments
  WHERE id = :new_parent_id
) || path[(SELECT level(:movable_id)) : array_length(path, 1)]
WHERE path && ARRAY[:movable_id];

Als Ergebnis erhalten wir eine Tabelle der folgenden Form:

 id |  path   |    content    
----+---------+---------------
  1 | {1}     | comment_1
  4 | {1,4}   | comment_1_2
  8 | {8}     | comment_3
  9 | {1,4,9} | comment_1_2_3
  2 | {8,2}   | comment_1_1
  3 | {8,2,3} | comment_1_1_1
(6 rows)
Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Stabiles Hosting des sozialen Netzwerks EVILEG. Wir empfehlen VDS-Hosting für Django-Projekte.

Magst du es? In sozialen Netzwerken teilen!

Mitai .
  • 5. Januar 2020 03:54

простите а Django MPTT в этом варианте не подходит? она вроде как раз для этих целей разрабатывалась

Evgenii Legotckoi
  • 6. Januar 2020 03:11

Добрый день. Почитал описание Django MPTT, и да, подойдёт. Рассматривайте статью как базовую теорию.

Nomad
  • 2. November 2020 11:02
  • (bearbeitet)

Django MPTT использует для построения дерева алгоритм Nested Set

во вторых если надо создавать динамическое дерево прямо в хтмл документе, Django MPTT, не подойдет, а вернее подойдет только если вы знакомы с алгоритмом Nested Set, а это означает что при добавлении нового элемента в дерево надо перешитать полностью все значения записей по полям lft и rght имея в виду еще поля parent_id и tree_id

=====

вопрос к автору статьи, скажите а Materialized Path сохраняет ордер элементов дерева. предположим что у нас есть дерево:

comment_1
comment_1_1
comment_1_1_1
comment_1_2
comment_2
comment_2_1
comment_2_2
comment_3

мы тут видим 3 родителя: comment_1, comment_2, comment_3

и мы видим что у родителя comment_2 есть 2 чаилда: comment_2_1 и comment_2_2

вот если мы поменяем местами данные чаилды как Materialized Path сохранит это????

Evgenii Legotckoi
  • 28. Januar 2021 04:06

Самый простой способ, который приходит на ум, это скопировать comment_2_1 до нового комментария comment_2_3, чтобы у него был более старший id и переписать колонку path. А оригинальный comment_2_1 удалить. Только так.

Kommentare

Nur autorisierte Benutzer können Kommentare posten.
Bitte Anmelden oder Registrieren
Letzte Kommentare
ИМ
Игорь Максимов5. Oktober 2024 07:51
Django – Lektion 064. So schreiben Sie eine Python-Markdown-Erweiterung Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55. Juli 2024 11:02
QML - Lektion 016. SQLite-Datenbank und das Arbeiten damit in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr8. Februar 2024 18:43
Qt Linux - Lektion 001. Autorun Qt-Anwendung unter Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lektion 007. Arbeiten mit ICMP-Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25. Dezember 2023 10:30
Boost - statisches Verknüpfen im CMake-Projekt unter Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
Jetzt im Forum diskutieren
J
JacobFib17. Oktober 2024 03:27
добавить qlineseries в функции Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью электронной почты https://topdecorpro.ru…
JW
Jhon Wick1. Oktober 2024 15:52
Indian Food Restaurant In Columbus OH| Layla’s Kitchen Indian Restaurant If you're looking for a truly authentic https://www.laylaskitchenrestaurantohio.com/ , Layla’s Kitchen Indian Restaurant is your go-to destination. Located at 6152 Cleveland Ave, Colu…
КГ
Кирилл Гусарев27. September 2024 09:09
Не запускается программа на Qt: точка входа в процедуру не найдена в библиотеке DLL Написал программу на C++ Qt в Qt Creator, сбилдил Release с помощью MinGW 64-bit, бинарнику напихал dll-ки с помощью windeployqt.exe. При попытке запуска моей сбилженной программы выдаёт три оши…
F
Fynjy22. Juli 2024 04:15
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

Folgen Sie uns in sozialen Netzwerken