使用 2 个 EXISTS 子查询改进 sql 查询

improve sql query with 2 EXISTS sub queries

我有这个查询 (mysql):

 SELECT `budget_items`.*
    FROM `budget_items`
    WHERE (budget_category_id = 4
           AND ((is_custom_for_family = 0)
                OR (is_custom_for_family = 1
                    AND custom_item_family_id = 999))
           AND ((EXISTS
                   (SELECT 1
                    FROM balance_histories
                    WHERE balance_histories.budget_item_id = budget_items.id
                      AND balance_histories.family_id = 999
                      AND payment_date >= '2021-02-01'
                      AND payment_date <= '2021-02-28' ))
                OR (EXISTS
                      (SELECT 1
                       FROM budget_lines
                       WHERE family_id = 999
                         AND budget_id = 188311
                         AND budget_item_id = budget_items.id
                         AND amount > 0))))

它在应用程序启动时运行多次。需要10多秒(全部)。

我有索引:

balance_histories table: budget_item_id, family_id(也试过 payment_date)

budget_lines table: family_id, budget_id, budget_item_id

如何提高速度?查询或可能 mysql (8) 配置。

balance_historiestable:

budget_linestable:

我会以与您所拥有的相反的方式开始此查询。假设您可能拥有多年的数据,但您的 EXISTS 查询正在更具体地查看 date-range 或特定预算行,从那里开始,它可能会小得多。一旦您拥有不同的 ID,然后返回到符合条件的 ID 加上附加条件的预算项目。

为了帮助优化查询,我会在

上建立索引
table              index
balance_histories  ( family_id, payment_date, budget_item_id )
budget_lines       ( family_id, budget_id, amount )
budget_items       ( id, budget_category_id, is_custom_for_family, custom_item_family_id )


select
        bi.*
    from
        -- pre-query a list of DISTINCT IDs from the balance history
        -- and budget lines that qualify. THEN join to the rest.
        ( select distinct
                bh.budget_item_id id
            from
                balance_histories bh
            where
                    bh.family_id = 999
                AND bh.payment_date >= '2021-02-01'
                AND bh.payment_date <= '2021-02-28'
        UNION
        select 
                bl.budget_item_id
            FROM 
                budget_lines bl
            WHERE 
                    bl.family_id = 999
                AND bl.budget_id = 188311
                AND bl.amount > 0 ) PQ
            JOIN budget_items bi
                on PQ.id = bi.id
                AND bi.budget_category_id = 4
                AND (       bi.is_custom_for_family = 0
                        OR 
                            (   bi.is_custom_for_family = 1
                            AND bi.custom_item_family_id = 999 )
                    )

意见反馈

至于许多 SQL 查询,通常有多种方法可以获得解决方案。有时使用 EXISTS 效果很好,有时效果不佳。您需要考虑数据的基数,这就是我的目标。首先看看您要的是什么:获取所有类别的预算项目和家庭的自定义项目是 1 或 0(全部),但如果是家庭,则只有 999 的预算项目。您的余额是正确的 AND/OR.但是,这将遍历每条记录,如果您有数百万行,这就是您要扫描的内容。只有在扫描完每一行之后,您现在才针对特定日期范围或 family/budget.

的历史记录进行二次查询(对于符合条件的每条记录)

我的猜测是,从您的两个 EXISTS 查询返回的可能记录数将会非常小。因此,首先获取属于该联合的那些 ID 的 DISTINCT 列表将是非常小的子集。一旦找到单个“ID”,它现在将直接匹配预算项目 table 并具有类别 ID/系列/自定义项目注意事项的最终过滤限制。

通过使索引更好地匹配查询的上下文,WHERE 子句将优化提取数据。我已经用类似的解决方案回答了其他几个问题,并澄清了索引以及为什么在那些... , and another here.