在SQL Server中水平连接两个以上的表
下面是模式在SQL Server中水平连接两个以上的表,sql,sql-server-2008,tsql,outer-join,Sql,Sql Server 2008,Tsql,Outer Join,下面是模式 +---------+---------+ | Employee Table | +---------+---------+ | EmpId | Name | +---------+---------+ | 1 | John | | 2 | Lisa | | 3 | Mike | | | | +---------+---------+ +---------+-----------
+---------+---------+
| Employee Table |
+---------+---------+
| EmpId | Name |
+---------+---------+
| 1 | John |
| 2 | Lisa |
| 3 | Mike |
| | |
+---------+---------+
+---------+-----------------+
| Family Table |
+---------+-----------------+
| EmpId | Relationship |
+---------+-----------------+
| 1 | Father |
| 1 | Mother |
| 1 | Wife |
| 2 | Husband |
| 2 | Child |
+---------+-----------------+
+---------+---------+
| Loan Table |
+---------+--------+
| LoanId | EmpId |
+---------+--------+
| L1 | 1 |
| L2 | 1 |
| L3 | 2 |
| L4 | 2 |
| L5 | 3 |
+---------+--------+
- Employee表和Family表具有一对多关系
- 员工表和贷款表有一对多的关系
+---------+---------+--------------+---------+
| EmpId | Name | RelationShip | Loan |
+---------+---------+--------------+---------+
| 1 | John | Father | L1 |
| - | - | Mother | L2 |
| - | - | Wife | - |
| 2 | Lisa | Husband | L3 |
| - | - | Child | L4 |
| 3 | Mike | - | L5 |
| | | | |
+---------+---------+--------------+---------+
下面概述的方法允许轻松地将更多表“连接”到结果集。它不限于两张桌子 我将使用表变量来说明解决方案。在现实生活中,这些表将是真实的表,当然不是变量,但我将坚持使用变量,以使此示例脚本易于运行和尝试
declare @TEmployee table (EmpId int, Name varchar(50));
declare @TFamily table (EmpId int, Relationship varchar(50));
declare @TLoan table (EmpId int, LoanId varchar(50));
insert into @TEmployee values (1, 'John');
insert into @TEmployee values (2, 'Lisa');
insert into @TEmployee values (3, 'Mike');
insert into @TFamily values (1, 'Father');
insert into @TFamily values (1, 'Mother');
insert into @TFamily values (1, 'Wife');
insert into @TFamily values (2, 'Husband');
insert into @TFamily values (2, 'Child');
insert into @TLoan values (1, 'L1');
insert into @TLoan values (1, 'L2');
insert into @TLoan values (2, 'L3');
insert into @TLoan values (2, 'L4');
insert into @TLoan values (3, 'L5');
我们需要一个数字表
同样,在现实生活中,你会有一个合适的数字表,但对于这个例子,我将使用以下内容:
declare @TNumbers table (Number int);
insert into @TNumbers values (1);
insert into @TNumbers values (2);
insert into @TNumbers values (3);
insert into @TNumbers values (4);
insert into @TNumbers values (5);
我的方法背后的主要思想是创建一个helper表,该表首先包含每个EmpId
的正确行数,然后使用该表高效地获得结果
我们将从计算每个EmpId
的关系和贷款数量开始:
WITH
CTE_Rows
AS
(
SELECT Relationships.EmpId, COUNT(*) AS EmpRows
FROM @TFamily AS Relationships
GROUP BY Relationships.EmpId
UNION ALL
SELECT Loans.EmpId, COUNT(*) AS EmpRows
FROM @TLoan AS Loans
GROUP BY Loans.EmpId
)
,CTE_MaxRows
AS
(
SELECT
CTE_Rows.empid
,MAX(CTE_Rows.EmpRows) AS MaxEmpRows
FROM CTE_Rows
GROUP BY CTE_Rows.empid
)
然后计算每个EmpId
的最大行数:
WITH
CTE_Rows
AS
(
SELECT Relationships.EmpId, COUNT(*) AS EmpRows
FROM @TFamily AS Relationships
GROUP BY Relationships.EmpId
UNION ALL
SELECT Loans.EmpId, COUNT(*) AS EmpRows
FROM @TLoan AS Loans
GROUP BY Loans.EmpId
)
,CTE_MaxRows
AS
(
SELECT
CTE_Rows.empid
,MAX(CTE_Rows.EmpRows) AS MaxEmpRows
FROM CTE_Rows
GROUP BY CTE_Rows.empid
)
上面的CTE为每个EmpId
:EmpId
本身有一行,以及此EmpId
的最大关系数或贷款数。现在我们需要展开这个表,为每个EmpId
生成给定数量的行。这里我使用的是数字表:
,CTE_RowNumbers
AS
(
SELECT
CTE_MaxRows.empid
,Numbers.Number AS rn
FROM
CTE_MaxRows
CROSS JOIN @TNumbers AS Numbers
WHERE
Numbers.Number <= CTE_MaxRows.MaxEmpRows
)
现在,我们已经准备好加入这一切CTE_RowNumbers
具有我们所需的精确行数,因此简单的左连接就足够了:
,CTE_Data
AS
(
SELECT
CTE_RowNumbers.empid
,CTE_Relationships.Relationship
,CTE_Loans.LoanId
FROM
CTE_RowNumbers
LEFT JOIN CTE_Relationships ON CTE_Relationships.EmpId = CTE_RowNumbers.EmpId AND CTE_Relationships.rn = CTE_RowNumbers.rn
LEFT JOIN CTE_Loans ON CTE_Loans.EmpId = CTE_RowNumbers.EmpId AND CTE_Loans.rn = CTE_RowNumbers.rn
)
我们差不多完成了。主Employee
表中可能有一些EmpId
没有任何相关数据,例如示例数据中的EmpId=3
。要在结果集中获得这些empid
,我将把CTE_数据
加入主表,并用破折号替换null
:
SELECT
Employees.EmpId
,Employees.Name
,ISNULL(CTE_Data.Relationship, '-') AS Relationship
,ISNULL(CTE_Data.LoanId, '-') AS LoanId
FROM
@TEmployee AS Employees
LEFT JOIN CTE_Data ON CTE_Data.EmpId = Employees.EmpId
ORDER BY Employees.EmpId, Relationship, LoanId;
要获得完整的脚本,只需将本文中的所有代码块按此处显示的顺序放在一起
这是结果集:
EmpId Name Relationship LoanId
1 John Father L1
1 John Mother L2
1 John Wife -
2 Lisa Child L3
2 Lisa Husband L4
3 Mike - L5
看起来您正试图“按顺序”将贷款分配给族表中的行。解决这个问题的方法是首先获得正确的行,然后获得分配给行的贷款
右行(和前三列)为:
请注意,这不会在重复值的列中放置连字符,而是放置实际值。虽然可以在SQL中安排连字符,但这不是一个好主意。SQL结果以表的形式出现,表是无序的集合,每列和每行都有值。在开始放置连字符时,取决于顺序
现在的问题是加入贷款。这实际上非常简单,使用行编号()
添加连接
键:
select f.EmpId, e.Name, f.Relationship, l.LoanId
from Employee e left join
(select f.*, row_number() over (partition by f.EmpId order by (select NULL)) as seqnum
from family f
) f
on f.empid = e.empid left join
(select l.*, row_number() over (partition by l.EmpId order by (select NULL)) as seqnum
from Loan l
) l
on f.EmpId = l.EmpId and f.seqnum = l.seqnum;
请注意,这并不保证为给定员工分配贷款的顺序。您的数据似乎没有足够的信息来处理更一致的分配。弗拉基米尔·巴拉诺夫已经编写了一个很好的解决方案,但它相当长(并且有一个小问题:您想要丈夫-L3和孩子-L4,但此解决方案返回孩子-L3和丈夫-L4)
Gordon Linoff编写了一个较短的解决方案,但它无法正常工作
我可以修复Gordon的解决方案,如下所示:
SELECT e.EmpId, e.Name, f.Relationship, l.LoanId
FROM @TEmployee e
LEFT JOIN (
SELECT f.*, ROW_NUMBER() OVER (PARTITION BY f.EmpId ORDER BY (SELECT NULL)) AS seqnum
FROM @TFamily f
) f ON f.empid = e.empid
LEFT JOIN (
SELECT l.*, ROW_NUMBER() OVER (PARTITION BY l.EmpId ORDER BY (SELECT NULL)) AS seqnum
FROM @TLoan l
) l ON l.EmpId = e.EmpId AND (f.seqnum = l.seqnum OR f.seqnum IS NULL)
然而,我宁愿说这个问题是不正确的,因为它要求我们任意地将家庭成员与特定的贷款相匹配(当不存在真正的关系时)
我宁愿说正确的问题是有以下答案的问题:
SELECT e.EmpId, e.Name,
SUBSTRING((
SELECT ', '+f.Relationship AS '*'
FROM @TFamily f
WHERE f.EmpId=e.EmpId
FOR XML PATH(''), TYPE
).value('.','nvarchar(4000)'),3,4000) AS FamilyMembers,
SUBSTRING((
SELECT ', '+l.LoanId AS '*'
FROM @TLoan l
WHERE l.EmpId=e.EmpId
FOR XML PATH(''), TYPE
).value('.','nvarchar(4000)'),3,4000) AS Loans
FROM @TEmployee e
贷款属于员工(1:N),家庭关系也属于员工(1:N)。如何将贷款L2和L4归于非员工?此信息不包含在数据中?select没有设置的顺序,也没有生成此信息的排序。对现有列没有办法做到这一点。@VladimirBaranov。现在应该是了。我用错误的表启动了左join
s链。