Bisakah saya mendapatkan struktur pohon dari tabel (hierarki) referensi-sendiri?

8

Diberikan tabel hirarkis seperti ini:

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

Saya ingin mendapatkan seluruh struktur pohon.

Misalnya, menggunakan data ini:

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');

Saya ingin mendapatkan:

+----+-----------+---------------------+
| 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 |
+----+-----------+---------------------+

Saya mengambil rekaman menggunakan kueri rekursif seperti ini:

;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)
;

Dan ini adalah hasil saat ini:

+----+-----------+---------------------+
| 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 |
+----+-----------+---------------------+

Saya tidak tahu cara memesannya berdasarkan level.

Apakah ada cara untuk menetapkan peringkat untuk setiap sub-level?

Saya sudah menyiapkan Rextester

McNets
sumber

Jawaban:

7

Tambahkan bidang "jalur" dan urutkan berdasarkan yang mirip dengan jalur file. Seperti yang disebutkan ypercube, penyortirannya terlalu sederhana dalam contoh ini dan kebetulan bekerja tetapi demi kesederhanaan saya akan pergi apa adanya. Sebagian besar waktu ketika saya menggunakan pola ini saya mengurutkan berdasarkan nama daripada 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)
;

Sini rextester

Ben Campbell
sumber
Itu ide yang tepat tetapi dalam ekspresi jalan seharusnya c2.iddiganti dengan row_number dan empuk di sebelah kiri sehingga semua bagian memiliki panjang yang sama. Kalau tidak, itu tidak akan berfungsi untuk semua data. Cukup ganti 2 dengan 55 di data dan perubahan pesanan
ypercubeᵀᴹ
Setuju. Saya di ponsel dan ingin memenangkan perlombaan untuk jawabannya :) Sebenarnya saya akan menggunakan bidang "nama" di jalur secara umum. Itu biasanya kasus penggunaan saya.
Ben Campbell
Saya mungkin salah tentang nomor baris (tidak diperlukan) tetapi padding-nya. +1 (Jika kita menggunakan row_number, path akan merekonstruksi bagian pertama dari nama!)
ypercubeᵀᴹ
Saya telah mengedit Pathdengan koreksi kecil, untuk menambahkan padding.
ypercubeᵀᴹ
1
Saya biasanya menggunakan dua kali panjang jalur yang saya harapkan jika ada keraguan tentang kedalaman maksimal. Anda juga dapat mengurangi bantalan nol jika Anda tahu urutan besarnya ID / row_number maksimum.
Ben Campbell
4

Kecurangan, hanya sedikit;) Lihat bu, jangan rekursi!

Diuji di 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 ;

Tentu saja hal di atas agak terbatas. Ini hanya berfungsi berdasarkan asumsi:

  • yang namekolom telah disimpan (di bagian pertama) sebenarnya "jalan".
  • kedalaman pohon maksimum 4 (sehingga jalur memiliki hingga 4 bagian).
  • yang CAST .. AS intdiperlukan hanya jika bagian-bagian yang nomor.

Penjelasan: Kode ini bekerja dengan menggunakan fungsi PARSENAME()yang memiliki tujuan utama membagi nama objek menjadi 4 bagian:

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

Perhatikan bahwa urutannya terbalik. Sebagai contoh, PARSENAME('dbo.btree', 2)akan memberi kita 'dbo'sebagai hasilnya. Dengan 3, kita akan mendapatkan NULL (itu sebabnya REVERSE()digunakan dua kali dalam kode. Kalau tidak kita akan mendapatkan nol di awal. '1.2'Akan diurai menjadi seperti yang null, null, 1, 2kita inginkan 1, 2, null, null. )


Kesimpulan: setelah semua itu, saya harus menambahkan bahwa jawaban oleh Bob Campbel adalah cara untuk pergi karena lebih umum dan menghasilkan (dalam kolom "jalan" di hasilnya) hierarki jalur, yang kemudian dapat digunakan untuk ORDER BY.

Opsi lain yang dapat Anda pertimbangkan - jika ukuran tabel bertambah besar dan solusi rekursif menjadi lambat - adalah untuk benar-benar menyimpan jalur dalam kolom terpisah (dalam format yang baik untuk pemesanan, yaitu dengan bantalan) atau menggunakan yang disediakan HierarchyIDjenis yang tepat untuk kasus penggunaan ini, data hierarkis.

ypercubeᵀᴹ
sumber
:) Ini benar-benar luar biasa! Sayangnya nametidak dapat digunakan dalam kasus ini. Butuh waktu sepanjang malam untuk menguraikannya, bisakah saya memiliki penjelasan?
McNets
Jadi, kolom "nama" tidak memiliki data yang Anda berikan dalam contoh? Kasihan.
ypercubeᵀᴹ
Tidak, saya sudah menggunakannya sebagai contoh, hanya untuk berkomentar ada beberapa level.
McNets
1
@Mnet dalam kasus (tidak mungkin) bahwa nametidak menyimpan path (dengan teks), seperti 'order173.palletA27.box9'.bag3A, Anda masih bisa menggunakan kode (cukup hapus gips ke int). Bagaimanapun, permintaan oleh BenCambell adalah cara untuk pergi secara umum.
ypercubeᵀᴹ
1
@ EvanCarroll ya, tipe hierarki. Saya baru saja menambahkan paragraf terakhir tentang opsi lain dengan tautan.
ypercubeᵀᴹ