簡體   English   中英

在閉包表分層數據結構中對子樹進行排序

[英]Sorting a subtree in a closure table hierarchical-data structure

我想請你幫我解決存儲為閉包表的分層數據結構的問題。

我想用這個結構來存儲我的網站菜單。 一切正常,但問題是我不知道如何按自定義順序對確切的子樹進行排序。 目前,樹按照項目添加到數據庫的順序排序。

我的結構基於Bill Karwin關於Closure Tables 的文章以及其他一些帖子。

這是我的MySQL數據庫結構,包含一些DEMO數據:

--
-- Table `category`
--

CREATE TABLE IF NOT EXISTS `category` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) COLLATE utf8_czech_ci NOT NULL,
  `active` tinyint(1) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;


INSERT INTO `category` (`id`, `name`, `active`) VALUES
(1, 'Cat 1', 1),
(2, 'Cat 2', 1),
(3, 'Cat  1.1', 1),
(4, 'Cat  1.1.1', 1),
(5, 'Cat 2.1', 1),
(6, 'Cat 1.2', 1),
(7, 'Cat 1.1.2', 1);

--
-- Table `category_closure`
--

CREATE TABLE IF NOT EXISTS `category_closure` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `ancestor` int(11) DEFAULT NULL,
  `descendant` int(11) DEFAULT NULL,
  `depth` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_category_closure_ancestor_category_id` (`ancestor`),
  KEY `fk_category_closure_descendant_category_id` (`descendant`)
) ENGINE=InnoDB;

INSERT INTO `category_closure` (`id`, `ancestor`, `descendant`, `depth`) VALUES
(1, 1, 1, 0),
(2, 2, 2, 0),
(3, 3, 3, 0),
(4, 1, 3, 1),
(5, 4, 4, 0),
(7, 3, 4, 1),
(8, 1, 4, 2),
(10, 6, 6, 0),
(11, 1, 6, 1),
(12, 7, 7, 0),
(13, 3, 7, 1),
(14, 1, 7, 2),
(16, 5, 5, 0),
(17, 2, 5, 1);

這是我對一棵樹的SELECT查詢:

SELECT c2.*, cc2.ancestor AS `_parent`
FROM category AS c1
JOIN category_closure AS cc1 ON (cc1.ancestor = c1.id)
JOIN category AS c2 ON (cc1.descendant = c2.id)
LEFT OUTER JOIN category_closure AS cc2 ON (cc2.descendant = c2.id AND cc2.depth = 1)
WHERE c1.id = __ROOT__ AND c1.active = 1
ORDER BY cc1.depth

對於查詢獲得__ROOT_ = 1的DEMO實例:

id  name        active     _parent
1   Cat 1       1          NULL
3   Cat 1.1     1          1
6   Cat 1.2     1          1
4   Cat 1.1.1   1          3
7   Cat 1.1.2   1          3

但是,如果我需要更改Cat 1.1和Cat 1.2的順序(根據名稱或某些自定義順序),該怎么辦?

我已經看到了一些面包屑解決方案(如何按面包屑排序),但我不知道如何生成和更改它們。

這個問題不僅經常出現在關閉表中,而且還出現在存儲分層數據的其他方法中。 在任何設計中都不容易。

我為Closure Table提出的解決方案涉及一個額外的連接。 樹中的每個節點都連接到其祖先的鏈,就像“breadcrumbs”類型的查詢一樣。 然后使用GROUP_CONCAT()將面包屑折疊為逗號分隔的字符串,按樹中的深度對id編號進行排序。 現在你有了一個可以排序的字符串。

SELECT c2.*, cc2.ancestor AS `_parent`,
  GROUP_CONCAT(breadcrumb.ancestor ORDER BY breadcrumb.depth DESC) AS breadcrumbs
FROM category AS c1
JOIN category_closure AS cc1 ON (cc1.ancestor = c1.id)
JOIN category AS c2 ON (cc1.descendant = c2.id)
LEFT OUTER JOIN category_closure AS cc2 ON (cc2.descendant = c2.id AND cc2.depth = 1)
JOIN category_closure AS breadcrumb ON (cc1.descendant = breadcrumb.descendant)
WHERE c1.id = 1/*__ROOT__*/ AND c1.active = 1
GROUP BY cc1.descendant
ORDER BY breadcrumbs;

+----+------------+--------+---------+-------------+
| id | name       | active | _parent | breadcrumbs |
+----+------------+--------+---------+-------------+
|  1 | Cat 1      |      1 |    NULL | 1           |
|  3 | Cat  1.1   |      1 |       1 | 1,3         |
|  4 | Cat  1.1.1 |      1 |       3 | 1,3,4       |
|  7 | Cat 1.1.2  |      1 |       3 | 1,3,7       |
|  6 | Cat 1.2    |      1 |       1 | 1,6         |
+----+------------+--------+---------+-------------+

注意事項:

  • id值應該具有統一的長度,因為排序“1,3”和“1,6”和“1,327”可能不會給出您想要的順序。 但排序“001,003”和“001,006”和“001,327”會。 所以,你要么需要在1000000+開始你的ID值,或者使用ZEROFILL在category_closure表祖先和后代。
  • 在此解決方案中,顯示順序取決於類別ID的數字順序。 id值的數字順序可能不表示您要顯示樹的順序。 或者您可能希望自由更改顯示順序,而不管數字ID值如何。 或者您可能希望相同的類別數據顯示在多個樹中,每個樹具有不同的顯示順序。
    如果您需要更多自由,則需要將id與值分開存儲,並且解決方案變得更加復雜。 但在大多數項目中,使用快捷方式是可以接受的,將類別id的雙重任務作為樹形顯示順序。

你的評論:

是的,您可以將“兄弟排序順序”存儲為閉包表中的另一列,然后使用該值而不是ancestor來構建breadcrumbs字符串。 但如果你這樣做,最終會產生大量的數據冗余。 也就是說,給定的祖先存儲在多個行中,每個行從其下降一個路徑。 因此,您必須在所有這些行上為兄弟排序順序存儲相同的值,這會產生異常的風險。

另一種方法是創建另一個表,與樹中的每個不同的祖先只有一行 ,並加入到該表以獲得同級次序。

CREATE TABLE category_closure_order (
  ancestor INT PRIMARY KEY,
  sibling_order SMALLINT UNSIGNED NOT NULL DEFAULT 1
);

SELECT c2.*, cc2.ancestor AS `_parent`,
  GROUP_CONCAT(o.sibling_order ORDER BY breadcrumb.depth DESC) AS breadcrumbs
FROM category AS c1
JOIN category_closure AS cc1 ON (cc1.ancestor = c1.id)
JOIN category AS c2 ON (cc1.descendant = c2.id)
LEFT OUTER JOIN category_closure AS cc2 ON (cc2.descendant = c2.id AND cc2.depth = 1)
JOIN category_closure AS breadcrumb ON (cc1.descendant = breadcrumb.descendant)
JOIN category_closure_order AS o ON breadcrumb.ancestor = o.ancestor
WHERE c1.id = 1/*__ROOT__*/ AND c1.active = 1
GROUP BY cc1.descendant
ORDER BY breadcrumbs;

+----+------------+--------+---------+-------------+
| id | name       | active | _parent | breadcrumbs |
+----+------------+--------+---------+-------------+
|  1 | Cat 1      |      1 |    NULL | 1           |
|  3 | Cat  1.1   |      1 |       1 | 1,1         |
|  4 | Cat  1.1.1 |      1 |       3 | 1,1,1       |
|  7 | Cat 1.1.2  |      1 |       3 | 1,1,2       |
|  6 | Cat 1.2    |      1 |       1 | 1,2         |
+----+------------+--------+---------+-------------+

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM