【问题标题】:Conditional Join on multiple fields in the same table同一张表中多个字段的条件连接
【发布时间】:2013-05-16 16:38:01
【问题描述】:

我有一个场景,您有两个表,如下所示,两个表中都填充了 person_id 或 organization_id

表_1

email_id, person_id, organisation_id, email_address   Usage  
1         NULL       12               O12@EMAIL.COM   WorkEmail  
2         12         NULL             P12@EMAIL.COM   WorkEmail  
3         13         NULL             P13@EMAIL.COM   WorkEmail  
4         14         NULL             P14@EMAIL.COM   WorkEmail  
5         NULL       13               O13@EMAIL.COM   WorkEmail  
6         14         NULL             P14_p@EMAIL.COM PersonalEmail  
7         NULL       14               O14@EMAIL.COM   PersonalEmail  
8         13         NULL             P13_2@EMAIL.COM WorkEmail 

表_2

registration_id, person_id, organisation_id, name, registration_Date  
1                NULL       12                ORG12 10/05/2013   
2                12         NULL              P12   10/05/2013  
3                13         NULL              P13   10/05/2013  
4                14         NULL              P14   10/05/2013  
5                NULL       13                O13   10/05/2013  
6                NULL       14                O14   10/05/2013  

我需要一份选择声明,它会给我每条注册记录的工作邮箱;注册记录有多个工作电子邮件地址 那么应该选择第一条记录(例如表 1 中 email_id 为 3 和 8 的记录):

registration_id, person_id, organisation_id, name, email address  
1                NULL       12           ORG12 O12@EMAIL.COM   
2                12         NULL         P12   P12@EMAIL.COM  
3                13         NULL         P13   P13@EMAIL.COM  
4                14         NULL         P14   P14@EMAIL.COM  
5                NULL       13           O13   O13@EMAIL.COM  
6                NULL       14           O14   NULL  

我已尝试执行以下操作,但不太确定这是否是最有效的方法;此外,它并没有完全满足我的需求:

SELECT t1.registration_id, t1.person_id, t1.organisation_id, t1.name, t2.email_Address
FROM table2 t1
LEFT JOIN table1 ON t2.person_id = t1.person_id
    OR
    t2.organisation_id = t1.organisation_id

【问题讨论】:

    标签: sql-server-2000 left-join conditional-statements coalesce


    【解决方案1】:

    修改后的答案

    /* setup */
    create table Table_1
    (
          email_id bigint not null --identity(1,1)
        , person_id bigint 
        , organisation_id bigint
        , email_address nvarchar(256) not null
        , Usage nvarchar(16) not null
    )
    insert Table_1 (email_id, person_id, organisation_id, email_address,   Usage)
          select 1         ,NULL       ,12               ,'O12@EMAIL.COM'   ,'WorkEmail'  
    union select 2         ,12         ,NULL             ,'P12@EMAIL.COM'   ,'WorkEmail'  
    union select 3         ,13         ,NULL             ,'P13@EMAIL.COM'   ,'WorkEmail'  
    union select 4         ,14         ,NULL             ,'P14@EMAIL.COM'   ,'WorkEmail'  
    union select 5         ,NULL       ,13               ,'O13@EMAIL.COM'   ,'WorkEmail'  
    union select 6         ,14         ,NULL             ,'P14_p@EMAIL.COM' ,'PersonalEmail'  
    union select 7         ,NULL       ,14               ,'O14@EMAIL.COM'   ,'PersonalEmail'  
    union select 8         ,13         ,NULL             ,'P13_2@EMAIL.COM' ,'WorkEmail' 
    
    create table Table_2
    (
          registration_id bigint not null --identity(1,1)
        , person_id bigint
        , organisation_id bigint
        , name nvarchar(32) not null
        , registration_Date date not null
    )
    insert Table_2 (registration_id, person_id, organisation_id, name, registration_Date)
          select 1                ,NULL       ,12                ,'ORG12' ,'10/05/2013'   
    union select 2                ,12         ,NULL              ,'P12'   ,'10/05/2013' 
    union select 3                ,13         ,NULL              ,'P13'   ,'10/05/2013'
    union select 4                ,14         ,NULL              ,'P14'   ,'10/05/2013'
    union select 5                ,NULL       ,13                ,'O13'   ,'10/05/2013'
    union select 6                ,NULL       ,14                ,'O14'   ,'10/05/2013'
    
    
    /* get the results */
    SELECT t2.registration_id, t2.person_id, t2.organisation_id, t2.name, t1.email_Address
    FROM table_2 t2
    left outer join 
    (
        select person_id, organisation_id, email_address
        from Table_1 a
        inner join 
        (
            select MIN(email_id) email_id
            from Table_1 
            where Usage = 'WorkEmail'       
            group by person_id, organisation_id
        ) b
        on a.email_id = b.email_id      
    ) t1 
        ON t2.person_id = t1.person_id
        OR t2.organisation_id = t1.organisation_id
    

    原答案

    我想这就是你所追求的:

    select x.registration_id, x.person_id, x.organisation_id, x.name, x.email_Address
    from
    (
        SELECT t2.registration_id, t2.person_id, t2.organisation_id, t2.name, t1.email_Address, t1.usage
        , row_number() over (partition by t2.registration_id, t1.usage order by t1.email_id) r
        FROM table_2 t2
        LEFT JOIN table_1 t1 
            ON t2.person_id = t1.person_id
            OR t2.organisation_id = t1.organisation_id
    ) x
    where (x.r = 1 and x.usage = 'WorkEmail') --limit to the first email address if there are multiple work email matches for the same registration (table2) record
    or x.usage <> 'WorkEmail' --if it's not work email, don't limit the number
    

    【讨论】:

    • 感谢您的回复。最后一行应该是:还是 x.usage 'PersonalEmail'?另外,我们的数据库是在 SQL Server 2000 上,row_number 不是一个公认的函数,有没有替代方法?
    • 不等于工作电子邮件就像我最初想要的那样,尽管您想要所有个人电子邮件,并且只是每封工作邮件的第一封 - 现在修改为只返回每封工作邮件的第一封。可悲的是,我无法模拟 SQL2000 / 不知道确切支持什么,但希望我修改后的答案适用于此;让我知道。 . .
    • 需要我说最后一个查询就像一个魅力:-) 非常感谢!
    【解决方案2】:

    ps。添加第二个答案以涵盖另一点。与其在任何地方都使用 Organisation_ID 和 Person_ID 来保存两者的详细信息,不如创建一个名为 Party 的表,该表为每个组织和个人提供唯一的 id - 然后将其与 Organization/Person 记录相关联。现在,您只需将一列用于您希望能够关联个人 ID 和组织 ID 的任何表。

    这是一种来自 OO(面向对象)世界的常见模式,称为派对模式或派对模型(谷歌这些术语以了解更多信息)。

    看看/玩一下下面的示例代码,以更好地了解它是如何工作的/有任何问题请给我留言:

    /* setup */
    if OBJECT_ID('Registrations') is not null drop table Registrations
    if OBJECT_ID('PartyContact') is not null drop table PartyContact
    if OBJECT_ID('ContactType') is not null drop table ContactType
    if OBJECT_ID('Organisation') is not null drop table Organisation
    if OBJECT_ID('Person') is not null drop table Person
    if OBJECT_ID('Party') is not null drop table Party
    
    go
    create table ContactType
    (
        contactType_id int not null identity(1,1) constraint PK_ContactType primary key clustered
        , name nvarchar(16) not null constraint UK_ContactType_Name unique 
    )
    go
    set identity_insert ContactType on
    insert ContactType (contactType_id, name)
          select 1, 'WorkEmail'
    union select 2, 'PersonalEmail'
    union select 3, 'Mobile/Cell'
    set identity_insert ContactType off
    go
    
    create table Party
    (
        party_id bigint not null identity(1,1) constraint PK_Party primary key clustered
        , name nvarchar(256) not null --this is duplicating the name on the Person/Organisation tables; normally this denormalisation would be bad practice, but here it assists in making data available in the table from which it will be referenced
        --any other attributes which you want to be common to all parties
    )
    go
    set identity_insert Party on
    insert Party (party_id, name)
          select 12, 'Rob Ottow'
    union select 13, 'Ann Droid'
    union select 14, 'Si Bermann'
    union select 112, 'Global Mega Org'
    union select 113, 'GeoTech Solutions'
    union select 114, 'Think Ink inc.'
    set identity_insert Party off
    go
    
    create table Person
    (
        person_id  bigint not null identity(1,1) constraint PK_Person primary key clustered
        , name nvarchar(256) not null
        , party_id bigint not null constraint FK_Person_PartyId references Party(party_id) 
                                   constraint UK_Person_PartyId unique
        , dob date
    )
    go
    set identity_insert Person on
    insert Person (person_id, name, party_id, dob)
          select 2, 'Rob Ottow' , 12, '1984-12-25'
    union select 3, 'Ann Droid' , 13, null --it's impolite to give a woman's age
    union select 4, 'Si Bermann', 14, '1973-06-12'
    set identity_insert Person off
    go
    
    create table Organisation --en-gb spelling since that's where I'm from
    (
        organisation_id  bigint not null identity(1,1) constraint PK_Organisation primary key clustered
        , name nvarchar(256) not null
        , party_id bigint not null constraint FK_Organisation_PartyId references Party(party_id)
        , taxNumber nchar(12) not null
    )
    go
    set identity_insert Organisation on
    insert Organisation (organisation_id, name, party_id, taxNumber)
          select 1, 'Global Mega Org'   , 112, '123456789012'
    union select 2, 'GeoTech Solutions' , 113, '012345678901'
    union select 3, 'Think Ink inc.'    , 114, '901234567890'
    set identity_insert Organisation off
    go
    
    create table PartyContact
    (
          partyContact_id bigint not null identity(1,1) constraint PK_PartyContract primary key clustered
        , party_id bigint not null constraint FK_PartyContract_PartyId foreign key references Party(party_id)
        , contactDetails nvarchar(256) not null
        , contactType_id int not null constraint FK_PartyContract_ContactTypeId foreign key references ContactType(contactType_id)
    )
    go
    set identity_insert PartyContact on
    insert PartyContact (partyContact_id, party_id, contactDetails,   contactType_id)
          select 1         ,112        ,'O12@EMAIL.COM'   ,1
    union select 2         ,12         ,'P12@EMAIL.COM'   ,1
    union select 3         ,13         ,'P13@EMAIL.COM'   ,1
    union select 4         ,14         ,'P14@EMAIL.COM'   ,1
    union select 5         ,113        ,'O13@EMAIL.COM'   ,1
    union select 6         ,14         ,'P14_p@EMAIL.COM' ,2
    union select 7         ,114        ,'O14@EMAIL.COM'   ,2
    union select 8         ,13         ,'P13_2@EMAIL.COM' ,1
    union select 9         ,13         ,'01234 567890'    ,3
    set identity_insert PartyContact off
    go
    
    create table Registrations
    (
          registration_id bigint not null identity(1,1) constraint PK_Registrations primary key clustered
        , party_id bigint not null constraint FK_Registrations_PartyId references Party(party_id)
        , name nvarchar(32) not null
        , registration_Date date not null
    )
    go
    set identity_insert Registrations on
    insert Registrations (registration_id, party_id, name, registration_Date)
          select 1                ,112        ,'ORG12' ,'10/05/2013'   
    union select 2                ,12         ,'P12'   ,'10/05/2013' 
    union select 3                ,13         ,'P13'   ,'10/05/2013'
    union select 4                ,14         ,'P14'   ,'10/05/2013'
    union select 5                ,113        ,'O13'   ,'10/05/2013'
    union select 6                ,114        ,'O14'   ,'10/05/2013'
    set identity_insert Registrations off
    go
    
    /* get the results */
    SELECT r.registration_id, r.party_id, p.person_id, o.organisation_id, r.name, c.contactDetails
    FROM Registrations r
    left outer join Person p on p.party_id = r.party_id
    left outer join Organisation o on o.party_id = r.party_id
    left outer join 
    (
        select party_id, contactDetails
        from PartyContact a
        inner join 
        (
            select MIN(partyContact_id) partyContact_id
            from PartyContact 
            where contactType_id in (select contactType_id from ContactType where name = 'WorkEmail')
            group by party_id
        ) b
        on a.partyContact_id = b.partyContact_id      
    ) c 
        ON r.party_id = c.party_id
    

    【讨论】: