Обзор паттернов хранения деревьев в реляционных БД / Хабр
Всем привет! Меня зовут Пантелеев Александр и я бэкенд-разработчик в компании Bimeister.
Постараюсь описать исчерпывающе, кратко и понятно суть основных паттернов хранения деревьев в реляционных базах данных. Надеюсь, что статья будет полезна тем, кто до сего момента не сталкивался с такими паттернами, и станет отправной точкой в их понимании.
В этой статье не будет терминов реляционной алгебры или базы данных: таких как атрибут, домен и т. д. Также не будет привязки к какой-либо СУБД, какому-либо SQL или пользовательскому коду.
Всего существует 4 общепринятых паттерна хранения деревьев:
Adjacency List;
Nested Sets;
Closure Table;
Materialized Path.
Кратко рассмотрим каждый из них.
Adjacency List
Описание
Это самый простой и интуитивный вариант хранения. Каждому элементу сопоставляется его свойство — его родительский элемент. Если родительский элемент не задан, то он считается корневым элементом.
Когда связь сопоставления элемента и родительского элемента хранится отдельно от элемента, Adjacency List можно рассматривать как частный случай Closure Table со связями 1 уровня.
Преимущества
Лёгкость реализации, а также простота вставки, удаления и перемещения элементов в дереве.
Недостатки
Можно получить только непосредственные дочерние элементы. Чтобы получить все дочерние элементы, необходимо выполнить рекурсивный запрос либо производить множественные запросы.
Примеры
Рисунок 1.Элемент | Родительский элемент |
A | — |
B | A |
C | B |
D | C |
E | B |
F | B |
G | A |
H | G |
I | A |
Рассмотрим элемент «B»:
Чтобы получить все его дочерние элементы, нам необходимо выбрать элементы, удовлетворяющие условию:
Родительский элемент равен «B»
Nested Sets
Описание
Каждому элементу сопоставляются свойства: левый и правый индекс, на основе которых будет производиться выборка дочерних элементов. Также, но необязательно, элемент может дополняться свойством уровень для указания желаемого уровня вложенности выбираемого элемента относительно корня или родительского элемента.
Запрос получения дочерних элементов строится на том факте, что для любого дочернего элемента выполняются условия:
При создании и обновлении дерева левые и правые индексы элементов дерева, при его обходе в глубину, заполняются по определённым правилам.
Преимущества
Возможность получения дочерних элементов любых уровней вложенности с помощью простого одиночного запроса.
Недостатки
При использовании целочисленных типов для левого и правого индекса и уровня необходимо пересчитывать индексы всех связанных элементов в следующих случаях:
при вставке элементов;
при удалении элементов;
при изменении родительского элемента.
Пример
Рисунок 2.Элемент | Левый индекс | Правый индекс | Уровень |
A | 1 | 18 | 0 |
B | 2 | 11 | 1 |
C | 3 | 6 | 2 |
D | 4 | 5 | 3 |
E | 7 | 8 | 2 |
F | 9 | 10 | 2 |
G | 12 | 15 | 1 |
H | 13 | 14 | 2 |
I | 16 | 17 | 1 |
Рассмотрим элемент «B». Его значения свойств:
левый индекс = 2;
правый индекс = 11;
уровень = 1.
Чтобы получить все его дочерние элементы, нам необходимо выбрать элементы, удовлетворяющие условию:
левый индекс больше 2 И правый индекс меньше 11
Чтобы получить его непосредственные дочерние элементы, нам необходимо добавить к условию ограничение на уровень:
левый индекс больше 2 И правый индекс меньше 11 И уровень = 1
Чтобы получить дочерние элементы вместе с родительским элементом, нам необходимо ослабить условия индексов:
левый индекс больше или равен 2 И правый индекс меньше или равен 11
Closure Table
Описание
Суть этого паттерна заключается в том, что мы сопоставляем каждому элементу множество связей со всеми его дочерними элементами или сопоставляем каждому элементу множество связей со всеми его родительскими элементами. Также, но необязательно, связь может содержать свойство Уровень. Уровень задаёт расстояние между элементами в дереве.
Если в запросе получения дочерних или родительских элементов по элементу необходимо получать в результате сам элемент, то нужно добавлять связь элемента самого на себя — то есть со значением уровня связи 0.
Преимущества
Возможность получения дочерних элементов любых уровней вложенности с помощью простого одиночного запроса.
Возможность получения родительских элементов любых уровней с их иерархией относительно дочернего элемента с помощью простого одиночного запроса.
Недостатки
При вставке и удалении элементов из дерева, а также при перемещении элементов в дереве необходимо пересчитывать все связи, в которых этот элемент участвует.
Пример
Рисунок 3.Родительский элемент | Дочерний элемент | Уровень |
A | A | 0 |
A | B | 1 |
A | C | 2 |
A | E | 2 |
A | D | 3 |
B | B | 0 |
B | C | 1 |
B | E | 1 |
B | D | 2 |
C | C | 0 |
C | D | 1 |
E | E | 0 |
D | D | 0 |
Рассмотрим элемент «B»:
Чтобы получить все его дочерние элементы, нам необходимо выбрать элементы, удовлетворяющие условию:
родительский элемент равен «B»
Чтобы получить его непосредственные дочерние элементы, нам необходимо добавить к условию ограничение на уровень:
родительский элемент равен «B» И уровень = 1
Чтобы получить дочерние элементы вместе с родительскими, нам необходимо ослабить условия индексов:
родительский элемент равен «B» И уровень = 0
Чтобы получить все его родительские элементы, нам необходимо выбрать элементы, удовлетворяющие условию:
дочерний элемент равен «B»
Materialized Path
Описание
Каждому элементу сопоставляется свойство — его путь, который является последовательностью родительских элементов заданного элемента, отсортированных по уровням. В общем случае, чтобы формировать гибкие запросы, тип реализации свойства путь должен поддерживать сопоставление по шаблону в каком-либо виде. При денормализации пути в отдельную таблицу получается разновидность Closue Table.
Условия запросов на получение элементов заключается в применении предиката над свойством путь.
Преимущества
Возможность получения дочерних элементов любых уровней вложенности.
Возможность получения родительских элементов любых уровней с их иерархией относительно дочернего элемента.
Лёгкость вставки элемента.
Лёгкость удаления элемента.
Недостатки
Сложность изменения родителя для существующего элемента. Для всех дочерних элементов необходимо пересчитать новый путь.
Операции со свойством путь обычно происходят долго.
Пример
Рисунок 4.Элемент | Путь |
A |
|
B | A |
C | A B |
D | A B C |
E | A B |
Рассмотрим элемент «B»:
Чтобы получить все его дочерние элементы, нам необходимо выбрать элементы, удовлетворяющие условию:
путь содержит «B»
Чтобы получить его непосредственные дочерние элементы, нужно указать позицию, в которой содержится элемент. В примере путь отсортирован так, что последняя часть пути — это непосредственный родительский элемент:
последняя часть пути равна «B»
Заключение
Мы кратко рассмотрели основные паттерны хранения деревьев в реляционной базе данных. Их основные достоинства и недоставки, а также на примерах рассмотрели основные запросы к ним. В этой статье не были рассмотрены алгоритмы построения и заполнения метаданных деревьев, то есть операции добавления, обновления и удаления элементов.
Компоновщик
/ Паттерны проектирования / Структурные паттерны
Также известен как: Дерево, Composite
Суть паттернаКомпоновщик — это структурный паттерн проектирования, который позволяет сгруппировать множество объектов в древовидную структуру, а затем работать с ней так, как будто это единичный объект.
ПроблемаПаттерн Компоновщик имеет смысл только тогда, когда основная модель вашей программы может быть структурирована в виде дерева.
Например, есть два объекта: Продукт
и Коробка
. Коробка
может содержать несколько Продуктов
и других Коробок
поменьше. Те, в свою очередь, тоже содержат либо
, либо Коробки
и так далее.
Теперь предположим, ваши Продукты
и Коробки
могут быть частью заказов. Каждый заказ может содержать как простые Продукты
без упаковки, так и составные Коробки
. Ваша задача состоит в том, чтобы узнать цену всего заказа.
Заказ может состоять из различных продуктов, упакованных в собственные коробки.
Если решать задачу в лоб, то вам потребуется открыть все коробки заказа, перебрать все продукты и посчитать их суммарную стоимость. Но это слишком хлопотно, так как типы коробок и их содержимое могут быть вам неизвестны.
Компоновщик предлагает рассматривать Продукт
и Коробку
через единый интерфейс с общим методом получения стоимости.
Продукт
просто вернёт свою цену. Коробка
спросит цену каждого предмета внутри себя и вернёт сумму результатов. Если одним из внутренних предметов окажется коробка поменьше, она тоже будет перебирать своё содержимое, и так далее, пока не будут посчитаны все составные части.
Компоновщик рекурсивно запускает действие по всем элементам дерева — от корня к листьям.
Для вас, клиента, главное, что теперь не нужно ничего знать о структуре заказов. Вы вызываете метод получения цены, он возвращает цифру, а вы не тонете в горах картона и скотча.
Аналогия из жизниПример армейской структуры.
Армии большинства государств могут быть представлены в виде перевёрнутых деревьев. На нижнем уровне у вас есть солдаты, затем взводы, затем полки, а затем целые армии. Приказы отдаются сверху и спускаются вниз по структуре командования, пока не доходят до конкретного солдата.
СтруктураЛист — это простой компонент дерева, не имеющий ответвлений.
Из-за того, что им некому больше передавать выполнение, классы листьев будут содержать большую часть полезного кода.
Контейнер (или композит) — это составной компонент дерева. Он содержит набор дочерних компонентов, но ничего не знает об их типах. Это могут быть как простые компоненты-листья, так и другие компоненты-контейнеры. Но это не является проблемой, если все дочерние компоненты следуют единому интерфейсу.
Методы контейнера переадресуют основную работу своим дочерним компонентам, хотя и могут добавлять что-то своё к результату.
Клиент работает с деревом через общий интерфейс компонентов.
Благодаря этому, клиенту не важно, что перед ним находится — простой или составной компонент дерева.
В этом примере Компоновщик помогает реализовать вложенные геометрические фигуры.
Пример редактора геометрических фигур.
Класс CompoundGraphic
может содержать любое количество подфигур, включая такие же контейнеры, как он сам. Контейнер реализует те же методы, что и простые фигуры. Но, вместо непосредственного действия, он передаёт вызовы всем вложенным компонентам, используя рекурсию. Затем он как бы «суммирует» результаты всех вложенных фигур.
Клиентский код работает со всеми фигурами через общий интерфейс фигур и не знает, что перед ним — простая фигура или составная. Это позволяет клиентскому коду работать с деревьями объектов любой сложности, не привязываясь к конкретным классам объектов, формирующих дерево.
// Общий интерфейс компонентов.
interface Graphic is
method move(x, y)
method draw()
// Простой компонент.
class Dot implements Graphic is
field x, y
constructor Dot(x, y) { ... }
method move(x, y) is
this.x += x, this.y += y
method draw() is
// Нарисовать точку в координате X, Y.
// Компоненты могут расширять другие компоненты.
class Circle extends Dot is
field radius
constructor Circle(x, y, radius) { ... }
method draw() is
// Нарисовать окружность в координате X, Y и радиусом R.
// Контейнер содержит операции добавления/удаления дочерних
// компонентов. Все стандартные операции интерфейса компонентов
// он делегирует каждому из дочерних компонентов.
class CompoundGraphic implements Graphic is
field children: array of Graphic
method add(child: Graphic) is
// Добавить компонент в список дочерних.
method remove(child: Graphic) is
// Убрать компонент из списка дочерних.
method move(x, y) is
foreach (child in children) do
child. move(x, y)
method draw() is
// 1. Для каждого дочернего компонента:
// - Отрисовать компонент.
// - Определить координаты максимальной границы.
// 2. Нарисовать пунктирную границу вокруг всей области.
// Приложение работает единообразно как с единичными
// компонентами, так и с целыми группами компонентов.
class ImageEditor is
field all: CompoundGraphic
method load() is
all = new CompoundGraphic()
all.add(new Dot(1, 2))
all.add(new Circle(5, 3, 10))
// ...
// Группировка выбранных компонентов в один сложный
// компонент.
method groupSelected(components: array of Graphic) is
group = new CompoundGraphic()
foreach (component in components) do
group.add(component)
all.remove(component)
all.add(group)
// Все компоненты будут отрисованы.
all.draw()
Когда вам нужно представить древовидную структуру объектов.
Паттерн Компоновщик предлагает хранить в составных объектах ссылки на другие простые или составные объекты. Те, в свою очередь, тоже могут хранить свои вложенные объекты и так далее. В итоге вы можете строить сложную древовидную структуру данных, используя всего две основные разновидности объектов.
Когда клиенты должны единообразно трактовать простые и составные объекты.
Благодаря тому, что простые и составные объекты реализуют общий интерфейс, клиенту безразлично, с каким именно объектом ему предстоит работать.
Шаги реализацииУбедитесь, что вашу бизнес-логику можно представить как древовидную структуру. Попытайтесь разбить её на простые компоненты и контейнеры. Помните, что контейнеры могут содержать как простые компоненты, так и другие вложенные контейнеры.
Создайте общий интерфейс компонентов, который объединит операции контейнеров и простых компонентов дерева. Интерфейс будет удачным, если вы сможете использовать его, чтобы взаимозаменять простые и составные компоненты без потери смысла.
Создайте класс компонентов-листьев, не имеющих дальнейших ответвлений. Имейте в виду, что программа может содержать несколько таких классов.
Создайте класс компонентов-контейнеров и добавьте в него массив для хранения ссылок на вложенные компоненты. Этот массив должен быть способен содержать как простые, так и составные компоненты, поэтому убедитесь, что он объявлен с типом интерфейса компонентов.
Реализуйте в контейнере методы интерфейса компонентов, помня о том, что контейнеры должны делегировать основную работу своим дочерним компонентам.
Добавьте операции добавления и удаления дочерних компонентов в класс контейнеров.
Имейте в виду, что методы добавления/удаления дочерних компонентов можно поместить и в интерфейс компонентов. Да, это нарушит принцип разделения интерфейса, так как реализации методов будут пустыми в компонентах-листьях.
- Упрощает архитектуру клиента при работе со сложным деревом компонентов.
- Облегчает добавление новых видов компонентов.
- Создаёт слишком общий дизайн классов.
Строитель позволяет пошагово сооружать дерево Компоновщика.
Цепочку обязанностей часто используют вместе с Компоновщиком. В этом случае запрос передаётся от дочерних компонентов к их родителям.
Вы можете обходить дерево Компоновщика, используя Итератор.
Вы можете выполнить какое-то действие над всем деревом Компоновщика при помощи Посетителя.
Компоновщик часто совмещают с Легковесом, чтобы реализовать общие ветки дерева и сэкономить при этом память.
Компоновщик и Декоратор имеют похожие структуры классов из-за того, что оба построены на рекурсивной вложенности. Она позволяет связать в одну структуру бесконечное количество объектов.
Декоратор оборачивает только один объект, а узел Компоновщика может иметь много детей. Декоратор добавляет вложенному объекту новую функциональность, а Компоновщик не добавляет ничего нового, но «суммирует» результаты всех своих детей.
Но они могут и сотрудничать: Компоновщик может использовать Декоратор, чтобы переопределить функции отдельных частей дерева компонентов.
Архитектура, построенная на Компоновщиках и Декораторах, часто может быть улучшена за счёт внедрения Прототипа. Он позволяет клонировать сложные структуры объектов, а не собирать их заново.
Tree Patterns — Etsy Turkey
Etsy больше не поддерживает старые версии вашего веб-браузера, чтобы обеспечить безопасность пользовательских данных. Пожалуйста, обновите до последней версии.
Воспользуйтесь всеми преимуществами нашего сайта, включив JavaScript.
Найдите что-нибудь памятное, присоединяйтесь к сообществу, делающему добро.
( 1000+ релевантных результатов, с рекламой Продавцы, желающие расширить свой бизнес и привлечь больше заинтересованных покупателей, могут использовать рекламную платформу Etsy для продвижения своих товаров. Вы увидите результаты объявлений, основанные на таких факторах, как релевантность и сумма, которую продавцы платят за клик. Узнать больше. )Sage Country Christmas Tree Quilt Pattern LSC-0101 (adv beginner)
Search
Designers
Пожалуйста, выберитеAA — Ann AndersonABL — Наследие тети БарбABO — Brenda Wineman Designs — Brenda WinemanAC — Amarar Creacions — Catalina Barcelo MaimoAEQ — A Одеяла Унта Эма — Эмили БэйлиAQ — Ааа. .. Квилтинг — Синтия МьюирAV — Элисон ВандертангAW- Sweetgrass Creative Designs — Anne Wiens BCC — Black Cat Creations — Jackie Theriot BH — Barbara Huber DesignsBL2 — Beaquilter — Bea LeeCAC — Coral + Co. — Shelly MorganCC — Lorraine StangnessCCQ — Cuddle Cat Quiltworks — Solomae StoycoffCCUP — The Country Cupboard-Wool Whimsies — Leah SmithCDB — QuilterChic — Cheryl Daines BrownCF — Something Sew Fine Quilt Design — Cary FlanaganCJC — Castilleja Cotton — Diane McGregorCMQ — C McCourt Quilt Designs — Christina McCourtCQ — Counted Quilts — Lisa MuilenburgCQA — Одеяла для домиков — Кэти ТомасCQD — Дизайны лоскутных одеял Canuck — Joanne KertonCS — Carolyn StartupCTD — Дизайн хлопкового сокровища — Lidia FroehlerCTG — Дизайн лоскутных одеял для коттеджей — Rochelle MartinDBM — Design And Be Mary — Mary MittelstadtDCM — Творческие моменты Дебби — Debbie Caffrey & Charlotte AngottiDFD — Designs for Divas — Sandy ChambersDLP — Delightful Piecing — Barbara ClineESD — Epida Studio and Designs — Elizabeth DeCroosFCP — FatCat Patterns — Sindy RodenmayerFHD — Frog Hollow Designs — Linda HahnFRD — Fairfield Road Designs — Christine BakerGGA- Grannie «G» Applique — Geraldine Richardson GQ — Gourmet Quilter — SusanClaire MayfieldGR — Gail’s Quilts — Gail RuminskiGTD — Golden Thyme Designs & L and L Studios — Lacey J. HillHBH — HareBrained Happenings — Mary EegHCH — H. Corinne Hewitt Quilt Patterns — Corinne HewittHCL — HCL Designs — Heather LindquistHHQ — Одеяла с ежиком — Терри АльберсHQ — Highview QuiltWorks — Лоис ХатлебергJD — Джин ДаннJJS — Джеки ШаалJKD — Сумки для металлолома/Jamie Kalvestran Design — Джейми КалвестранJL — Сумки Back River — Джаси ЛоусонJMI — Просто мое воображение — Мэри КеррKB — Кей БаффингтонKBK — Комплекты Kalt — Genevieve KaltKCS -Karen Combs Studio — Karen CombsKG — Karen Gibbs — Karen Gibbs DesignsLLD — Little Louise Designs — Jude SperoLLS — L and L Studios — Lacey J Hill & Lorraine FreedLOB — LOBO — Loretta ShrinerLQC — Little Quilts — Mary Ellen Von HoltLSC — Laura’s Sage Country Quilts — Laura EstesMAM — Mary Ann SpragueMD — Marlous Designs — Marlous CarterME — Eddy & Eddy Designs — Martha EddyMF — Mary FurberMGD — Morning Glory Designs — Reeze LaLonde HansonMMD2 — More the Merrier Designs — Sylvia HardenMPQ — Masterpiece Quilting LLC — Nancy ScottMSP — Ms P Designs USA — Sharon Andersen/Susan HatcherNDD — Nancy Dill Designs — Nancy DillNJD — Nellie J. Designs — Janelle CeduskyNMD — Nancy Messuri Designs, LLC — Nancy MessuriNS — Needlesongs — Carol BruceNZP — Nancy Zieman — Nancy Zieman ProductionsOLQ — Одеяла из оливковых листьев — Кэтлин ХосравиПАД — Дизайны Presto Avenue — Claudia LashPC — Patti CareyPES — Paned Expressions Studios — Martha HansonPG — Puppy Girls Designs — Lynn E KanePGT — Patterns From Grandmas Trunk — Patricia FriederichPJB — Patterns by Jean Boyd — Jean BoydPLD — Pearl Louise Designs — Pearl Louise KrushPLP — Penny Lane Primitives — Kathryn HeckerPPP — Patterns Pumpkin Patch Patterns Beatrice RieskePQ — Pamela Quilts — Pamela BoatrightPRL — PRL Quilts — Joyce RileyPS — Purrfect Spots — Nan BakerPVQ — PineRose Designs — Eileen Hoheisel PYP — Pine Berry Patch — Gretchen HudockQBE — Quilts by Elena — Elena McDowellQFA — Quilted Fabric Arts — Carol McDowellQJK — Quiltinjeanie Designs — Jean KritenbrinkQLD — QuiltLily Designs — Karen PrattQN — The Quilt and Needle — Jessica SmithQW — QuiltWoman.comRAS — Ruthann StilwellRMT — Rainbow Moon Treasures — Sheri RectorRQS — Rumpled Quilt Skins — Kathy BarbroSCC — Sleeping Cat Creations — Patti LairdSCN — Spring Creek NeedleArt — Nancy RichouxSDD — Snow Day Designs — Margaret StoneSE- Schoolhouse Enterprises — Merry MaySEW — S.