如何在复杂的 JOIN 中删除重复项

How to remove duplicates in a complicated JOIN

我有一个 EMPLOYEE table,它也有内置的层次结构(使用经理列)

我有另一个 REGION table 代表经理-地区关系

我正在尝试创建一个 SQL,它将通过跟进层次结构链来显示哪些员工属于哪个区域。

约束/规则:

这是我想出的天真 SQL(但结果有重复 - 第三条规则失败)

select distinct e.name, r.region
from employee e 
left outer join employee mgr1 on mgr1.id = e.manager 
left outer join employee mgr2 on mgr2.id = mgr1.manager 
left outer join employee mgr3 on mgr3.id = mgr2.manager 
left outer join employee mgr4 on mgr4.id = mgr3.manager 
left outer join REGION r on 
      (  r.id = mgr1.id 
      or r.id = mgr2.id 
      or r.id = mgr3.id 
      or r.id = mgr4.id  ) 

where e.IS_MANAGER = 'N'; //only interested in users for now; assume a flag

这是结果集:

如果我已经找到一个区域,如何有条件地停止左外连接?

试试这个:

select distinct e.name, COALESCE(r1.region, r2.region, r3.region, r4.region, 'No Region') region
from employee e left outer join 
                region r1 on e.manager = r1.id
left outer join employee mgr1 on mgr1.id = e.manager left outer join
                region r2 on mgr1.manager = r2.id
left outer join employee mgr2 on mgr2.id = mgr1.manager left outer join
                region r3 on mgr2.manager = r3.id
left outer join employee mgr3 on mgr3.id = mgr2.manager left outer join
                region r4 on mgr3.manager = r4.id
where e.IS_MANAGER = 'N'; //only interested in users for now; assume a flag

我不确定所有 mysql 版本都支持 COALESCE 函数,但您可以找到一个等效函数(它 returns 第一个非空参数)。

我不得不稍微修改一下你的脚本,但这很有效:

select distinct e.Name,
  CASE
    WHEN r1.RegionName IS NOT NULL THEN r1.RegionName
    WHEN r2.RegionName IS NOT NULL THEN r2.RegionName
    WHEN r3.RegionName IS NOT NULL THEN r3.RegionName
    WHEN r4.RegionName IS NOT NULL THEN r4.RegionName
    ELSE 'NA'
  END AS 'RegionName'
from employee e 
left outer join employee mgr1 on mgr1.id = e.Manager 
left outer join employee mgr2 on mgr2.id = mgr1.Manager 
left outer join employee mgr3 on mgr3.id = mgr2.Manager 
left outer join employee mgr4 on mgr4.id = mgr3.Manager 
left outer join Region r1 on r1.id = mgr1.RegionID 
left outer join Region r2 on r2.id = mgr2.RegionID 
left outer join Region r3 on r3.id = mgr3.RegionID 
left outer join Region r4 on r4.id = mgr4.RegionID 

where e.IS_MANAGER = 'N';

这是 SQL Fiddle:http://sqlfiddle.com/#!9/93b45/5

DB2(几乎所有版本)都支持递归CTE,就是为了处理这种分层数据而设计的(有些版本还支持Oracles CONNECT BY,但我对此并不熟悉)。使用它可能会使连接更容易推理:

WITH Employee_Region AS (SELECT name, manager, CAST(null AS VARCHAR(2)) AS region
                         FROM Employee
                         WHERE is_manager = 'N'
                         UNION ALL
                         SELECT ER.name, Manager.manager, Region.regionName
                         FROM Employee_Region ER
                         JOIN Employee Manager
                           ON Manager.id = ER.manager
                         LEFT JOIN Region 
                                ON Region.id = Manager.regionId
                         WHERE ER.region IS NULL)
SELECT name, region
FROM Employee_Region
WHERE region IS NOT NULL

SQL Fiddle Example
(Fiddle 基础取自@PhilWalton - 谢谢!PostgreSQL 需要 RECURSIVE 关键字,但 DB2 不需要)

查询确实是从底部开始的(假设您有一个标志),但也可以将其反转并从顶级管理器开始。
对于递归部分(CTE 中 UNION ALL 之后的所有内容):

  • 我们自己JOINEmployeetable去请下一位经理
  • 因为我们不知道当前经理是否有区域,所以我们 LEFT JOIN 到 table。
  • 如果我们没有找到区域,请使用下一位管理员再试一次。
  • 我们排除了所有在上一次迭代中接收到区域(将获取 'lowest' 区域)的行。否则,终止会在顶级经理处停止。

最后,在主查询中,我们排除了雇员没有地区的那些行。大多数情况下,这将删除搜索时生成的中间行,直到找到具有区域的管理器,尽管如果某些树没有区域(以某种方式),它将排除它们。
使用 WHERE 几乎肯定比 DISTINCT 所需的散列函数更便宜,尽管我不确定递归部分会产生什么影响(如果大多数直接管理者有一个区域,或者在一个跃点内,它可能比做四个连接表现得更好)