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

时间:2015-04-23 15:40:41

标签: sql database join db2

我还有一个EMPLOYEE表,它也有内置的层次结构(使用管理器列)

EMPLOYEE table

我是另一个表示经理 - 区域关系的REGION表

REGION table

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

约束/规则:

  • 员工的直属经理可能没有地区 - 所以我需要继续上行。
  • 我保证链上4级会有人有区域。

  • 如果在第4级之前找到区域,则使用较低经理的区域

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

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

这是ResultSet:

RESULTS

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

3 个答案:

答案 0 :(得分:1)

试试这个:

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函数,但你可以找到一个等价函数(它返回第一个非null参数)。

答案 1 :(得分:1)

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

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小提琴:http://sqlfiddle.com/#!9/93b45/5

答案 2 :(得分:0)

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
(小提琴基础取自@PhilWalton - 谢谢!PostgreSQL需要RECURSIVE关键字,但DB2不需要)

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

  • 我们自我 - JOINEmployee表以获得下一位经理
  • 因为我们不知道当前经理是否有区域,我们LEFT JOIN到该表。
  • 如果我们找不到某个地区,请再次尝试使用下一位经理。
  • 我们排除了在上一次迭代中收到某个区域的所有行(将抓住“最低”区域)。否则,终止在顶级经理处停止。

最后,在主查询中,我们排除员工没有区域的那些行。大多数情况下,这将删除生成的中间行,直到找到具有区域的管理器,但是如果某个树没有区域(不知何故),它将排除它们。 使用WHERE几乎肯定比DISTINCT所需的散列函数便宜,虽然我不确定递归部分会有什么影响(如果大多数直接管理者有一个区域,或者在一跳内,它可能比做四个连接表现更好)