Сортировка вложенного набора по имени при сохранении целостности глубины
Я использую модель вложенного набора, которая позже будет использоваться для создания карты сайта для моего веб-сайта. Это моя структура таблицы.
create table departments (
id int identity(0, 1) primary key
, lft int
, rgt int
, name nvarchar(60)
);
insert into departments (lft, rgt, name) values (1, 10, 'departments');
insert into departments (lft, rgt, name) values (2, 3, 'd');
insert into departments (lft, rgt, name) values (4, 9, 'a');
insert into departments (lft, rgt, name) values (5, 6, 'b');
insert into departments (lft, rgt, name) values (7, 8, 'c');
Как я могу отсортировать по глубине, а также имя? я могу сделать
select
replicate('----', count(parent.name) - 1) + ' ' + node.name
, count(parent.name) - 1 as depth
, node.lft
from
departments node
, departments parent
where
node.lft between parent.lft and parent.rgt
group by
node.name, node.lft
order by
depth asc, node.name asc;
Однако это не соответствует детям с их родителями по какой-то причине.
department lft rgt
---------------------------
departments 0 1
---- a 1 4
---- d 1 2
-------- b 2 5
-------- c 2 7
Как видите, в отделе 'd' есть отдел 'дети'!
Спасибо.
3 ответа
Я думаю, что наконец-то нашел решение ANSI SQL. Основная суть в том, что он подсчитывает количество строк, у которых либо есть родители с более низким значением имени на том же уровне, что и один из собственных родителей узла, либо он сам находится на том же уровне, что и узел, и имеет имя с более низким значением. Вам нужно будет внести в него небольшую корректировку, чтобы добавить отступы, если вы этого хотите. Кроме того, я не знаю, как производительность на большом наборе данных будет из-за всех подзапросов:
SELECT
N1.name
FROM
dbo.departments N1
ORDER BY
(
SELECT
COUNT(DISTINCT N2.lft)
FROM
dbo.departments N2
INNER JOIN (
SELECT
N.name,
N.lft,
N.rgt,
(SELECT COUNT(*) FROM dbo.departments WHERE lft < N.lft AND rgt > N.lft) AS depth
FROM
dbo.departments N) SQ1 ON
SQ1.lft <= N2.lft AND SQ1.rgt >= N2.lft
INNER JOIN (
SELECT
N3.name,
N3.lft,
N3.rgt,
(SELECT COUNT(*) FROM dbo.departments WHERE lft < N3.lft AND rgt > N3.lft) AS depth
FROM
dbo.departments N3) SQ2 ON
SQ2.lft <= N1.lft AND SQ2.rgt >= N1.lft AND
SQ2.depth = SQ1.depth AND
SQ2.name > SQ1.name
)
Дайте мне знать, если вы столкнетесь с какой-либо ситуацией, когда она сломается.
Ниже будет работать с вашим примером, хотя если имя содержит символ "-", оно может сломаться. Это может послужить отправной точкой, хотя. Это использует CTE, которые являются определенными для SQL Server, я верю. Если я подумаю о более общем методе ANSI SQL, я также опубликую его.
;WITH Tree_Path AS (
SELECT
lft,
rgt,
name,
CAST(name + '-' AS VARCHAR(MAX)) AS tree_path,
1 AS depth
FROM
dbo.departments
WHERE
lft = 1
UNION ALL
SELECT
c.lft,
c.rgt,
c.name,
CAST(tp.tree_path + c.name + '-' AS VARCHAR(MAX)),
tp.depth + 1
FROM
Tree_Path tp
INNER JOIN dbo.departments AS c ON
c.lft > tp.lft AND
c.lft < tp.rgt AND
NOT EXISTS (SELECT * FROM dbo.departments d WHERE d.lft < c.lft AND d.rgt > c.lft AND d.lft > tp.lft AND d.lft < tp.rgt))
SELECT
REPLICATE('----', depth - 1) + name,
depth - 1,
lft
FROM
Tree_Path
ORDER BY
tree_path,
name
В вопросе есть несоответствие. Запрос возвращает: node.name
, depth
, а также node.lft
- но таблица результатов помечена:
department lft rgt
В любом случае, этот запрос возвращает правильные результаты для уровня отдела - что, очевидно, не то, что вы хотите, чтобы глубина означала в этом случае. И a, и d являются отделами высшего уровня.
Если вы хотите количество подразделений, и вложенный набор поддерживается должным образом, тогда запрос просто:
SELECT
D1.name,
(D1.rgt - D1.lft - 1) / 2 AS SubordinateDepartments
FROM
departments AS D1
ORDER BY
SubordinateDepartments DESC,
D1.name