下面概述的方法允许轻松地将更多表“连接”到结果集。不限于两张表。
我将使用表变量来说明解决方案。在现实生活中,这些表当然是真实的表,而不是变量,但我会坚持使用变量,以使这个示例脚本易于运行和尝试。
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');
我们需要一个数字表。
SQL, Auxiliary table of numbers
http://web.archive.org/web/20150411042510/http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-numbers-table.html
http://dataeducation.com/you-require-a-numbers-table/
同样,在现实生活中,您将拥有一个适当的数字表,但对于本示例,我将使用以下内容:
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);
我的方法背后的主要思想是首先创建一个帮助表,该表将包含每个 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
)
然后我们计算每个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 生成给定的行数。这里我使用Numbers 表:
,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_Relationships
AS
(
SELECT
Relationships.EmpId
,ROW_NUMBER() OVER (PARTITION BY Relationships.EmpId ORDER BY Relationships.Relationship) AS rn
,Relationships.Relationship
FROM @TFamily AS Relationships
)
,CTE_Loans
AS
(
SELECT
Loans.EmpId
,ROW_NUMBER() OVER (PARTITION BY Loans.EmpId ORDER BY Loans.LoanId) AS rn
,Loans.LoanId
FROM @TLoan AS Loans
)
现在我们准备好将这一切结合在一起。 CTE_RowNumbers 有我们需要的确切行数,所以简单的LEFT JOIN 就足够了:
,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 表可能有一些EmpIds 没有任何相关数据,例如您的示例数据中的EmpId = 3。为了在结果集中获得这些EmpIds,我将把CTE_Data 加入到主表中,并将NULLs 替换为破折号:
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