Search code examples
sqlsql-server-2000hierarchynested-set-model

Sorting nested set by name while keep depth integrity


I'm using the nested set model that'll later be used to build a sitemap for my web site. This is my table structure.

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

How can I sort by depth as well as name? I can do

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;

However, that does not match children with their parent for some reason.

department      lft     rgt
---------------------------
 departments    0       1
---- a        1        4
---- d        1        2
-------- b    2        5
-------- c    2        7

As you can see, department 'd' has department 'a's children!

Thank you.


Solution

  • I think I finally came up with an ANSI SQL solution. The basic gist is that it counts the number of rows that either have parents with a lower valued name on the same level as one of the node's own parent or is itself on the same level as the node and has a lower valued name. You'll need to make a minor adjustment to it in order to add the indenting if you want it. Also, I don't know how performance on a large data set will be due to all of the subqueries:

    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
        )
    

    Let me know if you come up with any situations where it breaks.