MySQL重构冗长查询

时间:2014-03-07 23:43:06

标签: mysql union

好吧,我完全讨厌自己这个问题;但我正在寻找另一种方法来做这个查询,这可能更快,更优雅(这看起来像垃圾)。告诉我你的想法:

  SELECT TRUNCATE(SUM(sub.Total),3) AS GrpTotal, sub.ActualDate, 
         TRUNCATE(SUM(sub.BonusAmt),3) AS GrpBonusAmt, sub.UID, 
         sub.CUSTID, YEAR(MIN(sub.ActualDate)) AS Year, pusers.username
  FROM ( SELECT a.UID, a.ActualDate, 'Global Report' AS Report, 
                SUM(a.totalpayment) AS Total, a.CUSTID,
               ((SUM(a.totalpayment)*IFNULL((u.retention_percent/100),1))+IFNULL(u.bonus_amount,0)) AS BonusAmt
         FROM `globalreport` a
              LEFT JOIN `users` u ON u.uid = a.UID
         WHERE true AND a.CUSTID = 1020
         GROUP BY a.ActualDate, a.UID
         UNION ALL
         SELECT a.UID, a.ActualDate, 'Amex Residuals' AS Report, 
                SUM(a.payment) AS Total, a.CUSTID,
                ((SUM(a.payment)*IFNULL((u.retention_percent/100),1))+IFNULL(u.bonus_amount,0)) AS BonusAmnt
         FROM `amexresiduals` a
              LEFT JOIN `users` u ON u.uid = a.UID
         WHERE true AND a.CUSTID = 1020
         GROUP BY a.ActualDate, a.UID
         UNION ALL 
         SELECT a.UID, a.ActualDate, 'Compliance Fee' AS Report, 
                SUM(a.profit) AS Total, a.CUSTID, 
                ((SUM(a.profit)*IFNULL((u.retention_percent/100),1))+IFNULL(u.bonus_amount,0)) AS BonusAmnt
         FROM `compliancefee` a
              LEFT JOIN `users` u ON u.uid = a.UID
         WHERE true AND a.CUSTID = 1020
         GROUP BY a.ActualDate, a.UID
         UNION ALL
         SELECT a.UID, a.ActualDate, 'Checks On Demand' AS Report, 
                SUM(a.myprofit) AS Total, a.CUSTID, 
                ((SUM(a.myprofit)*IFNULL((u.retention_percent/100),1))+IFNULL(u.bonus_amount,0)) AS BonusAmnt
         FROM `geticheck` a
              LEFT JOIN `users` u ON u.uid = a.UID
         WHERE true AND a.CUSTID = 1020
         GROUP BY a.ActualDate, a.UID
         UNION ALL
         SELECT a.UID, a.ActualDate, 'Gift Cards on Demand' AS Report, 
                SUM(a.payment) AS Total, a.CUSTID,
                ((SUM(a.payment)*IFNULL((u.retention_percent/100),1))+IFNULL(u.bonus_amount,0)) AS BonusAmnt
         FROM `gcod` a
              LEFT JOIN `users` u ON u.uid = a.UID
         WHERE true AND a.CUSTID = 1020
         GROUP BY a.ActualDate, a.UID
         UNION ALL
         SELECT a.UID, a.ActualDate, 'Global Check' AS Report, 
                SUM(a.myprofit) AS Total, a.CUSTID,
                ((SUM(a.myprofit)*IFNULL((u.retention_percent/100),1))+IFNULL(u.bonus_amount,0)) AS BonusAmnt
         FROM `globalcheck` a
              LEFT JOIN `users` u ON u.uid = a.UID
         WHERE true AND a.CUSTID = 1020
         GROUP BY a.ActualDate, a.UID
         UNION ALL
         SELECT a.UID, a.ActualDate, 'Bonus True Up' AS Report, 
                SUM(a.finalpayment) AS Total, a.CUSTID,
                ((SUM(a.finalpayment)*IFNULL((u.retention_percent/100),1))+IFNULL(u.bonus_amount,0)) AS BonusAmnt
         FROM `bonustrueup` a
              LEFT JOIN `users` u ON u.uid = a.UID
         WHERE true AND a.CUSTID = 1020
         GROUP BY a.ActualDate, a.UID
         UNION ALL
         SELECT a.UID, a.ActualDate, 'Bonus Take Back - Did Not Activate' AS Report, 
                SUM(a.amount) AS Total, a.CUSTID,
                ((SUM(a.amount)*IFNULL((u.retention_percent/100),1))+IFNULL(u.bonus_amount,0)) AS BonusAmnt 
         FROM `bonusadjnosetup` a
              LEFT JOIN `users` u ON u.uid = a.UID
         WHERE true AND a.CUSTID = 1020
         GROUP BY a.ActualDate, a.UID
         UNION ALL
         SELECT a.UID, a.ActualDate, 'Bonus Take Back - Closed Less Than 6 Months' AS Report, 
                SUM(a.amount) AS Total, a.CUSTID,
                ((SUM(a.amount)*IFNULL((u.retention_percent/100),1))+IFNULL(u.bonus_amount,0)) AS BonusAmnt
         FROM `bonusadjclosed6mo` a
             LEFT JOIN `users` u ON u.uid = a.UID
         WHERE true AND a.CUSTID = 1020
         GROUP BY a.ActualDate, a.UID
         UNION ALL
         SELECT a.UID, a.ActualDate, 'Month End Fee Rejects' AS Report, 
                SUM(a.amount) AS Total, a.CUSTID, SUM(a.amount) AS BonusAmnt        
         FROM `merchantloss` a
             LEFT JOIN `users` u ON u.uid = a.UID
         WHERE true AND a.CUSTID = 1020
         GROUP BY a.ActualDate, a.UID
         UNION ALL
         SELECT a.UID, a.ActualDate, 'Direct ACH Debits and Credits' AS Report, 
                SUM(a.amount*-1) AS Total, a.CUSTID, SUM(a.amount*-1) AS BonusAmnt
         FROM `dirachdebcred` a
             LEFT JOIN `users` u ON u.uid = a.UID
         WHERE true AND a.CUSTID = 1020
         GROUP BY a.ActualDate, a.UID
         UNION ALL
         SELECT a.UID, a.ActualDate, 'Merchant Adjustments' AS Report, 
                SUM(a.amount) AS Total, a.CUSTID,
                ((SUM(a.amount)*IFNULL((u.retention_percent/100),1))+IFNULL(u.bonus_amount,0)) AS BonusAmnt
         FROM `merchantadj` a
             LEFT JOIN `users` u ON u.uid = a.UID
         WHERE true AND a.CUSTID = 1020
         GROUP BY a.ActualDate, a.UID
  ) sub
      LEFT JOIN `pending_users` pusers ON pusers.UID = sub.UID
  WHERE sub.CUSTID = 1020
       AND sub.`UID` NOT IN 
                ( SELECT `UID` 
                  FROM `users` 
                  WHERE `is_admin` AND `company_id` = sub.`CUSTID`)
  GROUP BY sub.ActualDate, sub.UID, sub.Report
  ORDER BY sub.ActualDate ASC

显然,这是一个冗长的查询。我只是不确定它是否必须如此。基本上,我正在收集并汇总每个联合表中的不同列,并在最后对该数量进行分组,这样我就可以从所有表中获得总和。

2 个答案:

答案 0 :(得分:1)

由于您从多个不同的表中获得结果,因此您的查询实际上看起来非常好。我同意SQL文本的长度有点令人生畏。

整体方法看起来似乎是获得指定结果的最有效方法。我假设针对各个表的内部查询正在折叠很多行。

就性能而言,我唯一要改变的是NOT IN谓词,因为这将导致每行执行子查询。

您可以使用反连接模式获得等效结果,假设UIDusers表中的主键,或者它至少保证为NOT NULL。 (我假设子查询不返回NULL;如果是,则NOT IN谓词不会返回TRUE,并且根本不会返回任何行。)

所以这个:

  WHERE sub.CUSTID = 1020
       AND sub.`UID` NOT IN 
                ( SELECT `UID` 
                  FROM `users` 
                  WHERE `is_admin` AND `company_id` = sub.`CUSTID`)

可以替换为等效,但(通常)更有效:

   LEFT
   JOIN `users` n
     ON n.is_admin
    AND n.company_id = sub.`CUSTID`
    AND n.`UID` = sub.`UID`
  WHERE n.`UID` IS NULL
    AND sub.CUSTID = 1020

反连接模式正在寻找匹配的行,然后排除任何匹配的行,所以我们剩下的就是那些不匹配的行。

实际上,不需要检查sub.CUSTID = 1020,可以省略。我们已经确保sub内联视图中的每个查询都是1020。


在外部查询的SELECT列表中,我认为这个MIN聚合会增加一些混淆:

YEAR(MIN(sub.ActualDate))

它看起来没必要,因为查询正在执行GROUP BY sub.ActualDate,并且因为YEAR函数是确定性的(即YEAR(foo)返回的值对于相等的值将是相等的foo。我只用以下代替:

YEAR(sub.ActualDate)

实际上,我认为在外部查询中根本不需要执行GROUP BY操作。

Report sub列的返回值与每个UNION ALL查询不同。并且每个查询都已执行GROUP BY UID, ActualDate。因此,来自(UID,ActualDate,Report)的{​​{1}}元组已经保证是唯一的。

唯一的问题是sub表中UID是否唯一。如果不是,那么查询返回的总数将增加一倍,三倍等,并且只从一个匹配的行中选择pending_users值。但我不认为查询是这样做的,我强烈怀疑usernameUID中是唯一的。 (最外面的GROUP BY中没有包含pending_users的任何列。)除了匹配pending_users中的多个行...

该查询已经保证最外层查询上的GROUP BY没有要折叠的行。它看起来就像GROUP BY所带来的那样是一个排序操作。这可以用pending_users替换。

这也意味着ORDER BY聚合(在最外层查询的SELECT列表中)不是必需的。例如,这个表达式:

SUM

可以替换为:

TRUNCATE(SUM(sub.Total),3) 

并返回等效结果。


在性能方面......拥有合适的索引非常重要。在EXPLAIN中,我们宁愿看不到TRUNCATE(sub.Total,3) 这些内部查询的GROUP BY操作。我们宁愿看Using filesort

对于那些内部查询,理想情况下我们会看到这样的索引:

Using index

CUSTID上有一个等式谓词,并且在ActualDate,UID上有一个GROUP BY操作。按此顺序(几乎)保证MySQL将使用索引来执行GROUP BY并避免... ON merchantloss (CUSTID, ActualDate, UID, amount) 操作。包括查询中引用的其他列使其成为覆盖索引,这意味着可以完全从索引中满足查询,而无需访问基础表中的页面。


最后一点注意......如果限制行数,那么在每个单独的内部查询上执行Using filesort排除(使用反连接模式)可能只是效率更高一点。从is_admin返回。但我怀疑它不会产生太大的影响,甚至可能会更慢......我会把它留在最外面的查询上,如果只是为了避免多次重复。

答案 1 :(得分:1)

<强>更新

好像我以前的答案不够长......

我还将JOIN从内部查询移动到用户表到外部查询,看起来它在每种情况下都是相同的。

但是,我们需要计算BonusAmnt(它似乎总是基于Total;除了两个之外,它们的计算是相同的。所以,我也移动了BonusAmnt计算也可以使用CASE表达式添加对要执行的计算的检查。

我可能错过了一些东西。

但这是我写这个查询的方式。

我会将它显示为两部分,sub内联视图的查询与外部查询分开。

  SELECT TRUNCATE(sub.Total,3) AS GrpTotal
       , sub.ActualDate
       , CASE
           WHEN sub.Report IN ('Month End Fee Rejects','Direct ACH Debits and Credits') THEN
             TRUNCATE(sub.Total,3)
           ELSE
             TRUNCATE(sub.Total*IFNULL((u.retention_percent/100),1)+IFNULL(u.bonus_amount,0)),3)
         END AS GrpBonusAmt
       , sub.UID
       , sub.CUSTID
       , YEAR(sub.ActualDate) AS Year
       , pusers.username
    FROM (
           -- query to produce sub goes here
         ) sub
    LEFT
    JOIN `users` u
      ON u.uid = sub.UID
    LEFT
    JOIN `pending_users` pusers
      ON pusers.UID = sub.UID
    LEFT
    JOIN `users` n
      ON n.UID = sub.CUSTID
     AND n.is_admin
   WHERE n.UID IS NULL
   ORDER BY sub.ActualDate, sub.UID, sub.Report

这是第二部分。这是一个插入到上面部分中间的查询,作为内联视图。这是为外部查询生成sub行源的查询:

           SELECT 'Global Report' AS Report
                , a.UID, a.ActualDate, a.CUSTID
                , SUM(a.totalpayment) AS Total
             FROM `globalreport` a
            WHERE a.CUSTID = 1020
            GROUP BY a.ActualDate, a.UID
            UNION ALL
           SELECT 'Amex Residuals'
                , a.UID, a.ActualDate, a.CUSTID
                , SUM(a.payment) AS Total
             FROM `amexresiduals` a
            WHERE a.CUSTID = 1020
            GROUP BY a.ActualDate, a.UID
            UNION ALL
           SELECT 'Compliance Fee'
                , a.UID, a.ActualDate, a.CUSTID
                , SUM(a.profit) AS Total
             FROM `compliancefee` a
            WHERE a.CUSTID = 1020
            GROUP BY a.ActualDate, a.UID
            UNION ALL
           SELECT 'Checks On Demand'
                , a.UID, a.ActualDate, a.CUSTID
                , SUM(a.myprofit) AS Total
             FROM `geticheck` a
            WHERE a.CUSTID = 1020
            GROUP BY a.ActualDate, a.UID
            UNION ALL
           SELECT 'Gift Cards on Demand'
                , a.UID, a.ActualDate, a.CUSTID
                , SUM(a.payment) AS Total
             FROM `gcod` a
            WHERE a.CUSTID = 1020
            GROUP BY a.ActualDate, a.UID
            UNION ALL
           SELECT 'Global Check' AS Report
                , a.UID, a.ActualDate, a.CUSTID
                , SUM(a.myprofit) AS Total
             FROM `globalcheck` a
            WHERE a.CUSTID = 1020
            GROUP BY a.ActualDate, a.UID
            UNION ALL
           SELECT 'Bonus True Up'
                , a.UID, a.ActualDate, a.CUSTID
                , SUM(a.finalpayment) AS Total
             FROM `bonustrueup` a
            WHERE a.CUSTID = 1020
            GROUP BY a.ActualDate, a.UID
            UNION ALL
           SELECT 'Bonus Take Back - Did Not Activate'
                , a.UID, a.ActualDate, a.CUSTID
                , SUM(a.amount) AS Total
             FROM `bonusadjnosetup` a
            WHERE a.CUSTID = 1020
            GROUP BY a.ActualDate, a.UID
            UNION ALL
           SELECT 'Bonus Take Back - Closed Less Than 6 Months'
                , a.UID, a.ActualDate, a.CUSTID
                , SUM(a.amount) AS Total
             FROM `bonusadjclosed6mo` a
            WHERE a.CUSTID = 1020
            GROUP BY a.ActualDate, a.UID
            UNION ALL
           SELECT 'Month End Fee Rejects'
                , a.UID, a.ActualDate, a.CUSTID
                , SUM(a.amount) AS Total
             FROM `merchantloss` a
            WHERE a.CUSTID = 1020
            GROUP BY a.ActualDate, a.UID
            UNION ALL
           SELECT 'Direct ACH Debits and Credits'
                , a.UID, a.ActualDate, a.CUSTID
                , SUM(a.amount*-1) AS Total
             FROM `dirachdebcred` a
            WHERE a.CUSTID = 1020
            GROUP BY a.ActualDate, a.UID
            UNION ALL
           SELECT 'Merchant Adjustments'
                , a.UID, a.ActualDate, a.CUSTID
                , SUM(a.amount) AS Total
             FROM `merchantadj` a
            WHERE a.CUSTID = 1020
            GROUP BY a.ActualDate, a.UID