如何在复杂的 JOIN 中删除重复项
How to remove duplicates in a complicated JOIN
我有一个 EMPLOYEE table,它也有内置的层次结构(使用经理列)
我有另一个 REGION table 代表经理-地区关系
我正在尝试创建一个 SQL,它将通过跟进层次结构链来显示哪些员工属于哪个区域。
约束/规则:
- 员工的直属经理可能没有区域 - 所以我需要继续向上链。
我保证链上4级有人会有区域
如果在第 4 级之前找到区域,则使用较低管理器的区域
这是我想出的天真 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
之后的所有内容):
- 我们自己
JOIN
到Employee
table去请下一位经理
- 因为我们不知道当前经理是否有区域,所以我们
LEFT JOIN
到 table。
- 如果我们没有找到区域,请使用下一位管理员再试一次。
- 我们排除了所有在上一次迭代中接收到区域(将获取 'lowest' 区域)的行。否则,终止会在顶级经理处停止。
最后,在主查询中,我们排除了雇员没有地区的那些行。大多数情况下,这将删除搜索时生成的中间行,直到找到具有区域的管理器,尽管如果某些树没有区域(以某种方式),它将排除它们。
使用 WHERE
几乎肯定比 DISTINCT
所需的散列函数更便宜,尽管我不确定递归部分会产生什么影响(如果大多数直接管理者有一个区域,或者在一个跃点内,它可能比做四个连接表现得更好)
我有一个 EMPLOYEE table,它也有内置的层次结构(使用经理列)
我有另一个 REGION table 代表经理-地区关系
我正在尝试创建一个 SQL,它将通过跟进层次结构链来显示哪些员工属于哪个区域。
约束/规则:
- 员工的直属经理可能没有区域 - 所以我需要继续向上链。
我保证链上4级有人会有区域
如果在第 4 级之前找到区域,则使用较低管理器的区域
这是我想出的天真 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
之后的所有内容):
- 我们自己
JOIN
到Employee
table去请下一位经理 - 因为我们不知道当前经理是否有区域,所以我们
LEFT JOIN
到 table。 - 如果我们没有找到区域,请使用下一位管理员再试一次。
- 我们排除了所有在上一次迭代中接收到区域(将获取 'lowest' 区域)的行。否则,终止会在顶级经理处停止。
最后,在主查询中,我们排除了雇员没有地区的那些行。大多数情况下,这将删除搜索时生成的中间行,直到找到具有区域的管理器,尽管如果某些树没有区域(以某种方式),它将排除它们。
使用 WHERE
几乎肯定比 DISTINCT
所需的散列函数更便宜,尽管我不确定递归部分会产生什么影响(如果大多数直接管理者有一个区域,或者在一个跃点内,它可能比做四个连接表现得更好)