通过CTE或子查询与其下属更新记录

时间:2010-06-07 21:16:01

标签: sql subquery sql-update hierarchy common-table-expression

假设我有一个包含以下列的表:

员工

employeeID int
employeeName varchar(50)
managerID int
totalOrganization int

managerID是对employeeID的引用。对于所有记录,totalOrganization目前为0。

我想将每行的totalOrganization更新为其下的员工总数。

所以有以下记录:

employeeID     employeeName     managerID     totalOrganization
1              John Cruz        NULL          0
2              Mark Russell     1             0
3              Alice Johnson    1             0
4              Juan Valdez      3             0

查询应将totalOrganizations更新为:

employeeID     employeeName     managerID     totalOrganization
1              John Cruz        NULL          3
2              Mark Russell     1             0
3              Alice Johnson    1             1
4              Juan Valdez      3             0

我知道我可以得到一些组织。图表使用以下CTE:

WITH OrgChart (employeeID, employeeName,managerID,level)
AS (
    SELECT employeeID,employeeName,0 as managerID,0 AS Level
    FROM Employees
    WHERE managerID IS NULL
    UNION ALL
    SELECT Employees.employeeID,Employees.employeeName,Employees.managerID,Level + 1
    FROM Employees INNER JOIN
    OrgChart ON Employees.managerID = OrgChart.employeeID
   )
SELECT employeeID,employeeName,managerID, level
FROM OrgChart;

有没有办法使用存储过程更新Employees表,而不是在SQL之外构建一些例程来解析数据?

3 个答案:

答案 0 :(得分:2)

您可以添加另一个CTE来确定员工数,然后在Update语句中使用它:

WITH OrgChart (employeeID, employeeName,managerID,level)
AS (
    SELECT employeeID,employeeName,0 as managerID,0 AS Level
    FROM Employees
    WHERE managerID IS NULL
    UNION ALL
    SELECT Employees.employeeID,Employees.employeeName,Employees.managerID,Level + 1
    FROM Employees 
        INNER JOIN OrgChart 
            ON Employees.managerID = OrgChart.employeeID
   )
   , SubordinateCount As
   (
   Select ManagerId, Count(*) As Total
   From OrgChart
   Group By ManagerId
   )
Update Employees
Set TotalOrganization = SubordinateCount.Total
FROM SubordinateCount
    Join Employees As E
        On E.employeeId = SubordinateCount.ManagerId

<强> ADDITION

规范的变化是您想要计算所有下属员工。这样做的诀窍是为每个经理创建一个员工的路径。所以,首先是我的测试数据:

Insert Employees(EmployeeId, Name, ManagerId) Values(1, 'Alice', Null)
Insert Employees(EmployeeId, Name, ManagerId) Values(2, 'Bob', 1)
Insert Employees(EmployeeId, Name, ManagerId) Values(3, 'Charlie', 1)
Insert Employees(EmployeeId, Name, ManagerId) Values(4, 'Dan', 3)
Insert Employees(EmployeeId, Name, ManagerId) Values(5, 'Ellen', 3)
Insert Employees(EmployeeId, Name, ManagerId) Values(6, 'Fred', 5)
Insert Employees(EmployeeId, Name, ManagerId) Values(7, 'Gale', 6)
Insert Employees(EmployeeId, Name, ManagerId) Values(8, 'Harry', 6)

所以,首先我们编写一个查询,为我们提供他们经理的路径:

With 
    OrgChart As
    (
    Select E.EmployeeId, E.Name, Null As ManagerId, 0 AS Level
        , Cast( '/' + Cast(E.EmployeeId As varchar(10)) + '/' As varchar(100) ) As Path
    From dbo.Employees As E
    Where E.ManagerId Is Null
    Union All
    Select E.EmployeeID, E.Name, E.ManagerID, Level + 1
        , Cast( OrgChart.Path + Cast(E.EmployeeId As varchar(10)) + '/' As varchar(100))
    From dbo.Employees As E
        Join OrgChart 
            On OrgChart.EmployeeId = E.ManagerID
   )
Select *
From OrgChart 

产生:


    EmployeeId  Name    ManagerId   Level   Path
    1           Alice       NULL        0       /1/
    2           Bob         1           1       /1/2/
    3           Charlie     1           1       /1/3/
    4           Dan         3           2       /1/3/4/
    5           Ellen       3           2       /1/3/5/
    6           Fred        5           3       /1/3/5/6/
    7           Gale        6           4       /1/3/5/6/7/
    8           Harry       6           4       /1/3/5/6/8/

现在我们只需要计算给定员工存在于某人路径中的实例:

With 
    OrgChart As
    (
    Select E.EmployeeId, E.Name, Null As ManagerId, 0 AS Level
        , Cast( '/' + Cast(E.EmployeeId As varchar(10)) + '/' As varchar(100) ) As Path
    From dbo.Employees As E
    Where E.ManagerId Is Null
    Union All
    Select E.EmployeeID, E.Name, E.ManagerID, Level + 1
        , Cast( OrgChart.Path + Cast(E.EmployeeId As varchar(10)) + '/' As varchar(100))
    From dbo.Employees As E
        Join OrgChart 
            On OrgChart.EmployeeId = E.ManagerID
   )
    , OrgCounts As
    (
    Select O.EmployeeId, O.Name, O.ManagerId, O.Level, O.Path
        , (Select Count(*)
            From OrgChart As O1
            Where O1.Path Like '%/' + Cast(E.EmployeeId As varchar(10)) + '/%') - 1 As SubordinateTotal
    From Employees As E
        Join OrgChart As O
            On O.EmployeeId = E.EmployeeId
    )
Select O.EmployeeId, O.Name, O.ManagerId, O.Level, O.Path, O.SubordinateTotal
From OrgCounts

我从总数中减去一个以排除当前员工。现在我们已经找到了一个提供正确结果的查询,我们可以轻松地使用它来进行更新:

With 
    OrgChart As
    (
    Select E.EmployeeId, E.Name, Null As ManagerId, 0 AS Level
        , Cast( '/' + Cast(E.EmployeeId As varchar(10)) + '/' As varchar(100) ) As Path
    From dbo.Employees As E
    Where E.ManagerId Is Null
    Union All
    Select E.EmployeeID, E.Name, E.ManagerID, Level + 1
        , Cast( OrgChart.Path + Cast(E.EmployeeId As varchar(10)) + '/' As varchar(100))
    From dbo.Employees As E
        Join OrgChart 
            On OrgChart.EmployeeId = E.ManagerID
   )
    , OrgCounts As
    (
    Select O.EmployeeId, O.Name, O.ManagerId, O.Level, O.Path
        , (Select Count(*)
            From OrgChart As O1
            Where O1.Path Like '%/' + Cast(E.EmployeeId As varchar(10)) + '/%') - 1 As SubordinateTotal
    From Employees As E
        Join OrgChart As O
            On O.EmployeeId = E.EmployeeId
    )
Update Employees
Set TotalOrganization = O.SubordinateTotal
From OrgCounts As O
    Join dbo.Employees As E
        On E.EmployeeId = O.EmployeeId

答案 1 :(得分:2)

经过几个小时的实验,我想出了以下内容。它给出了期望的结果。有人看到改善它的方法吗?

CREATE TABLE #totalOrganization (employeeID int,managerID int,level int);
CREATE TABLE #countedOrganization (employeeID int,managerID int,orgCount int,level int);


WITH OrgChart (employeeID,managerID,level) 
AS ( 
    SELECT employeeID,0 as managerID,0 AS Level 
    FROM Emp
    WHERE managerID IS NULL 
    UNION ALL 
    SELECT Emp.employeeID,Emp.managerID,Level + 1 
    FROM Emp 
        INNER JOIN OrgChart  
            ON Emp.managerID = OrgChart.employeeID 
   )
INSERT INTO
    #totalOrganization
    SELECT 
        employeeID,managerID,level
    FROM
        OrgChart;

DECLARE @maxLevel int
SELECT 
    @maxLevel = MAX(level)
FROM
    #totalOrganization;

WHILE (@maxLevel > -1)
    BEGIN
        INSERT INTO
            #countedOrganization
            SELECT
                upline.employeeID,upline.managerID,SUM(CONVERT(INT,CASE WHEN downline.orgCount IS NULL THEN 0 ELSE downline.orgCount END)) + CONVERT(INT,CASE WHEN COUNT(downline.employeeID) IS NULL THEN 0 ELSE COUNT(downline.employeeID) END),upline.level
            FROM
                #totalOrganization AS upline LEFT OUTER JOIN
                #countedOrganization AS downline ON downline.managerID=upline.employeeID
            WHERE
                upline.level = @maxLevel
            GROUP BY
                upline.employeeID,upline.managerID,upline.level

        SET @maxLevel = @maxLevel - 1
    END

UPDATE
    Emp
SET
    totalOrg= CONVERT(INT,CASE WHEN orgCount IS NULL THEN 0 ELSE orgCount END)
FROM
    #countedOrganization INNER JOIN
    Emp ON #countedOrganization.employeeID=Emp.employeeID

答案 2 :(得分:0)

这可以(当然)在存储过程中完成。但是,它看起来非常像单个(CTE)语句无法完成,因为您无法对给定员工的下属及其所有下属进行求和(即,计算所有后代在给定项目下面层次结构),根据此错误消息:

GROUP BY, HAVING, or aggregate functions are not allowed in the recursive part of a recursive common table expression 'Subordinates'.

因此,您在SQL之外编写的例程(从层次结构的最低“级别开始,计算所有下级员工,在迭代层次结构时重复)将必须在SQL中编写。