-
Notifications
You must be signed in to change notification settings - Fork 20
ru_Tutorial4
Существует несколько способов отображения древовидных структур в реляционные базы данных. Вот один из них:
-
все элементы дерева хранятся в одной таблице;
-
у каждой записи может быть ссылка на родительскую;
-
необходимо контролировать отсутствие циклов.
В следующей схеме описана иерархия номенклатуры продуктов и групп продуктов.
<schema>
<table name="tbl_product_group" sequence="seq_prod_group"
class="ProductGroup" xml-name="product-group">
<column name="id" type="longint">
<primary-key />
</column>
<column name="parent_id" type="longint">
<foreign-key table="tbl_product_group"/>
</column>
<column name="name" type="string" size="100"
null="false" />
</table>
<table name="tbl_product" sequence="seq_prod_group"
class="Product" xml-name="product">
<column name="id" type="longint">
<primary-key />
</column>
<column name="parent_id" type="longint">
<foreign-key table="tbl_product_group"/>
</column>
<column name="name" type="string" size="100"
null="false" />
<column name="price" type="decimal" null="false" />
</table>
<relation type="one-to-many" cascade="delete">
<one class="ProductGroup" property="children" />
<many class="ProductGroup" property="parent" />
</relation>
<relation type="one-to-many" cascade="delete">
<one class="ProductGroup" property="products" />
<many class="Product" property="parent" />
</relation>
</schema>
Видим, что таблица tbl_product_group
ссылается внешним ключом на саму себя.
При описании отношения «один-ко-многим» мы указали в качестве обеих сторон
этого отношения один и тот же класс.
При попытке удаления родительской записи, имеющей дочерние записи, возможны несколько вариантов сохранения целостности базы:
-
запретить удаление родительской записи, поскольку на неё есть ссылки;
-
удалить дочерние записи, так чтобы ссылок не осталось;
-
сохранить дочерние записи, но ссылки установить в NULL.
Как правило, поведением по умолчанию является вариант 1. Чтобы разрешить
каскадное удаление ссылающихся записей, атрибут отношения cascade
установлен
здесь в значение delete
.
Следующий фрагмент кода создаёт дерево из четырёх элементов:
Session session(Yb::theSchema::instance(), &engine);
ProductGroup::Holder pg1(session);
pg1->name = "Group1";
ProductGroup::Holder pg2(session);
pg2->name = "Group2";
pg2->parent = pg1;
ProductGroup::Holder pg3(session);
pg3->name = "Group3";
pg3->parent = pg1;
Product::Holder pr1(session);
pr1->name = "Product1";
pr1->price = Decimal("1.00");
pr1->parent = pg2;
session.commit();
root = pg1->id;
Лог сессии, используется диалект SQLITE:
orm: flush started
sql: begin transaction
sql: prepare: INSERT INTO tbl_product_group (parent_id, name) VALUES (?, ?)
sql: bind: (LongInt, String)
sql: exec prepared: p1="NULL" p2="'Group1'"
sql: prepare: SELECT SEQ LID FROM SQLITE_SEQUENCE WHERE NAME = 'tbl_product_group'
sql: exec prepared:
sql: fetch: LID='1'
sql: fetch: no more rows
sql: prepare: INSERT INTO tbl_product_group (parent_id, name) VALUES (?, ?)
sql: bind: (LongInt, String)
sql: exec prepared: p1="1" p2="'Group2'"
sql: prepare: SELECT SEQ LID FROM SQLITE_SEQUENCE WHERE NAME = 'tbl_product_group'
sql: exec prepared:
sql: fetch: LID='2'
sql: fetch: no more rows
sql: exec prepared: p1="1" p2="'Group3'"
sql: prepare: SELECT SEQ LID FROM SQLITE_SEQUENCE WHERE NAME = 'tbl_product_group'
sql: exec prepared:
sql: fetch: LID='3'
sql: fetch: no more rows
sql: prepare: INSERT INTO tbl_product (parent_id, name, price) VALUES (?, ?, ?)
sql: bind: (LongInt, String, Decimal)
sql: exec prepared: p1="2" p2="'Product1'" p3="1"
sql: prepare: SELECT SEQ LID FROM SQLITE_SEQUENCE WHERE NAME = 'tbl_product'
sql: exec prepared:
sql: fetch: LID='1'
sql: fetch: no more rows
orm: flush finished OK
sql: commit
Получится вот такое дерево:
Следующий фрагмент показывает каскадное удаление созданной иерархии:
Session session(Yb::theSchema::instance(), &engine);
ProductGroup::Holder pg1(session, root);
cout << pg1->parent->id.is_null() << endl;
pg1->delete_object();
session.commit();
Лог сессии удаления, используется диалект SQLITE:
sql: prepare: SELECT tbl_product_group.id, tbl_product_group.parent_id, tbl_product_group.name FROM tbl_product_group WHERE tbl_product_group.id = ?
sql: exec prepared: p1="1"
sql: fetch: ID='1' PARENT_ID=NULL NAME='Group1'
sql: fetch: no more rows
orm: delete_object mode=0 depth=0 status=3
sql: prepare: SELECT tbl_product_group.id, tbl_product_group.parent_id, tbl_product_group.name FROM tbl_product_group WHERE tbl_product_group.parent_id = ?
sql: exec prepared: p1="1"
sql: fetch: ID='2' PARENT_ID='1' NAME='Group2'
sql: fetch: ID='3' PARENT_ID='1' NAME='Group3'
sql: fetch: no more rows
orm: delete_object mode=1 depth=1 status=3
sql: prepare: SELECT tbl_product_group.id, tbl_product_group.parent_id, tbl_product_group.name FROM tbl_product_group WHERE tbl_product_group.parent_id = ?
sql: exec prepared: p1="2"
sql: fetch: no more rows
sql: prepare: SELECT tbl_product.id, tbl_product.parent_id, tbl_product.name, tbl_product.price FROM tbl_product WHERE tbl_product.parent_id = ?
sql: exec prepared: p1="2"
sql: fetch: ID='1' PARENT_ID='2' NAME='Product1' PRICE='1'
sql: fetch: no more rows
orm: delete_object mode=1 depth=2 status=3
orm: delete_object mode=1 depth=1 status=3
sql: prepare: SELECT tbl_product_group.id, tbl_product_group.parent_id, tbl_product_group.name FROM tbl_product_group WHERE tbl_product_group.parent_id = ?
sql: exec prepared: p1="3"
sql: fetch: no more rows
sql: prepare: SELECT tbl_product.id, tbl_product.parent_id, tbl_product.name, tbl_product.price FROM tbl_product WHERE tbl_product.parent_id = ?
sql: exec prepared: p1="3"
sql: fetch: no more rows
sql: prepare: SELECT tbl_product.id, tbl_product.parent_id, tbl_product.name, tbl_product.price FROM tbl_product WHERE tbl_product.parent_id = ?
sql: exec prepared: p1="1"
sql: fetch: no more rows
orm: delete_object mode=2 depth=1 status=3
orm: delete_object mode=2 depth=2 status=3
orm: flush started
orm: flush_delete: depth: 3
orm: flush_delete: table: tbl_product
sql: begin transaction
sql: prepare: DELETE FROM tbl_product WHERE tbl_product.id = ?
sql: bind: (LongInt)
sql: exec prepared: p1="1"
orm: flush_delete: depth: 2
orm: flush_delete: table: tbl_product_group
sql: prepare: DELETE FROM tbl_product_group WHERE tbl_product_group.id = ?
sql: bind: (LongInt)
sql: exec prepared: p1="2"
sql: exec prepared: p1="3"
orm: flush_delete: depth: 1
orm: flush_delete: table: tbl_product_group
sql: prepare: DELETE FROM tbl_product_group WHERE tbl_product_group.id = ?
sql: bind: (LongInt)
sql: exec prepared: p1="1"
orm: flush_delete: depth: 0
orm: flush finished OK
sql: commit
Как видим, YB.ORM прослеживает связи по дереву до самых листьев и начинает удаление с именно с них.