如何使用条件连接选择重复项的表

Posted

技术标签:

【中文标题】如何使用条件连接选择重复项的表【英文标题】:How to join tables selecting duplicates with condition 【发布时间】:2021-12-28 18:26:31 【问题描述】:

我有两张桌子

TicketHeaders TH

ticketID Amount
1 600
2 900
3 400

TicketBody TB

ticketID SellerName SellerType
1 Karen Manager
1 James Trainee
2 John Manager
3 James Trainee

我需要的是一张桌子 TicketID - Amount - SellerName,但如果我有一张有 2 个卖家的票,我只需要为该票选择经理。

输出表应该是:

ticketID Amount SellerName
1 600 Karen
2 900 John
3 400 James

如果我使用左连接,我会得到票 1 的重复金额

SELECT TH.ticketID, TH.Amount, TB.SellerName
FROM TH
LEFT JOIN TB ON TH.ticketID = TB.ticketID

【问题讨论】:

【参考方案1】:
SELECT TH.ticketID, TH.Amount, COALESCE(TB_M.SellerName, TB_T.SellerName)
FROM TH
LEFT JOIN TB TB_M ON TH.ticketID = TB_M.ticketID AND TB_M.SellerType = 'Manager'
LEFT JOIN TB TB_T ON TH.ticketID = TB_T.ticketID AND TB_T.SellerType <> 'Manager'

【讨论】:

【参考方案2】:

根据声明的 2.5 版,您似乎无法使用 row_number() 解决方案。您可以使用单个内部连接来解决此问题。我不知道避免额外加入 Firebird 是否有任何好处。

select ticketID, min(Amount) as Amount,
    case min(case SellerType when 'Manager' then 1 else 2 end) when 1
        then min(case SellerType =  'Manager' then SellerName end)
        else min(case SellerType <> 'Manager' then SellerName end)
    end SellerName
from TH th inner join TB tb on tb.ticketID = th.ticketID
group by ticketID

另一个好处是,这将适用于不同卖家的更大层次结构(通过添加新案例)。但如果在一个级别有多个卖家,它就行不通了。

【讨论】:

【参考方案3】:

在这种情况下我会使用 row_number():

with _cte as (
     SELECT TH.ticketID, 
            TH.Amount, 
            TB.SellerName, 
            row_number() over (partition by TH.ticketID order by case when SellerType = 'Manager' then 0 else 1 end) as rn
     FROM TH
     LEFT JOIN TB ON TH.ticketID = TB.ticketID
)
select ticketID, Amount, SellerName
from _cte 
where rn = 1

【讨论】:

row_number() 在 Firebird 3.0 中引入,OP 使用的是 Firebird 2.5。【参考方案4】:

A.S.我认为您的问题尚未明确定义:

我只需要为该特定工单选择经理

如果有很多行,但有两个或更多经理怎么办?零经理?如果没有考虑清楚,就像保证 SQL Server 永远不会允许插入此类数据一样,这是一个等待发生的错误。

我还认为 SellerType 最好是一个整数字段,是一些 Seller_types 字典表的外键 - 既因为整数字段更容易 indexed 和比较 joining 表,因为那会允许您以后随心所欲地重命名“功能角色”(或按照您的职责),而无需更改任何内容:您的 Seller_types 可以有额外的列,如整数 role_priority 甚至像 max_persons_of_type_in_one_ticket 之类的东西(你老板例如,可能决定一张票可以有两个经理,或者一个经理和一个副经理,然后不超过四名学员)。

但回到这个问题,还有另一种方法可以做到这一点。事实上,它会为TicketHeaders 表中的每一行运行一个correlated sub-query,因此如果您对数千行进行“长”选择,它可能会更慢。特别是如果您保留 Firebird 的默认小内存缓存(请参阅 ib-aid.com 上有关配置 Firebird 和宽松配置的文章)。

另一方面,它需要“做一次就忘记”,使您的查询更简单,从而减少您将来出错的机会。对于短(100 行或更少的行)查询,速度损失可能并不明显。而且,如果您根本不查询该新列(您不会在生产中使用 select * 查询,对吗?),也不会受到任何惩罚。

所以,代码,最后:dbfiddle here

create table Ticket_Headers (
  ticket_id integer primary key, 
  amount integer not null
)
create table Ticket_Body (
  ticket_id integer 
     REFERENCES Ticket_Headers(ticket_id) 
        ON DELETE CASCADE
        ON UPDATE CASCADE, 
  Seller_Name varchar(20) not null,
  Seller_Type varchar(20) not null,
  CONSTRAINT TicketBody_PK PRIMARY KEY (ticket_id, Seller_Name)
)  
create index idx_TicketBody_Type on Ticket_Body(Seller_Type)
insert into Ticket_Headers
select 1, 600 from rdb$database union all
select 2, 900 from rdb$database union all
select 3, 400 from rdb$database 
3 行受影响
insert into Ticket_body
select 1, 'Karen', 'Manager' from rdb$database union all
select 1, 'James', 'Trainee' from rdb$database union all
select 2, 'John',  'Manager' from rdb$database union all
select 3, 'James', 'Trainee' from rdb$database 
4 行受影响
select * from Ticket_Headers
TICKET_ID |数量 --------: | -----: 1 | 600 2 | 900 3 | 400
select * from Ticket_Body 
TICKET_ID | SELLER_NAME | SELLER_TYPE --------: | :------------ | :---------- 1 |凯伦 |经理 1 |詹姆斯 |实习生 2 |约翰 |经理 3 |詹姆斯 |实习生
select * from Ticket_Headers TH, Ticket_Body TB where TH.ticket_id = TB.ticket_ID
TICKET_ID |数量 | TICKET_ID | SELLER_NAME | SELLER_TYPE --------: | -----: | --------: | :------------ | :---------- 1 | 600 | 1 |凯伦 |经理 1 | 600 | 1 |詹姆斯 |实习生 2 | 900 | 2 |约翰 |经理 3 | 400 | 3 |詹姆斯 |实习生
alter table Ticket_Headers
  add Seller_Top computed by 
  ( -- this parenthesis is required by COMPUTED BY SQL syntax
   ( -- this parenthesis is required to coerce SELECT from query to expression 
    select First(1) TB.Seller_Name
      from Ticket_Body TB
     where TB.ticket_id = Ticket_Headers.ticket_id
    order by TB.Seller_type /* Descending - if other order to be needed */ 
   ) 
  )
select * from Ticket_Headers
TICKET_ID |数量 | SELLER_TOP --------: | -----: | :--------- 1 | 600 |凯伦 2 | 900 |约翰 3 | 400 |詹姆士

前面提到的Seller_types.role_priority 会更加灵活,因此对于这样的order-by 来说是面向未来的方法。

【讨论】:

以上是关于如何使用条件连接选择重复项的表的主要内容,如果未能解决你的问题,请参考以下文章

如何连接具有选择性重复记录的表? (oracle10g)

如何使用 SQL 和 Python 连接两个具有日期条件的表?

如何在没有任何重复行的情况下连接两个表中的表?

如何在不删除先前相同值的情况下选择具有重复项的列表中的特定数字?

要提高SQL查询效率where语句条件的先后次序应如何写

要提高SQL查询效率where语句条件的先后次序应如何写