【问题标题】:How to join multiple rows with the same value for a column如何连接具有相同值的列的多行
【发布时间】:2017-09-25 14:04:33
【问题描述】:

这可能很难理解我在问什么,所以我提前道歉。

我正在尝试连接数据库中具有不同主键的两个表,员工表的主键是 EmployeeID,而联系人表的主键是 ContactID。我尝试在 EmployeeID 上加入他们,但我得到了同一员工的太多重复项,只是与该员工关联的不同电子邮件地址。我希望忽略联系人 ID 并查询表以创建显示名称、ID 并列出电子邮件地址的输出。

这可能吗?

这是一个例子:

员工表:

EmployeeID | Employee Name | Address |
____________ ______________ __________

0001            John         123 Ave.

联系表:

ContactID |     Email     | EmployeeID|
__________ ______________ ______________ 
0001        abc@email.com   001
0002        bcd@email.com   001

我想要的输出是:

EmployeeID| Name         |  Email1      |  Email2      |
__________ ______________ ______________ _______________

001       |  John        |abc@email.com| bcd@email.com

【问题讨论】:

标签: database oracle


【解决方案1】:

如果你使用 SQL Server,你可以使用一些动态 SQL 来完成你想要的。

tl;dr:最后的代码块包含您想要的 SQL。


首先,我们将对查询进行硬编码,以提供我们想要的内容。接下来,我们将研究如何使用动态 SQL 让我们的生活更轻松。我将使用以下示例表:

#Employee
+ ------ + ------------- + ----------- +
| EMP_Id | EMP_Name      | EMP_Address |
+ ------ + ------------- + ----------- +
| 1      | John Jacob    | 123 Ave.    |
| 2      | Ermit Schmidt | 101 St.     |
+ ------ + ------------- + ----------- +

#Contact
+ ------ + --------------- + ------ +
| CNT_Id | CNT_Email       | EMP_Id |
+ ------ + --------------- + ------ +
| 1      | abc@email.com   | 1      |
| 2      | bcd@email.com   | 1      |
| 3      | es@email.com    | 2      |
| 4      | es12@email.com  | 2      |
| 5      | es_01@email.com | 2      |
+ ------ + --------------- + ------ +

1) 硬编码

首先,我们必须对#Contact 表进行排名,以便我们选择每个员工的每个电子邮件地址。以下查询就是这样做的

select  *
    from #Employee emp
    left join (
        select *, ROW_NUMBER() over (partition by emp_id order by cnt_id) as Ranking
            from #Contact
    ) cnt on cnt.EMP_Id = emp.EMP_Id

并生成一个类似的表格

+ ------ + ------------- + ----------- + ------ + --------------- + ------ + ------- +
| EMP_Id | EMP_Name      | EMP_Address | CNT_Id | CNT_Email       | EMP_Id | Ranking |
+ ------ + ------------- + ----------- + ------ + --------------- + ------ + ------- +
| 1      | John Jacob    | 123 Ave.    | 1      | abc@email.com   | 1      | 1       |
| 1      | John Jacob    | 123 Ave.    | 2      | bcd@email.com   | 1      | 2       |
| 2      | Ermit Schmidt | 101 St.     | 3      | es@email.com    | 2      | 1       |
| 2      | Ermit Schmidt | 101 St.     | 4      | es12@email.com  | 2      | 2       |
| 2      | Ermit Schmidt | 101 St.     | 5      | es_01@email.com | 2      | 3       |
+ ------ + ------------- + ----------- + ------ + --------------- + ------ + ------- +

现在我们可以使用Ranking 字段来选择电子邮件,就像这样

select    emp.*
        , case cnt.Ranking when 1 then cnt.CNT_Email end as Email_1
        , case cnt.Ranking when 2 then cnt.CNT_Email end as Email_2
        , case cnt.Ranking when 3 then cnt.CNT_Email end as Email_3
    from #Employee emp
    left join (
        select *, ROW_NUMBER() over (partition by emp_id order by cnt_id) as Ranking
            from #Contact
    ) cnt on cnt.EMP_Id = emp.EMP_Id

这产生了几乎完整的表格:

+ ------ + ------------- + ----------- + ------------- + -------------- + --------------- +
| EMP_Id | EMP_Name      | EMP_Address | Email_1       | Email_2        | Email_3         |
+ ------ + ------------- + ----------- + ------------- + -------------- + --------------- +
| 1      | John Jacob    | 123 Ave.    | abc@email.com | NULL           | NULL            |
| 1      | John Jacob    | 123 Ave.    | NULL          | bcd@email.com  | NULL            |
| 2      | Ermit Schmidt | 101 St.     | es@email.com  | NULL           | NULL            |
| 2      | Ermit Schmidt | 101 St.     | NULL          | es12@email.com | NULL            |
| 2      | Ermit Schmidt | 101 St.     | NULL          | NULL           | es_01@email.com | 
+ ------ + ------------- + ----------- + ------------- + -------------- + --------------- +

添加一个简单的 group by 给了我们想要的东西

select    emp.*
        , max(case cnt.Ranking when 1 then cnt.CNT_Email end) as Email_1
        , max(case cnt.Ranking when 2 then cnt.CNT_Email end) as Email_2
        , max(case cnt.Ranking when 3 then cnt.CNT_Email end) as Email_3
    from #Employee emp
    left join (
        select *, ROW_NUMBER() over (partition by emp_id order by cnt_id) as Ranking
            from #Contact
    ) cnt on cnt.EMP_Id = emp.EMP_Id
    group by  emp.EMP_Id
            , emp.EMP_Name
            , emp.EMP_Address


+ ------ + ------------- + ----------- + ------------- + -------------- + --------------- +
| EMP_Id | EMP_Name      | EMP_Address | Email_1       | Email_2        | Email_3         |
+ ------ + ------------- + ----------- + ------------- + -------------- + --------------- +
| 1      | John Jacob    | 123 Ave.    | abc@email.com | bcd@email.com  | NULL            |
| 2      | Ermit Schmidt | 101 St.     | es@email.com  | es12@email.com | es_01@email.com |
+ ------ + ------------- + ----------- + ------------- + -------------- + --------------- +

2) 动态 SQL

无论员工拥有多少个电子邮件地址,上述大部分查询都保持不变。唯一改变的部分是在选择部分;我们必须问自己:“我们怎么知道要选择多少电子邮件”?在我们的示例中,我们知道我们需要 3 个,但是如果有一个员工有 4 个或 8 个或 100 个电子邮件地址怎么办?这就是动态 SQL 的用武之地。

我们的想法是构建一个带有循环的字符串,该循环构造一个 SQL 语句,然后我们将执行该语句。首先,我们需要知道我们应该循环多少次迭代,所以我们提取#Contact表中的最大排名,像这样

if OBJECT_ID('tempdb..#max_rank') is not null drop table #max_rank
select top 1 COUNT(*) as Max_Rank
    into #max_rank
    from #Contact
    group by EMP_Id
    order by count(*) desc

declare @max_rank int
select @max_rank = Max_Rank from #max_rank

print @max_rank

接下来,我们编写一个循环来构建查询的max(case ...) 部分

declare @sql varchar(max) = ''

declare @iter int = 1
while @iter <= @max_rank
begin
    set @sql = @sql + '
        , max(case cnt.Ranking when ' + cast(@iter as varchar(max)) + ' then cnt.CNT_Email end) as Email_' + cast(@iter as varchar(max))
    set @iter = @iter+1
end

print @sql

然后我们追加不改变的查询的其余部分

set @sql = 
'select    emp.*'
    + @sql
    + '
    from #Employee emp
    left join (
        select *, ROW_NUMBER() over (partition by emp_id order by cnt_id) as Ranking
            from #Contact
    ) cnt on cnt.EMP_Id = emp.EMP_Id
    group by  emp.EMP_Id
            , emp.EMP_Name
            , emp.EMP_Address'

print @sql

综合起来,我们得到了完整的代码

if OBJECT_ID('tempdb..#max_rank') is not null drop table #max_rank
select top 1 COUNT(*) as Max_Rank
    into #max_rank
    from #Contact
    group by EMP_Id
    order by count(*) desc

declare @max_rank int
select @max_rank = Max_Rank from #max_rank

declare @sql varchar(max) = ''

declare @iter int = 1
while @iter <= @max_rank
begin
    set @sql = @sql + '
        , max(case cnt.Ranking when ' + cast(@iter as varchar(max)) + ' then cnt.CNT_Email end) as Email_' + cast(@iter as varchar(max))
    set @iter = @iter+1
end

set @sql = 
'select    emp.*'
    + @sql
    + '
    from #Employee emp
    left join (
        select *, ROW_NUMBER() over (partition by emp_id order by cnt_id) as Ranking
        from #Contact
    ) cnt on cnt.EMP_Id = emp.EMP_Id
    group by  emp.EMP_Id
            , emp.EMP_Name
            , emp.EMP_Address'

print @sql
exec(@sql)

希望这会有所帮助!而且我也希望你使用的是 SQL Server,哈哈。

【讨论】:

  • 嗯,它是一个oracle数据库,通过Visual Studio连接。
  • 我可能会在这里使用 ROW_NUMBER() 而不是 RANK() 即使它可能没问题,因为 cnt_id 应该是唯一的。但是,如果 Order by 字段发生更改,您可能会为每位员工获得多个具有相同 RANK 的记录。在这里也没有充分的理由使用 RANK
  • @MonkeyWithMachine Oracle 还支持动态 SQL。我以前从未使用过 Oracle,所以我不知道上述查询需要更改多少,但希望修改会足够简单。
  • 太棒了。谢谢@kindaTechy。
猜你喜欢
  • 2011-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-19
  • 2016-05-12
  • 1970-01-01
相关资源
最近更新 更多