Tôi có thể có được cấu trúc cây từ bảng tự tham chiếu (phân cấp) không?


8

Đưa ra một bảng phân cấp như thế này:

CREATE TABLE [dbo].[btree]
(
  id INT PRIMARY KEY
, parent_id INT REFERENCES [dbo].[btree] ([id])
, name NVARCHAR(20)
);

Tôi muốn có được toàn bộ cấu trúc cây.

Ví dụ: sử dụng dữ liệu này:

INSERT INTO [btree] VALUES (1, null, '1 Root');
INSERT INTO [btree] VALUES (2,    1, '1.1 Group');
INSERT INTO [btree] VALUES (3,    1, '1.2 Group');
INSERT INTO [btree] VALUES (4,    2, '1.1.1 Group');
INSERT INTO [btree] VALUES (5,    2, '1.1.2 Group');
INSERT INTO [btree] VALUES (6,    3, '1.2.1 Group');
INSERT INTO [btree] VALUES (7,    3, '1.2.2 Group');
INSERT INTO [btree] VALUES (8,    4, '1.1.1.1 Items');
INSERT INTO [btree] VALUES (9,    4, '1.1.1.2 Items');
INSERT INTO [btree] VALUES (10,   5, '1.1.2.1 Items');
INSERT INTO [btree] VALUES (11,   5, '1.1.1.2 Items');
INSERT INTO [btree] VALUES (12,   6, '1.2.1.1 Items');
INSERT INTO [btree] VALUES (13,   6, '1.2.1.2 Items');
INSERT INTO [btree] VALUES (14,   7, '1.2.2.1 Items');

Tôi muốn có được:

+----+-----------+---------------------+
| id | parent_id | description         |
+----+-----------+---------------------+
|  1 |    NULL   | 1 Root              |
|  2 |     1     |   1.1 Group         |
|  4 |     2     |     1.1.1 Group     |
|  8 |     4     |       1.1.1.1 Items |
|  9 |     4     |       1.1.1.2 Items |
|  5 |     2     |     1.1.2 Group     |
| 10 |     5     |       1.1.2.1 Items |
| 11 |     5     |       1.1.2.2 Items |
|  3 |     1     |   1.2 Group         |
|  6 |     3     |     1.2.1 Group     |
| 12 |     6     |       1.2.1.1 Items |
| 13 |     6     |       1.2.1.2 Items |
|  7 |     3     |     1.2.2 Group     |
| 14 |     7     |       1.2.2.1 Items |
+----+-----------+---------------------+

Tôi đang tìm nạp các bản ghi bằng truy vấn đệ quy như thế này:

;WITH tree AS
(
    SELECT c1.id, c1.parent_id, c1.name, [level] = 1
    FROM dbo.[btree] c1
    WHERE c1.parent_id IS NULL
    UNION ALL
    SELECT c2.id, c2.parent_id, c2.name, [level] = tree.[level] + 1
    FROM dbo.[btree] c2 INNER JOIN tree ON tree.id = c2.parent_id
)
SELECT tree.level, tree.id, parent_id, REPLICATE('  ', tree.level - 1) + tree.name AS description
FROM tree
OPTION (MAXRECURSION 0)
;

Và đây là kết quả hiện tại:

+----+-----------+---------------------+
| id | parent_id | description         |
|  1 |    NULL   | 1 Root              |
|  2 |     1     |   1.1 Group         |
|  3 |     1     |   1.2 Group         |
|  6 |     3     |     1.2.1 Group     |
|  7 |     3     |     1.2.2 Group     |
| 14 |     7     |       1.2.2.1 Items |
| 12 |     6     |       1.2.1.1 Items |
| 13 |     6     |       1.2.1.2 Items |
|  4 |     2     |     1.1.1 Group     |
|  5 |     2     |     1.1.2 Group     |
| 10 |     5     |       1.1.2.1 Items |
| 11 |     5     |       1.1.1.2 Items |
|  8 |     4     |       1.1.1.1 Items |
|  9 |     4     |       1.1.1.2 Items |
+----+-----------+---------------------+

Tôi không thể tìm ra cách đặt hàng theo cấp độ.

Có cách nào để thiết lập một thứ hạng cho mỗi cấp độ phụ?

Tôi đã thiết lập một Rextester

Câu trả lời:


7

Thêm trường "đường dẫn" và sắp xếp theo trường tương tự như đường dẫn tệp. Như ypercube đã đề cập, việc sắp xếp quá đơn giản trong ví dụ này và chỉ xảy ra với công việc nhưng vì đơn giản tôi sẽ để nguyên như vậy. Hầu hết khi tôi sử dụng mẫu này, tôi sắp xếp theo tên chứ không phải ID.

IF OBJECT_ID('[dbo].[btree]', 'U') IS NOT NULL 
    DROP TABLE [dbo].[btree];
GO

CREATE TABLE [dbo].[btree]
(
  id INT PRIMARY KEY
, parent_id INT REFERENCES [dbo].[btree] ([id])
, name NVARCHAR(20)
);
GO

INSERT INTO [btree] VALUES (1, null, '1 Root');
INSERT INTO [btree] VALUES (2,    1, '1.1 Group');
INSERT INTO [btree] VALUES (3,    1, '1.2 Group');
INSERT INTO [btree] VALUES (4,    2, '1.1.1 Group');
INSERT INTO [btree] VALUES (5,    2, '1.1.2 Group');
INSERT INTO [btree] VALUES (6,    3, '1.2.1 Group');
INSERT INTO [btree] VALUES (7,    3, '1.2.2 Group');
INSERT INTO [btree] VALUES (8,    4, '1.1.1.1 Items');
INSERT INTO [btree] VALUES (9,    4, '1.1.1.2 Items');
INSERT INTO [btree] VALUES (10,   5, '1.1.2.1 Items');
INSERT INTO [btree] VALUES (11,   5, '1.1.2.2 Items');
INSERT INTO [btree] VALUES (12,   6, '1.2.1.1 Items');
INSERT INTO [btree] VALUES (13,   6, '1.2.1.2 Items');
INSERT INTO [btree] VALUES (14,   7, '1.2.2.1 Items');

;WITH tree AS
(
    SELECT c1.id, c1.parent_id, c1.name, [level] = 1, path = cast('root' as varchar(100))
    FROM dbo.[btree] c1
    WHERE c1.parent_id IS NULL
    UNION ALL
    SELECT c2.id, c2.parent_id, c2.name, [level] = tree.[level] + 1, 
           Path = Cast(tree.path+'/'+right('000000000' + cast(c2.id as varchar(10)),10) as varchar(100))
    FROM dbo.[btree] c2 INNER JOIN tree ON tree.id = c2.parent_id
)
SELECT tree.path, tree.id, parent_id, REPLICATE('  ', tree.level - 1) + tree.name AS description
FROM tree
Order by path
OPTION (MAXRECURSION 0)
;

Đây là một rextester


Đó là ý tưởng đúng nhưng trong biểu thức đường dẫn nên được c2.idthay thế bằng một row_number và được đệm ở bên trái để tất cả các phần có độ dài bằng nhau. Nếu không, nó sẽ không hoạt động cho tất cả dữ liệu. Chỉ cần thay 2 bằng 55 trong dữ liệu và thay đổi thứ tự
ypercubeᵀᴹ

Hoàn toàn đồng ý. Tôi đang sử dụng điện thoại di động và muốn giành chiến thắng trong cuộc đua đến câu trả lời :) Thực ra tôi thường sử dụng trường "tên" trong đường dẫn. Đó thường là trường hợp sử dụng của tôi.
Ben Campbell

Tôi có thể sai về số hàng (không cần thiết) nhưng phần đệm là. +1 (Nếu chúng tôi sử dụng row_number, đường dẫn sẽ xây dựng lại phần đầu tiên của tên!)
ypercubeᵀᴹ

Tôi đã chỉnh sửa Pathvới một chỉnh sửa nhỏ, để thêm phần đệm.
ypercubeᵀᴹ

1
Tôi thường sử dụng gấp đôi chiều dài đường dẫn dự kiến ​​của mình nếu có bất kỳ nghi ngờ nào về độ sâu tối đa. Ngoài ra, bạn có thể giảm phần đệm bằng 0 nếu bạn biết thứ tự độ lớn tối đa của ID / row_number.
Ben Campbell

4

Gian lận, chỉ một chút;) Nhìn ma, không có đệ quy!

Đã thử nghiệm tại rextester.com

SELECT btree.*        -- , a,b,c,d     -- uncomment to see the parts
FROM btree 
  OUTER APPLY
    ( SELECT rlc = REVERSE(LEFT(name, CHARINDEX(' ', name)-1))) AS r
  OUTER APPLY
    ( SELECT a = CAST(REVERSE(PARSENAME(r.rlc, 1)) AS int),
             b = CAST(REVERSE(PARSENAME(r.rlc, 2)) AS int),
             c = CAST(REVERSE(PARSENAME(r.rlc, 3)) AS int),
             d = CAST(REVERSE(PARSENAME(r.rlc, 4)) AS int)
    ) AS p 
ORDER BY a, b, c, d ;

Tất nhiên những điều trên là khá hạn chế. Nó chỉ hoạt động theo các giả định:

  • các namecột đã được lưu trữ (trong phần đầu tiên) là "đường dẫn" thực tế.
  • độ sâu của cây tối đa là 4 (vì vậy đường dẫn có tới 4 phần).
  • những CAST .. AS intchỉ cần thiết nếu các phần là những con số.

Giải thích: Mã hoạt động bằng cách sử dụng hàm PARSENAME()có mục đích chính là tách một tên đối tượng thành 4 phần:

Server.Database.Schema.Object
  |        |       |      |
 4th      3rd     2nd    1st

Lưu ý rằng thứ tự là đảo ngược. Ví dụ, PARSENAME('dbo.btree', 2)sẽ cho chúng tôi kết 'dbo'quả. Trong 3, chúng tôi sẽ nhận được NULL (đó là lý do tại sao mã REVERSE()được sử dụng hai lần trong mã. Nếu không, chúng tôi sẽ nhận được các giá trị null ngay từ đầu. Chúng tôi '1.2'sẽ phân tích cú pháp null, null, 1, 2trong khi chúng tôi muốn 1, 2, null, null. )


Kết luận: sau tất cả những điều đó, tôi nên nói thêm rằng câu trả lời của Bob Campbel là cách để nó đi chung hơn và tạo ra (trong cột "đường dẫn" trong kết quả) hệ thống phân cấp đường dẫn, sau đó có thể được sử dụng cho ORDER BY.

Các tùy chọn khác bạn có thể xem xét - nếu kích thước của bảng phát triển lớn và giải pháp đệ quy trở nên chậm - là thực sự lưu trữ đường dẫn trong một cột riêng biệt (theo định dạng phù hợp để đặt hàng, ví dụ như có đệm) hoặc sử dụng được cung cấp HierarchyIDloại chính xác cho trường hợp sử dụng này, dữ liệu phân cấp.


:) Nó thực sự tuyệt vời! Thật không may không namethể được sử dụng trong trường hợp này. Tôi sẽ mất cả đêm để giải mã nó, tôi có thể có một số lời giải thích?
McNets

Vì vậy, cột "tên" không có dữ liệu bạn cung cấp trong ví dụ? Điều đáng tiếc.
ypercubeᵀᴹ

Không, tôi đã sử dụng nó làm ví dụ, chỉ để nhận xét có một số cấp độ.
McNets

1
@Mcnets trong trường hợp (không có khả năng) namelưu trữ một đường dẫn (có văn bản), như 'order173.palletA27.box9'.bag3A, bạn vẫn có thể sử dụng mã (chỉ cần loại bỏ các phôi thành int). Trong mọi trường hợp, truy vấn của BenCambell là cách đi chung.
ypercubeᵀᴹ

1
@EvanCarroll có, loại phân cấp. Tôi chỉ thêm một đoạn cuối về các tùy chọn khác với liên kết.
ypercubeᵀᴹ
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.