SQL tree data struct(3): nested set (1,12) include (2,3) & (4, 11) ...

来源:互联网 发布:win10修改mac地址 编辑:程序博客网 时间:2024/05/22 16:09


-- Credit to JOE CELKO
-- http://archives.postgresql.org/pgsql-sql/2001-11/msg00004.php

--  CREATE TABLE Personnel
--  (emp CHAR(10) NOT NULL PRIMARY KEY,
--   lft INTEGER NOT NULL UNIQUE CHECK (lft > 0),
--   rgt INTEGER NOT NULL UNIQUE CHECK (rgt > 1),
--   CONSTRAINT order_okay CHECK (lft < rgt) );

/*
The organizational chart would look like this as a directed graph:

            Albert (1,12)
            /        /
          /            /
    Bert (2,3)    Chuck (4,11)
                   /    |   /
                 /      |     /
               /        |       /
             /          |         /
        Donna (5,6)  Eddie (7,8)  Fred (9,10)
*/
if object_id('personnel') is not null
    drop table personnel
go
create table personnel (emp varchar(30) not null primary key, lft int, rgt int)
go
if object_id('salaries') is not null
    drop table salaries
go
create table salaries (emp varchar(30), salary int)
go
go

insert personnel
select 'Albert', 1, 12
union all select 'Bert', 2, 3
union all select 'Chuck', 4, 11
union all select 'Donna', 5, 6
union all select 'Eddie', 7, 8
union all select 'Fred', 9, 10
go

insert salaries
select 'Albert', 100000
union all select 'Bert', 70000
union all select 'Chuck', 90000
union all select 'Donna', 60000
union all select 'Eddie', 60000
union all select 'Fred', 50000
go
--==============================================================================
-- 1. An employee and all their Supervisors, no matter how deep the tree.
if object_id('func_get_supervisors') is not null
    drop function func_get_supervisors
go
create function func_get_supervisors(@staff varchar(30))
    returns table
as
return
    select supervisor.*
    from personnel as subordinate, personnel as supervisor
    where subordinate.lft between supervisor.lft and supervisor.rgt and subordinate.emp = @staff
go
--==============================================================================
-- 2. The employee and all subordinates. There is a nice symmetry here.
if object_id('func_get_subordinates') is not null
    drop function func_get_subordinates
go
create function func_get_subordinates(@staff varchar(30))
    returns table
as
return
    select subordinate.*
    from personnel as subordinate, personnel as supervisor
    where subordinate.lft between supervisor.lft and supervisor.rgt and supervisor.emp = @staff
go
--==============================================================================
-- 3. Add a GROUP BY and aggregate functions to these basic queries and you have hierarchical reports.
-- For example, the total salaries which each employee controls:
if object_id('func_dept_salaries') is not null
    drop function func_dept_salaries
go
create function func_dept_salaries(@staff varchar(30))
    returns int
as
begin
    return
    (select sum(sal.salary)
    from personnel as subordinate, personnel as supervisor, salaries as sal
    where subordinate.lft between supervisor.lft and supervisor.rgt and subordinate.emp = sal.emp
    group by supervisor.emp
    having supervisor.emp = @staff)
end
go
--==============================================================================
-- 4. To find the level of each node, so you can print the tree as an indented listing.
if object_id('func_get_levels') is not null
    drop function func_get_levels
go
create function func_get_levels()
    returns table
as
return
    select indentation=count(supervisor.emp), subordinate.emp
    from personnel as subordinate, personnel as supervisor
    where subordinate.lft between supervisor.lft and supervisor.rgt
    group by subordinate.emp
go
--==============================================================================
-- 5. The nested set model has an implied ordering of siblings which the adjacency list model does not.
if object_id('proc_add_staff') is not null
    drop procedure proc_add_staff
go
create procedure proc_add_staff @new_staff varchar(30), @boss varchar(30)
as
    if not exists(select * from personnel where emp = @boss)
        return

    declare @right_most_sibling int
    set @right_most_sibling = (select rgt from personnel where emp = @boss)
    update personnel set
        lft = case when lft > @right_most_sibling then lft + 2 else lft end,
        rgt = case when rgt >= @right_most_sibling then rgt + 2 else rgt end
    where rgt >= @right_most_sibling
    insert personnel select @new_staff, @right_most_sibling, @right_most_sibling+1
go

--==============================================================================
-- delete a node and put all the subordinates under the big boss ???
if object_id('proc_del_staff') is not null
    drop procedure proc_del_staff
go
create procedure proc_del_staff @staff varchar(30)
as
    declare @lft int, @rgt int
    select @lft = lft, @rgt = rgt from personnel where emp = @staff
    -- shrink subordinate group   
    update personnel set lft = lft - 1, rgt = rgt -1 where lft between @lft and @rgt
    -- shrink supervisor group
    update personnel set
        lft = case when lft > @rgt then lft - 2 else lft end,
        rgt = case when rgt > @rgt then rgt -2 else rgt end
    where rgt > @rgt
    delete from personnel where emp = @staff
go

--==============================================================================
-- promotion (1 level) ??
if object_id('proc_promote_staff') is not null
    drop procedure proc_promote_staff
go
create procedure proc_promote_staff @staff varchar(30)
as
declare @current_boss varchar(30), @curr_boss_right int, @lft int, @rgt int, @sibling_spread int, @self_spread int

select @lft = lft, @rgt = rgt from personnel where emp = @staff
select top 1 @current_boss = emp, @curr_boss_right = rgt from personnel where lft < @lft and @lft < rgt order by lft desc
-- set expand spread
select @sibling_spread = @rgt - @lft + 1, @self_spread = @curr_boss_right - @rgt

-- memorize all @subordinates
declare @subordinates table(emp varchar(30) collate database_default)
insert @subordinates select emp from personnel where lft between @lft and @rgt
-- update all right sibling
update personnel set lft = lft - @sibling_spread, rgt = rgt - @sibling_spread where lft between @rgt and @curr_boss_right
-- update current_boss right bound
update personnel set rgt = rgt - @sibling_spread where emp = @current_boss
-- promote entire group
update personnel set lft = lft + @self_spread, rgt = rgt + @self_spread from personnel where emp in (select emp from @subordinates)
go
--==============================================================================
-- change post
if object_id('proc_change_post') is not null
    drop procedure proc_change_post
go
create procedure proc_change_post @staff varchar(30), @new_boss varchar(30)
as
    exec proc_del_staff @staff
    exec proc_add_staff @staff, @new_boss
go
--==============================================================================
if object_id('proc_show_dept') is not null
    drop procedure proc_show_dept
go
create procedure proc_show_dept @boss varchar(30), @debug bit = 0
as
    declare @s nvarchar(1000)
    set @s =
    'select emp,
    boss=case
        when emp = ''' + @boss + ''' then NULL
        else
            (select top 1 emp from personnel super where sub.lft between super.lft and super.rgt
            and sub.emp <> super.emp order by super.lft desc)
        end
    from personnel sub
    where sub.lft <> 1 /*exclude big boss, so boss field doesn''t contain nulls*/'

    exec proc_print_tree @s, @debug
go
--==============================================================================
if object_id('proc_show_all') is not null
    drop procedure proc_show_all
go
create procedure proc_show_all @debug bit = 0
as
    exec proc_print_tree
    'select emp,
    boss=(select top 1 emp from personnel super where sub.lft between super.lft and super.rgt
        and sub.emp <> super.emp order by super.lft desc)
    from personnel sub', @debug
go
--==============================================================================
-- select * from func_get_supervisors('Eddie')
-- select * from func_get_subordinates('Chuck')
-- select dbo.func_dept_salaries('Chuck')
-- select emp, dbo.func_dept_salaries(emp) from personnel
exec proc_add_staff 'Mark', 'Eddie'
exec proc_promote_staff 'Eddie'
exec proc_add_staff 'Gen', 'Eddie'
exec proc_del_staff 'Chuck'
exec proc_show_all
print '=============================================================================='
exec proc_del_staff 'Mark'
exec proc_change_post 'Mark', 'Donna'
exec proc_show_all

--##############################################################################
drop table personnel, salaries
drop function func_get_supervisors, func_get_subordinates, func_dept_salaries, func_get_levels
drop procedure proc_add_staff, proc_del_staff, proc_promote_staff, proc_change_post, proc_show_all, proc_show_dept
 
原创粉丝点击