You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

LEFT JOIN替代方案:优化Invoice/Note关联DocumentBookingDate的慢查询

优化LEFT JOIN + UNION ALL查询的性能问题

表结构

Invoice表

Id| Number | Amount
1 |   001  |  10

Note表

Id| Number | Amount
2 |   002  |  20

DocumentBookingDate表(外键关联:InvoiceId对应Invoice.Id,NoteId对应Note.Id)

NoteId| InvoiceId | Date
 NULL |   1       | 02-02-2022
  2   |   NULL    | 02-03-2022  

原查询及性能问题

原查询通过LEFT JOIN关联日期表后执行UNION ALL,性能极差;移除LEFT JOIN后速度恢复正常,且存在部分Invoice/Note无对应预订日期的场景:

SELECT inv.Id as ID, inv.Number as Number, inv.Amount, dbd.Date as BookingDate 
FROM Invoice inv
LEFT JOIN DocumentBookingDate dbd on dbd.InvoiceId = inv.Id

UNION ALL 

SELECT note.Id as ID, note.Number as Number, note.Amount, dbd.Date as BookingDate 
FROM Note note
LEFT JOIN DocumentBookingDate dbd on dbd.NoteId = note.Id

将LEFT JOIN移至UNION ALL外部后,查询速度进一步变慢。

替代优化方案

1. 子查询过滤后关联

先分别提取Invoice和Note对应的日期数据,减少JOIN时的数据集,避免重复全表扫描日期表:

SELECT inv.Id as ID, inv.Number as Number, inv.Amount, inv_dates.Date as BookingDate
FROM Invoice inv
LEFT JOIN (
    SELECT InvoiceId, Date
    FROM DocumentBookingDate
    WHERE InvoiceId IS NOT NULL
) inv_dates ON inv_dates.InvoiceId = inv.Id

UNION ALL

SELECT note.Id as ID, note.Number as Number, note.Amount, note_dates.Date as BookingDate
FROM Note note
LEFT JOIN (
    SELECT NoteId, Date
    FROM DocumentBookingDate
    WHERE NoteId IS NOT NULL
) note_dates ON note_dates.NoteId = note.Id

子查询提前过滤出仅属于Invoice/Note的记录,让数据库能更高效地利用索引,减少JOIN的计算开销。

2. 使用OUTER APPLY(适用于SQL Server等支持的数据库)

如果每个Invoice/Note对应最多一条日期记录,用APPLY替代LEFT JOIN可以生成更轻量的执行计划:

SELECT inv.Id as ID, inv.Number as Number, inv.Amount, dbd.Date as BookingDate
FROM Invoice inv
OUTER APPLY (
    SELECT TOP 1 Date
    FROM DocumentBookingDate
    WHERE InvoiceId = inv.Id
) dbd

UNION ALL

SELECT note.Id as ID, note.Number as Number, note.Amount, dbd.Date as BookingDate
FROM Note note
OUTER APPLY (
    SELECT TOP 1 Date
    FROM DocumentBookingDate
    WHERE NoteId = note.Id
) dbd

APPLY会逐行匹配主表数据,避免LEFT JOIN可能带来的不必要数据膨胀,配合索引使用时性能提升明显。

3. 补充针对性索引

性能差的核心原因大概率是缺少合适的索引,给DocumentBookingDate表添加两个关联专用索引:

-- 针对Invoice关联的索引
CREATE INDEX IX_DocumentBookingDate_InvoiceId 
ON DocumentBookingDate(InvoiceId) 
INCLUDE(Date);

-- 针对Note关联的索引
CREATE INDEX IX_DocumentBookingDate_NoteId 
ON DocumentBookingDate(NoteId) 
INCLUDE(Date);

添加索引后,原查询的性能可能直接达标,无需修改查询逻辑。

4. 统一数据结构后单次关联

先合并Invoice、Note的数据集,同时将日期表的数据统一格式,再进行一次JOIN:

WITH UnifiedDates AS (
    SELECT InvoiceId AS DocId, Date FROM DocumentBookingDate WHERE InvoiceId IS NOT NULL
    UNION ALL
    SELECT NoteId AS DocId, Date FROM DocumentBookingDate WHERE NoteId IS NOT NULL
),
AllDocs AS (
    SELECT Id AS DocId, Number, Amount FROM Invoice
    UNION ALL
    SELECT Id AS DocId, Number, Amount FROM Note
)
SELECT ad.DocId AS ID, ad.Number, ad.Amount, ud.Date AS BookingDate
FROM AllDocs ad
LEFT JOIN UnifiedDates ud ON ad.DocId = ud.DocId

这种方式减少了JOIN的次数,适合数据量较大的场景,让数据库可以一次性处理所有关联逻辑。


内容的提问来源于stack exchange,提问作者SJMan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 18:04:52