Skip to content
Viacheslav Naydenov edited this page Jul 17, 2014 · 1 revision

Отображение иерархических структур (tut4.cpp)

Существует несколько способов отображения древовидных структур в реляционные базы данных. Вот один из них:

  • все элементы дерева хранятся в одной таблице;

  • у каждой записи может быть ссылка на родительскую;

  • необходимо контролировать отсутствие циклов.

В следующей схеме описана иерархия номенклатуры продуктов и групп продуктов.

<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 ссылается внешним ключом на саму себя. При описании отношения «один-ко-многим» мы указали в качестве обеих сторон этого отношения один и тот же класс.

При попытке удаления родительской записи, имеющей дочерние записи, возможны несколько вариантов сохранения целостности базы:

  1. запретить удаление родительской записи, поскольку на неё есть ссылки;

  2. удалить дочерние записи, так чтобы ссылок не осталось;

  3. сохранить дочерние записи, но ссылки установить в 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 прослеживает связи по дереву до самых листьев и начинает удаление с именно с них.

Clone this wiki locally