Các tùy chọn để lưu trữ dữ liệu phân cấp trong cơ sở dữ liệu quan hệ là gì? [đóng cửa]


1333

Tổng quan tốt

Nói chung, bạn đang đưa ra quyết định giữa thời gian đọc nhanh (ví dụ: tập hợp lồng nhau) hoặc thời gian viết nhanh (danh sách kề). Thông thường, bạn kết thúc với sự kết hợp của các tùy chọn bên dưới phù hợp nhất với nhu cầu của bạn. Sau đây cung cấp một số đọc sâu:

Tùy chọn

Những người tôi biết và các đặc điểm chung:

  1. Danh sách điều chỉnh :
    • Cột: ID, ParentID
    • Dễ để thực hiện.
    • Nút di chuyển giá rẻ, chèn và xóa.
    • Đắt tiền để tìm cấp độ, tổ tiên & con cháu, con đường
    • Tránh N + 1 thông qua Biểu thức bảng chung trong cơ sở dữ liệu hỗ trợ chúng
  2. Nested Set (còn gọi là Traversal Preorder Tree Traversal )
    • Cột: Trái, Phải
    • Tổ tiên giá rẻ, con cháu
    • Di O(n/2)chuyển rất tốn kém , chèn, xóa do mã hóa dễ bay hơi
  3. Bảng cầu (còn gọi là Bảng đóng / kích hoạt w )
    • Sử dụng bảng tham gia riêng biệt với: tổ tiên, hậu duệ, độ sâu (tùy chọn)
    • Tổ tiên và con cháu giá rẻ
    • Ghi chi phí O(log n)(kích thước của cây con) để chèn, cập nhật, xóa
    • Mã hóa chuẩn hóa: tốt cho thống kê RDBMS và công cụ lập kế hoạch truy vấn khi tham gia
    • Yêu cầu nhiều hàng trên mỗi nút
  4. Cột Lineage (còn gọi là Đường dẫn cụ thể hóa , liệt kê đường dẫn)
    • Cột: dòng dõi (ví dụ / cha mẹ / con / cháu / v.v ...)
    • Hậu duệ giá rẻ thông qua truy vấn tiền tố (ví dụ LEFT(lineage, #) = '/enumerated/path')
    • Ghi chi phí O(log n)(kích thước của cây con) để chèn, cập nhật, xóa
    • Không liên quan: dựa trên kiểu dữ liệu mảng hoặc định dạng chuỗi nối tiếp
  5. Khoảng cách lồng nhau
    • Giống như tập hợp lồng nhau, nhưng với thực / float / thập phân để mã hóa không biến động (di chuyển / chèn / xóa không tốn kém)
    • Có vấn đề thực / nổi / thập phân / chính xác
    • Biến thể mã hóa ma trận thêm mã hóa tổ tiên (đường dẫn cụ thể hóa) cho "miễn phí", nhưng có thêm độ khó của đại số tuyến tính.
  6. Bàn phẳng
    • Danh sách điều chỉnh đã được sửa đổi có thêm cột Cấp và Xếp hạng (ví dụ: đặt hàng) cho mỗi bản ghi.
    • Giá rẻ để lặp / phân trang
    • Di chuyển và xóa tốn kém
    • Sử dụng tốt: thảo luận theo luồng - diễn đàn / bình luận blog
  7. Nhiều cột dòng
    • Cột: một cho mỗi cấp độ dòng dõi, đề cập đến tất cả các bậc cha mẹ cho đến gốc, các cấp độ từ cấp độ của vật phẩm được đặt thành NULL
    • Tổ tiên giá rẻ, con cháu, đẳng cấp
    • Giá rẻ chèn, xóa, di chuyển của lá
    • Đắt tiền, xóa, di chuyển các nút bên trong
    • Giới hạn cứng cho việc phân cấp có thể sâu đến mức nào

Cơ sở dữ liệu ghi chú

MySQL

Oracle

  • Sử dụng CONNECT BY để duyệt qua Danh sách điều chỉnh

PostgreSQL

Máy chủ SQL

  • Tóm tắt chung
  • 2008 cung cấp kiểu dữ liệu HVELyId xuất hiện để trợ giúp với cách tiếp cận Cột Lineage và mở rộng độ sâu có thể được biểu diễn.

5
Theo sl slideshoware.net/billkarwin/sql-antipotypes-strike-back trang 77, Closure Tablesvượt trội hơn Adjacency List, Path EnumerationNested Setsvề mặt dễ sử dụng (và tôi cũng đoán hiệu suất).
Gili

Tôi nhớ một phiên bản rất đơn giản ở đây: một BLOB đơn giản. Nếu hệ thống phân cấp của bạn chỉ có một vài mục dozend, cây id được xê-ri hóa có thể là lựa chọn tốt nhất.
Lothar

@Lothar: câu hỏi là một wiki cộng đồng vì vậy hãy thoải mái để có nó. Suy nghĩ của tôi về vấn đề đó là tôi sẽ chỉ làm điều đó với những cơ sở dữ liệu hỗ trợ một số loại cấu trúc blob như XML với ngôn ngữ truy vấn ổn định như XPATH. Mặt khác, tôi không thấy một cách tốt để truy vấn ngoài việc truy xuất, giải tuần tự hóa và munge trong mã, không phải SQL. Và nếu bạn thực sự gặp vấn đề khi cần nhiều yếu tố tùy ý, bạn có thể sử dụng cơ sở dữ liệu Node như Neo4J mà tôi đã sử dụng và thích, mặc dù chưa bao giờ được đưa vào sản xuất.
đười ươi


2
Liên kết MSDN cho "Tóm tắt chung" không còn hiển thị bài viết. Đó là trong ấn bản tháng 9 năm 2008 của Tạp chí MSDN, bạn có thể tải xuống dưới dạng tệp CHM hoặc xem qua kho lưu trữ web tại: web.archive.org/web/20080913041559/http://msdn.microsoft.com:80/ ...
Kemp ͩ

Câu trả lời:


66

Câu trả lời yêu thích của tôi là những gì câu đầu tiên trong chủ đề này gợi ý. Sử dụng Danh sách điều chỉnh để duy trì cấu trúc phân cấp và sử dụng Bộ lồng nhau để truy vấn cấu trúc phân cấp.

Vấn đề cho đến nay là phương pháp che phủ từ Danh sách điều chỉnh cho các bộ lồng nhau đã bị chậm một cách khủng khiếp vì hầu hết mọi người sử dụng phương pháp RBAR cực đoan được gọi là "Đẩy ngăn xếp" để thực hiện chuyển đổi và được coi là đắt tiền để đạt đến Niết bàn về sự đơn giản của việc bảo trì bởi Danh sách điều chỉnh và hiệu suất tuyệt vời của Bộ lồng. Kết quả là, hầu hết mọi người cuối cùng phải giải quyết cho cái này hay cái khác, đặc biệt là nếu có nhiều hơn 100.000 nút tệ hại. Sử dụng phương pháp ngăn xếp đẩy có thể mất cả ngày để thực hiện chuyển đổi trên những gì MLM'ers sẽ coi là một hệ thống phân cấp nút nhỏ.

Tôi nghĩ rằng tôi sẽ cho Celko một chút cạnh tranh bằng cách đưa ra một phương pháp để chuyển đổi Danh sách điều chỉnh sang các bộ Nested với tốc độ dường như không thể. Đây là hiệu suất của phương pháp ngăn xếp đẩy trên máy tính xách tay i5 của tôi.

Duration for     1,000 Nodes = 00:00:00:870 
Duration for    10,000 Nodes = 00:01:01:783 (70 times slower instead of just 10)
Duration for   100,000 Nodes = 00:49:59:730 (3,446 times slower instead of just 100) 
Duration for 1,000,000 Nodes = 'Didn't even try this'

Và đây là thời lượng cho phương thức mới (với phương pháp ngăn xếp đẩy trong ngoặc đơn).

Duration for     1,000 Nodes = 00:00:00:053 (compared to 00:00:00:870)
Duration for    10,000 Nodes = 00:00:00:323 (compared to 00:01:01:783)
Duration for   100,000 Nodes = 00:00:03:867 (compared to 00:49:59:730)
Duration for 1,000,000 Nodes = 00:00:54:283 (compared to something like 2 days!!!)

Vâng đúng rồi. 1 triệu nút được chuyển đổi trong vòng chưa đầy một phút và 100.000 nút trong vòng dưới 4 giây.

Bạn có thể đọc về phương thức mới và nhận một bản sao của mã tại URL sau. http://www.sqlservercentral.com/articles/HVELy/94040/

Tôi cũng đã phát triển một hệ thống phân cấp "tổng hợp trước" bằng các phương pháp tương tự. MLM'ers và những người lập hóa đơn vật liệu sẽ đặc biệt quan tâm đến bài viết này. http://www.sqlservercentral.com/articles/T-SQL/94570/

Nếu bạn dừng lại để xem một trong hai bài viết, hãy nhảy vào liên kết "Tham gia thảo luận" và cho tôi biết bạn nghĩ gì.


MLMer là gì?
David Mann

MLM = "Tiếp thị đa cấp". Amway, Shaklee, ACN, v.v.
Jeff Moden

31

Đây là một câu trả lời rất một phần cho câu hỏi của bạn, nhưng tôi hy vọng vẫn hữu ích.

Microsoft SQL Server 2008 triển khai hai tính năng cực kỳ hữu ích để quản lý dữ liệu phân cấp:

  • các HierarchyId kiểu dữ liệu.
  • biểu thức bảng phổ biến, sử dụng với từ khóa.

Hãy xem "Mô hình phân cấp dữ liệu của bạn với SQL Server 2008" của Kent Tegels trên MSDN để bắt đầu. Xem thêm câu hỏi của riêng tôi: Truy vấn cùng bảng đệ quy trong SQL Server 2008


2
Thật thú vị, HVELyId, không biết gì về điều đó: msdn.microsoft.com/en-us/l
Library / bb677290.aspx

1
Thật. Tôi làm việc với nhiều dữ liệu phân cấp đệ quy và tôi thấy các biểu thức bảng chung cực kỳ hữu ích. Xem msdn.microsoft.com/en-us/l Library / ms186243.aspx để biết phần giới thiệu.
CesarGon

28

Thiết kế này chưa được đề cập:

Nhiều cột dòng

Mặc dù nó có những hạn chế, nhưng nếu bạn có thể chịu đựng được, nó rất đơn giản và rất hiệu quả. Đặc trưng:

  • Cột: một cho mỗi cấp độ dòng, đề cập đến tất cả các bậc cha mẹ cho đến gốc, các mức dưới mức của các mục hiện tại được đặt thành 0 (hoặc NULL)
  • Có một giới hạn cố định về mức độ phân cấp có thể sâu
  • Tổ tiên giá rẻ, con cháu, đẳng cấp
  • Giá rẻ chèn, xóa, di chuyển của lá
  • Đắt tiền, xóa, di chuyển các nút bên trong

Dưới đây là một ví dụ - cây phân loại chim, vì vậy hệ thống phân cấp là Class / Order / Family / Genus / Species - loài là cấp thấp nhất, 1 hàng = 1 taxon (tương ứng với các loài trong trường hợp của các nút lá):

CREATE TABLE `taxons` (
  `TaxonId` smallint(6) NOT NULL default '0',
  `ClassId` smallint(6) default NULL,
  `OrderId` smallint(6) default NULL,
  `FamilyId` smallint(6) default NULL,
  `GenusId` smallint(6) default NULL,
  `Name` varchar(150) NOT NULL default ''
);

và ví dụ về dữ liệu:

+---------+---------+---------+----------+---------+-------------------------------+
| TaxonId | ClassId | OrderId | FamilyId | GenusId | Name                          |
+---------+---------+---------+----------+---------+-------------------------------+
|     254 |       0 |       0 |        0 |       0 | Aves                          |
|     255 |     254 |       0 |        0 |       0 | Gaviiformes                   |
|     256 |     254 |     255 |        0 |       0 | Gaviidae                      |
|     257 |     254 |     255 |      256 |       0 | Gavia                         |
|     258 |     254 |     255 |      256 |     257 | Gavia stellata                |
|     259 |     254 |     255 |      256 |     257 | Gavia arctica                 |
|     260 |     254 |     255 |      256 |     257 | Gavia immer                   |
|     261 |     254 |     255 |      256 |     257 | Gavia adamsii                 |
|     262 |     254 |       0 |        0 |       0 | Podicipediformes              |
|     263 |     254 |     262 |        0 |       0 | Podicipedidae                 |
|     264 |     254 |     262 |      263 |       0 | Tachybaptus                   |

Điều này thật tuyệt vời vì cách này bạn hoàn thành tất cả các thao tác cần thiết một cách rất dễ dàng, miễn là các danh mục nội bộ không thay đổi cấp độ của chúng trong cây.


22

Mô hình điều chỉnh + Mô hình bộ lồng nhau

Tôi đã tìm nó vì tôi có thể chèn các mục mới vào cây một cách dễ dàng (bạn chỉ cần id của một nhánh để chèn một mục mới vào nó) và cũng truy vấn nó khá nhanh.

+-------------+----------------------+--------+-----+-----+
| category_id | name                 | parent | lft | rgt |
+-------------+----------------------+--------+-----+-----+
|           1 | ELECTRONICS          |   NULL |   1 |  20 |
|           2 | TELEVISIONS          |      1 |   2 |   9 |
|           3 | TUBE                 |      2 |   3 |   4 |
|           4 | LCD                  |      2 |   5 |   6 |
|           5 | PLASMA               |      2 |   7 |   8 |
|           6 | PORTABLE ELECTRONICS |      1 |  10 |  19 |
|           7 | MP3 PLAYERS          |      6 |  11 |  14 |
|           8 | FLASH                |      7 |  12 |  13 |
|           9 | CD PLAYERS           |      6 |  15 |  16 |
|          10 | 2 WAY RADIOS         |      6 |  17 |  18 |
+-------------+----------------------+--------+-----+-----+
  • Mỗi khi bạn cần tất cả con cái của bất kỳ cha mẹ nào, bạn chỉ cần truy vấn parent cột.
  • Nếu bạn cần tất cả con cháu của bất kỳ cha mẹ nào, bạn truy vấn các mục có lftgiữa chúng lftrgtcủa cha mẹ.
  • Nếu bạn cần tất cả cha mẹ của bất kỳ nút nào cho đến gốc của cây, bạn truy vấn các mục có lftgiá trị thấp hơn nút lftrgtlớn hơn nút đó rgtvà sắp xếp theo parent.

Tôi cần phải truy cập và truy vấn cây nhanh hơn chèn, đó là lý do tại sao tôi chọn cái này

Vấn đề duy nhất là sửa lỗi leftrightcột khi chèn các mục mới. Tôi đã tạo một thủ tục được lưu trữ cho nó và gọi nó mỗi khi tôi chèn một mục mới rất hiếm trong trường hợp của tôi nhưng nó thực sự nhanh. Tôi đã có ý tưởng từ cuốn sách của Joe Celko, và quy trình được lưu trữ và cách tôi đưa ra nó được giải thích ở đây trong DBA SE https://dba.stackexchange.com/q/89051/41481


3
+1 đây là một cách tiếp cận hợp pháp. Theo kinh nghiệm của riêng tôi, chìa khóa sẽ quyết định xem bạn có ổn với việc đọc bẩn khi các hoạt động cập nhật lớn xảy ra hay không. Nếu không, nó trở thành vấn đề hoặc ngăn mọi người truy vấn trực tiếp các bảng và luôn luôn thông qua API - hàm sprocs / hàm hoặc mã.
đười ươi

1
Đây là một giải pháp thú vị; tuy nhiên, tôi không chắc chắn việc truy vấn cột cha mẹ thực sự mang lại bất kỳ lợi thế lớn nào khi cố gắng tìm con - đó là lý do tại sao chúng ta có cột trái và cột phải, ở vị trí đầu tiên.
Thomas

2
@Thomas, có một sự khác biệt giữa childrendescendants. leftrightđược sử dụng để tìm con cháu.
azerafati 17/03/2016

14

Nếu cơ sở dữ liệu của bạn hỗ trợ các mảng, bạn cũng có thể triển khai một cột dòng hoặc đường dẫn cụ thể hóa như một mảng của các id cha.

Cụ thể với Postgres, sau đó bạn có thể sử dụng các toán tử được thiết lập để truy vấn cấu trúc phân cấp và có được hiệu suất tuyệt vời với các chỉ số GIN. Điều này làm cho việc tìm kiếm cha mẹ, con cái và độ sâu khá nhỏ trong một truy vấn duy nhất. Cập nhật cũng khá dễ quản lý.

Tôi có một bài viết đầy đủ về việc sử dụng các mảng cho các đường dẫn cụ thể hóa nếu bạn tò mò.


9

Đây thực sự là một chốt vuông, câu hỏi lỗ tròn.

Nếu cơ sở dữ liệu quan hệ và SQL là búa duy nhất bạn có hoặc sẵn sàng sử dụng, thì câu trả lời đã được đăng cho đến nay là đầy đủ. Tuy nhiên, tại sao không sử dụng một công cụ được thiết kế để xử lý dữ liệu phân cấp? Cơ sở dữ liệu đồ thị là lý tưởng cho dữ liệu phân cấp phức tạp.

Sự không hiệu quả của mô hình quan hệ cùng với sự phức tạp của bất kỳ giải pháp mã / truy vấn nào để ánh xạ mô hình đồ thị / phân cấp lên mô hình quan hệ chỉ là không đáng để nỗ lực khi so sánh với sự dễ dàng mà giải pháp cơ sở dữ liệu đồ thị có thể giải quyết cùng một vấn đề.

Hãy xem xét một Bill of Vật liệu như một cấu trúc dữ liệu phân cấp phổ biến.

class Component extends Vertex {
    long assetId;
    long partNumber;
    long material;
    long amount;
};

class PartOf extends Edge {
};

class AdjacentTo extends Edge {
};

Con đường ngắn nhất giữa hai cụm phụ : Thuật toán duyệt đồ thị đơn giản. Đường dẫn có thể chấp nhận có thể đủ điều kiện dựa trên các tiêu chí.

Độ tương tự : Mức độ tương đồng giữa hai cụm là gì? Thực hiện một giao dịch trên cả hai cây con tính toán giao điểm và liên kết của hai cây con. Phần trăm tương tự là giao điểm chia cho liên minh.

Sự đóng kín : Đi bộ cây con và tổng hợp (các) trường quan tâm, ví dụ: "Bao nhiêu nhôm trong một cụm lắp ráp phụ?"

Có, bạn có thể giải quyết vấn đề với SQL và cơ sở dữ liệu quan hệ. Tuy nhiên, có nhiều cách tiếp cận tốt hơn nếu bạn sẵn sàng sử dụng công cụ phù hợp cho công việc.


5
Câu trả lời này sẽ vô cùng hữu ích hơn nếu các trường hợp sử dụng được trình bày hoặc tương phản tốt hơn, làm thế nào để truy vấn cơ sở dữ liệu đồ thị với SPARQL chẳng hạn thay vì SQL trong RDBMS.
đười ươi

1
SPARQL có liên quan đến cơ sở dữ liệu RDF là một lớp con của miền cơ sở dữ liệu đồ thị lớn hơn. Tôi làm việc với InfiniteGraph không phải là cơ sở dữ liệu RDF và hiện không hỗ trợ SPARQL. InfiniteGraph hỗ trợ một số cơ chế truy vấn khác nhau: (1) API điều hướng biểu đồ để thiết lập chế độ xem, bộ lọc, vòng loại đường dẫn và trình xử lý kết quả, (2) ngôn ngữ khớp mẫu biểu đồ đường dẫn phức tạp và (3) Gremlin.
djhallx

6

Tôi đang sử dụng PostgreSQL với các bảng đóng cho hệ thống phân cấp của mình. Tôi có một thủ tục lưu trữ chung cho toàn bộ cơ sở dữ liệu:

CREATE FUNCTION nomen_tree() RETURNS trigger
    LANGUAGE plpgsql
    AS $_$
DECLARE
  old_parent INTEGER;
  new_parent INTEGER;
  id_nom INTEGER;
  txt_name TEXT;
BEGIN
-- TG_ARGV[0] = name of table with entities with PARENT-CHILD relationships (TBL_ORIG)
-- TG_ARGV[1] = name of helper table with ANCESTOR, CHILD, DEPTH information (TBL_TREE)
-- TG_ARGV[2] = name of the field in TBL_ORIG which is used for the PARENT-CHILD relationship (FLD_PARENT)
    IF TG_OP = 'INSERT' THEN
    EXECUTE 'INSERT INTO ' || TG_ARGV[1] || ' (child_id,ancestor_id,depth) 
        SELECT $1.id,$1.id,0 UNION ALL
      SELECT $1.id,ancestor_id,depth+1 FROM ' || TG_ARGV[1] || ' WHERE child_id=$1.' || TG_ARGV[2] USING NEW;
    ELSE                                                           
    -- EXECUTE does not support conditional statements inside
    EXECUTE 'SELECT $1.' || TG_ARGV[2] || ',$2.' || TG_ARGV[2] INTO old_parent,new_parent USING OLD,NEW;
    IF COALESCE(old_parent,0) <> COALESCE(new_parent,0) THEN
      EXECUTE '
      -- prevent cycles in the tree
      UPDATE ' || TG_ARGV[0] || ' SET ' || TG_ARGV[2] || ' = $1.' || TG_ARGV[2]
        || ' WHERE id=$2.' || TG_ARGV[2] || ' AND EXISTS(SELECT 1 FROM '
        || TG_ARGV[1] || ' WHERE child_id=$2.' || TG_ARGV[2] || ' AND ancestor_id=$2.id);
      -- first remove edges between all old parents of node and its descendants
      DELETE FROM ' || TG_ARGV[1] || ' WHERE child_id IN
        (SELECT child_id FROM ' || TG_ARGV[1] || ' WHERE ancestor_id = $1.id)
        AND ancestor_id IN
        (SELECT ancestor_id FROM ' || TG_ARGV[1] || ' WHERE child_id = $1.id AND ancestor_id <> $1.id);
      -- then add edges for all new parents ...
      INSERT INTO ' || TG_ARGV[1] || ' (child_id,ancestor_id,depth) 
        SELECT child_id,ancestor_id,d_c+d_a FROM
        (SELECT child_id,depth AS d_c FROM ' || TG_ARGV[1] || ' WHERE ancestor_id=$2.id) AS child
        CROSS JOIN
        (SELECT ancestor_id,depth+1 AS d_a FROM ' || TG_ARGV[1] || ' WHERE child_id=$2.' 
        || TG_ARGV[2] || ') AS parent;' USING OLD, NEW;
    END IF;
  END IF;
  RETURN NULL;
END;
$_$;

Sau đó, với mỗi bảng nơi tôi có cấu trúc phân cấp, tôi tạo một trình kích hoạt

CREATE TRIGGER nomenclature_tree_tr AFTER INSERT OR UPDATE ON nomenclature FOR EACH ROW EXECUTE PROCEDURE nomen_tree('my_db.nomenclature', 'my_db.nom_helper', 'parent_id');

Để điền vào bảng đóng từ hệ thống phân cấp hiện có, tôi sử dụng quy trình được lưu trữ này:

CREATE FUNCTION rebuild_tree(tbl_base text, tbl_closure text, fld_parent text) RETURNS void
    LANGUAGE plpgsql
    AS $$
BEGIN
    EXECUTE 'TRUNCATE ' || tbl_closure || ';
    INSERT INTO ' || tbl_closure || ' (child_id,ancestor_id,depth) 
        WITH RECURSIVE tree AS
      (
        SELECT id AS child_id,id AS ancestor_id,0 AS depth FROM ' || tbl_base || '
        UNION ALL 
        SELECT t.id,ancestor_id,depth+1 FROM ' || tbl_base || ' AS t
        JOIN tree ON child_id = ' || fld_parent || '
      )
      SELECT * FROM tree;';
END;
$$;

Các bảng đóng được xác định với 3 cột - ANCESTOR_ID, DESCENDANT_ID, DEPTH. Có thể (và tôi thậm chí còn khuyên) lưu trữ các bản ghi có cùng giá trị cho ANCESTOR và DESCENDANT và giá trị bằng 0 cho DEPTH. Điều này sẽ đơn giản hóa các truy vấn để lấy thứ bậc. Và chúng thực sự rất đơn giản:

-- get all descendants
SELECT tbl_orig.*,depth FROM tbl_closure LEFT JOIN tbl_orig ON descendant_id = tbl_orig.id WHERE ancestor_id = XXX AND depth <> 0;
-- get only direct descendants
SELECT tbl_orig.* FROM tbl_closure LEFT JOIN tbl_orig ON descendant_id = tbl_orig.id WHERE ancestor_id = XXX AND depth = 1;
-- get all ancestors
SELECT tbl_orig.* FROM tbl_closure LEFT JOIN tbl_orig ON ancestor_id = tbl_orig.id WHERE descendant_id = XXX AND depth <> 0;
-- find the deepest level of children
SELECT MAX(depth) FROM tbl_closure WHERE ancestor_id = XXX;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.