【问题标题】:Need help reducing very large MySQL query需要帮助减少非常大的 MySQL 查询
【发布时间】:2015-10-20 11:09:25
【问题描述】:

我正在开展一个项目,用于管理现场项目关键绩效指标 (KPI) 的提交。每个提交都附有绩效衡量 (PM),这些是在提交进入现场时决定的。

用户根据完成程度为 PM 打分。我现在正在使用所有这些数据制作一份报告。我有一个有效的查询,但它非常大而且很麻烦。我正在尝试找到一种简化它的方法。

我有一个生成 KPI 列表的查询。有了这个列表,我然后应用一些子查询和简单的数学来产生那个时期的“分数”。

有 13 个句点。分数是根据本年度已完成提交的绩效衡量标准计算的。有两种类型的提交“面板”和“程序”。

需要为每个时期的两种提交类型计算分数。然后取这些分数的平均值并计算最终分数。

最终得分称为痛苦/收获指标。我们将计算出的分数除以 100,然后乘以 0.3。如果数字小于 1,则我们从痛苦/收益中减去 1,如果数字大于 0,则分别将其加 1。

第1期不同,我们只需要计算提交的节目,见下图:

第 1 期

(
    SELECT
    ROUND(AVG(sp.progress), 2)
    FROM `submissions` AS sub
    LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
    LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
    WHERE sub.programme = 'programme'
    AND sub.submission_year = '2015/2016' //current year
    AND sub.state = '2' //state 2 is complete
    AND sub.period = '1'
    AND pm.kpi_id = kpi.id
) AS p1

但是其他时段需要根据两种提交类型的分数:

第 2 - 12 期

CASE 
WHEN

( 
      IFNULL((
              SELECT
              ROUND(AVG(sp.progress), 2)
              FROM `submissions` AS sub
              LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
              LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
              WHERE sub.period = '2'
              AND sub.submission_year = '2015/2016'
              AND sub.state = '2'
              AND sub.panel = 'panel'
              AND pm.kpi_id = kpi.id
             ), 0) + 
      IFNULL((
              SELECT
              ROUND(AVG(sp.progress), 2)
              FROM `submissions` AS sub
              LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
              LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
              WHERE sub.programme = 'programme'
              AND sub.submission_year = '2015/2016'
              AND sub.state = '2'
              AND sub.period = '2'
              AND pm.kpi_id = kpi.id
            ), 0)/2) / 100 * 0.3 < 0 THEN 

       1- ROUND(( 
               IFNULL((
                        SELECT
                        ROUND(AVG(sp.progress), 2)
                        FROM `submissions` AS sub
                        LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
                        LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
                        WHERE sub.period = '2'
                        AND sub.submission_year = '2015/2016'
                        AND sub.state = '2'
                        AND sub.panel = 'panel'
                        AND pm.kpi_id = kpi.id
                      ), 0) + 
                IFNULL((
                         SELECT
                         ROUND(AVG(sp.progress), 2)
                         FROM `submissions` AS sub
                         LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
                         LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
                         WHERE sub.programme = 'programme'
                         AND sub.submission_year = '2015/2016'
                         AND sub.state = '2'
                         AND sub.period = '2'
                         AND pm.kpi_id = kpi.id
                        ), 0)/2) / 100 * 0.3, 2)
        WHEN 

               ( 
                IFNULL((
                        SELECT
                        ROUND(AVG(sp.progress), 2)
                        FROM `submissions` AS sub
                        LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
                        LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
                        WHERE sub.period = '2'
                        AND sub.submission_year = '2015/2016'
                        AND sub.state = '2'
                        AND sub.panel = 'panel'
                        AND pm.kpi_id = kpi.id
                       ), 0) + 
                IFNULL((
                        SELECT
                        ROUND(AVG(sp.progress), 2)
                        FROM `submissions` AS sub
                        LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
                        LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
                        WHERE sub.programme = 'programme'
                        AND sub.submission_year = '2015/2016'
                        AND sub.state = '2'
                        AND sub.period = '2'
                        AND pm.kpi_id = kpi.id
                       ), 0)/2) / 100 * 0.3 > 0 THEN 

      1+ ROUND(( 
               IFNULL((
                       SELECT
                       ROUND(AVG(sp.progress), 2)
                       FROM `submissions` AS sub
                       LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
                       LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
                       WHERE sub.period = '2'
                       AND sub.submission_year = '2015/2016'
                       AND sub.state = '2'
                       AND sub.panel = 'panel'
                       AND pm.kpi_id = kpi.id
                     ), 0) + 
              IFNULL((
                      SELECT
                      ROUND(AVG(sp.progress), 2)
                      FROM `submissions` AS sub
                      LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
                      LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
                      WHERE sub.programme = 'programme'
                      AND sub.submission_year = '2015/2016'
                      AND sub.state = '2'
                      AND sub.period = '2'
                      AND pm.kpi_id = kpi.id
                     ), 0)/2) / 100 * 0.3, 2)
         END
END AS p2

目前在剩余时段内重复此操作。我得到的数据很好,但查询又大又丑。最重要的是,我希望在结果的末尾有一列,为所有 13 个时期的每个 KPI 创建一个平均分数。我知道我不能引用我通过它的别名创建的列,所以目前我必须再次重复子查询才能获得这个分数。

无论如何我可以使这个查询更有效率或循环通过周期 2 - 13?

完整的查询结果如下:

查询到第 2 期

        SELECT
        kra.id AS id,
        kra.kra_name AS name,
        kra.panel_weighting AS panel_weighting,
        kra.programme_weighting AS programme_weighting,
        kpi.id AS id,
        kpi.kpi_name AS name,
        kpi.panel_weighting AS panel_weighting,
        kpi.programme_weighting AS programme_weighting,
        (
            SELECT
            ROUND(AVG(sp.progress), 2)
            FROM `submissions` AS sub
            LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
            LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
            WHERE sub.programme = 'programme'
            AND sub.submission_year = '2015/2016'
            AND sub.state = '2'
            AND sub.period = '1'
            AND pm.kpi_id = kpi.id
    ) AS p1,
     CASE  
      WHEN (
                SELECT
                ROUND(AVG(sp.progress), 2)
                FROM `submissions` AS sub
                LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
                LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
                WHERE sub.period = '2'
                AND sub.submission_year = '2015/2016'
                AND sub.state = '2'
                AND sub.panel = 'panel'
                AND pm.kpi_id = kpi.id
      ) IS NULL 
      AND
      (
            SELECT
            ROUND(AVG(sp.progress), 2)
            FROM `submissions` AS sub
            LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
            LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
            WHERE sub.programme = 'programme'
            AND sub.submission_year = '2015/2016'
            AND sub.state = '2'
            AND sub.period = '2'
            AND pm.kpi_id = kpi.id
           ) IS NULL
           THEN NULL
     ELSE 

     CASE 
     WHEN

            ( 
             IFNULL((
                SELECT
                ROUND(AVG(sp.progress), 2)
                FROM `submissions` AS sub
                LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
                LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
                WHERE sub.period = '2'
                AND sub.submission_year = '2015/2016'
                AND sub.state = '2'
                AND sub.panel = 'panel'
                AND pm.kpi_id = kpi.id
         ), 0) + 
         IFNULL((
            SELECT
            ROUND(AVG(sp.progress), 2)
            FROM `submissions` AS sub
            LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
            LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
            WHERE sub.programme = 'programme'
            AND sub.submission_year = '2015/2016'
            AND sub.state = '2'
            AND sub.period = '2'
            AND pm.kpi_id = kpi.id
          ), 0)/2) / 100 * 0.3 < 0 THEN 

         1- ROUND(( 
         IFNULL((
            SELECT
            ROUND(AVG(sp.progress), 2)
            FROM `submissions` AS sub
            LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
            LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
            WHERE sub.period = '2'
            AND sub.submission_year = '2015/2016'
            AND sub.state = '2'
            AND sub.panel = 'panel'
            AND pm.kpi_id = kpi.id
         ), 0) + 
         IFNULL((
            SELECT
            ROUND(AVG(sp.progress), 2)
            FROM `submissions` AS sub
            LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
            LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
            WHERE sub.programme = 'programme'
            AND sub.submission_year = '2015/2016'
            AND sub.state = '2'
            AND sub.period = '2'
                AND pm.kpi_id = kpi.id
              ), 0)/2) / 100 * 0.3, 2)
     WHEN 

         ( 
             IFNULL((
                SELECT
                ROUND(AVG(sp.progress), 2)
                FROM `submissions` AS sub
                LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
                LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
                WHERE sub.period = '2'
                AND sub.submission_year = '2015/2016'
                AND sub.state = '2'
                AND sub.panel = 'panel'
                AND pm.kpi_id = kpi.id
         ), 0) + 
         IFNULL((
            SELECT
            ROUND(AVG(sp.progress), 2)
            FROM `submissions` AS sub
            LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
            LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
            WHERE sub.programme = 'programme'
            AND sub.submission_year = '2015/2016'
            AND sub.state = '2'
            AND sub.period = '2'
            AND pm.kpi_id = kpi.id
          ), 0)/2) / 100 * 0.3 > 0 THEN 

         1+ ROUND(( 
         IFNULL((
            SELECT
            ROUND(AVG(sp.progress), 2)
            FROM `submissions` AS sub
            LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
            LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
            WHERE sub.period = '2'
            AND sub.submission_year = '2015/2016'
            AND sub.state = '2'
            AND sub.panel = 'panel'
            AND pm.kpi_id = kpi.id
         ), 0) + 
         IFNULL((
            SELECT
            ROUND(AVG(sp.progress), 2)
            FROM `submissions` AS sub
            LEFT JOIN `sub_performancemeasures` sp ON sp.sub_id = sub.id
            LEFT JOIN `performance_measures` pm ON pm.id = sp.pm_id
            WHERE sub.programme = 'programme'
            AND sub.submission_year = '2015/2016'
            AND sub.state = '2'
            AND sub.period = '2'
                AND pm.kpi_id = kpi.id
              ), 0)/2) / 100 * 0.3, 2)
     END
     END AS p2

    FROM `kpis` kpi
    LEFT JOIN `key_reporting_areas` kra ON kra.id = kpi.kra_id
    GROUP BY kpi.id

【问题讨论】:

  • 如果您愿意,请考虑遵循以下简单的两步操作: 1. 如果您还没有这样做,请提供适当的 CREATE 和 INSERT 语句(和/或 sqlfiddle),以便我们可以更容易复制问题。 2. 如果您尚未这样做,请提供与步骤 1 中提供的信息相对应的所需结果集。
  • @Strawberry,请不要经常发布有关 DDL/Sample/SqlFiddle 的帖子。仅在过去的 10 天里,我就已经看到了十几次。如果您无法弄清楚或在精神上理解它,请不要。

标签: mysql


【解决方案1】:

由于您的所有底层子查询都基于与您的“pm.kpi_id”相关联的相应“kpi.id”,因此左连接现在已通过 WHERE 子句将所有内容拉入隐含的 INNER 连接。这是您的预期吗?

现在,如何简化它。我会将每个周期/状态的一个大查询放入一个“预查询”中,然后根据 ID 将它一次加入您的“kpi”表并提取任何最终值。这只是一个示例,但应该足以作为我所指内容的指南。

对于索引,我会首先确保您的“提交”表在 (submission_year, state, period, program) 上有一个索引以帮助优化。

SELECT
      kra.id AS id,
      kra.kra_name AS name,
      kra.panel_weighting AS panel_weighting,
      kra.programme_weighting AS programme_weighting,
      kpi.id AS id,
      kpi.kpi_name AS name,
      kpi.panel_weighting AS panel_weighting,
      kpi.programme_weighting AS programme_weighting,
      PreAgg.AvgProgrammePer1,
      PreAgg.AvgPanelPer1,
      PreAgg.AdjFactorPer1,
      CASE when PreAgg.AdjFactorPer1 < 0 then 1 - PreAgg.AdjFactorPer1
           when PreAgg.AdjFactorPer1 > 0 then 1 + PreAgg.AdjFactorPer1
           END as FinalAdjPer1,
      PreAgg.AvgProgrammePer2,
      PreAgg.AvgPanelPer2,
      PreAgg.AdjFactorPer2,
      CASE when PreAgg.AdjFactorPer2 < 0 then 1 - PreAgg.AdjFactorPer2
           when PreAgg.AdjFactorPer2 > 0 then 1 + PreAgg.AdjFactorPer2
           END as FinalAdjPer2,
      PreAgg.AvgProgrammePer3,
      PreAgg.AvgPanelPer3,
      PreAgg.AdjFactorPer3,
      CASE when PreAgg.AdjFactorPer3 < 0 then 1 - PreAgg.AdjFactorPer3
           when PreAgg.AdjFactorPer3 > 0 then 1 + PreAgg.AdjFactorPer3
           END as FinalAdjPer3
   from
      kpis kpi
         LEFT JOIN
         ( select
                 KPI_Period.kpi_id,
                 CASE when KPI_Period.Period = 1 
                      then AvgProgramme END as AvgProgrammePer1,
                 CASE when KPI_Period.Period = 1 
                      then AvgPanel END as AvgPanelPer1,
                 CASE when KPI_Period.Period = 1 
                      then (( coalesce( AvgProgramme, 0 )
                            + coalesce( AvgPanel, 0 )) / 2 ) / 100 * .3 as AdjFactorPer1,
                 CASE when KPI_Period.Period = 2 
                      then AvgProgramme END as AvgProgrammePer2,
                 CASE when KPI_Period.Period = 2 
                      then AvgPanel END as AvgPanelPer2,
                 CASE when KPI_Period.Period = 2 
                      then (( coalesce( AvgProgramme, 0 )
                            + coalesce( AvgPanel, 0 )) / 2 ) / 100 * .3 as AdjFactorPer2,
                 CASE when KPI_Period.Period = 3 
                      then AvgProgramme END as AvgProgrammePer3,
                 CASE when KPI_Period.Period = 3 
                      then AvgPanel END as AvgPanelPer3,
                 CASE when KPI_Period.Period = 3 
                      then (( coalesce( AvgProgramme, 0 )
                            + coalesce( AvgPanel, 0 )) / 2 ) / 100 * .3 as AdjFactorPer3
              from
                 ( SELECT
                         pm.kpi_id,
                         sub.period,
                         AVG( CASE when sub.programme = 'programme' then sp.progress end ) as AvgProgramme,
                         AVG( CASE when sub.programme = 'panel' then sp.progress end ) as AvgPanel,
                      FROM 
                         submissions AS sub
                            LEFT JOIN sub_performancemeasures sp 
                               ON sub.id = sp.sub_id
                               LEFT JOIN performance_measures pm 
                                  ON sp.pm_id = pm.id
                      WHERE 
                             sub.submission_year = '2015/2016'
                         AND sub.state = '2'
                         AND sub.programme IN ( 'programme', 'panel' ) 
                      GROUP BY
                         pm.kpi_id,
                         sub.period ) as KPI_Period
              group by
                 KPI_Period.kpi_id ) PreAgg

         ON kpi.id = PreAgg.kpi_id

现在,解释我在这里尝试的内容。根据 2015/2016 年、状态 = 2 和程序作为程序或面板,最里面的查询直接转到您的基础表以进行提交进度活动。我根据每个 PERIOD 对平均值进行分组。是的,您可以调整平均值的 ROUNDING,但我做了平均值。所以,在这个阶段结束时,我会有类似...

KPI_ID   Period   AvgProgramme   AvgPanel
1        1        prog1.blah      panel1.blah
1        2        prog2.blah      panel2.blah
1        3        prog3.blah      panel3.blah

随着一年中剩余时间的推移,第 4-12 期的记录也将增加(如果是 4 周期与每月期,则为 13 期)。这个结果就是“KPI_Period”别名子选择。

现在,这已针对所有可能的时期进行了简化,就像“年初至今”的可能性一样,我正在将每个时期的交叉表构建成列。现在,这部分将显着简化您的 IFNULL() 条件。 COALESCE() 将接受一个值,如果为 null,则使用第二个参数作为值来应用任何内容。所以,下一个级别为我们带来了

CASE when KPI_Period.Period = 1 
     then AvgProgramme END as AvgProgrammePer1,
CASE when KPI_Period.Period = 1 
     then AvgPanel END as AvgPanelPer1,
CASE when KPI_Period.Period = 1 
     then (( coalesce( AvgProgramme, 0 )
           + coalesce( AvgPanel, 0 )) / 2 ) / 100 * .3 as AdjFactorPer1,

注意这种情况/何时只关心第 1 期(或 2 或 3 期)。因此,对于 Period = 1,我将获得“prog1.blah”和“panel1.blah”的 AvgProgramme 值,这可能是空值,也可能是实际值。所以它们就是它们的本来面目,但它们的最终名称分别为“AvgProgrammePer1”和“AvgPanelPer1”。

现在,你的疯狂计算。我还通过应用 COALESCE 预先汇总了这个计算一次。

(   ( coalesce( AvgProgramme, 0 ) + coalesce( AvgPanel, 0 )) 
    / 2 ) / 100 * .3 as AdjFactorPer1,

因此,如果 AvgProgramme OR AvgPanel 为空值,则会将其转换为 0 以进行计算。所以你永远不会有除以 0 失败,但 0/2 为零,然后 0 / 100 = 0... * .3 = 0。所以,如果两个值都为空,你的最终调整因子为零,因此没有+/- 1 应用于最后一组。这适用于每个“周期”,给出的结果类似于...

(abbreviated columns and sample data)
KP  AvgProg1  AvgPan1  Adj1  AvgProg2  AvgPan2  Adj2  AvgProg3  AvgPan3  Adj3
1   prog1blah pan1blah avg1  prog2blah pan2blah avg2  prog3blah pan3blah avg3

所以现在,对于每个 KPI_ID,您有 1 行应用了所有程序、面板和调整计算因子,没有笛卡尔结果,因此不应发生重复会计。这是将加入您的 KPI 表的“PreAgg”(预聚合)最终结果。

现在,所有字段的最顶层。您可以(如我所采样的那样)抓取所有列,但这是调整因子的简化部分。由于它已经在 Pre-Agg 级别预先计算,我们现在可以应用因式分解

PreAgg.AvgProgrammePer1,
PreAgg.AvgPanelPer1,
PreAgg.AdjFactorPer1,
CASE when PreAgg.AdjFactorPer1 < 0 then 1 - PreAgg.AdjFactorPer1
     when PreAgg.AdjFactorPer1 > 0 then 1 + PreAgg.AdjFactorPer1
     END as FinalAdjPer1,

您始终可以删除最终结果中不需要的冗余列,但至少可以让它们在此过程中确认计算。而且这个查询适用于 3 个完整的周期......只需要额外的 case/when 块用于周期 4-12,不需要额外的子查询结构来敲你的头。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-18
    • 1970-01-01
    • 2011-08-05
    • 2012-02-21
    相关资源
    最近更新 更多