查询优化以产生计算结果

时间:2016-02-16 12:08:14

标签: sql query-optimization

Table Structure 在这篇文章中添加表格非常困难,至少我不知道。尝试使用HTML表格标签,但它们看起来不太好。因此将表结构发布为图像。

考虑到图像中看到的3个表,Projects,BC,Actual Spend,作为样本,我正在寻找一个最佳查询,该查询返回Reports作为结果。如您所见,BC有一些计算,实际支出

ActiveForm::begin([
  'enableClientScript' => false,
]);

如您所见,BC的SELECT,实际支出运行两次。我有几个其他的表,如BC,实际支出,产生一些计算。有没有办法优化这个。即使我把它们放在一个函数中,它也是一样的,函数需要不止一次调用。 有没有办法优化此查询。

粘贴下面的表格结构:

项目表:

SELECT ProjectId, Name, Budget
        , (SELECT b.[BC]  FROM [BC] b
                WHERE b.[BC] IN 
                    (SELECT SUM(mx.[BC]) FROM [BC] mx
                        WHERE ProjectId=p.ProjectId)) AS 'BC'
        , (SELECT sp.[ActualSpendAmount] FROM [ActualSpend] sp
                WHERE sp.[DateSpent] IN 
                    (SELECT MAX(as.[DateSpent]) FROM [ActualSpend] as
                        WHERE ProjectId=p.ProjectId)) AS 'Actual Spend'

        , t.[Budget] - ((SELECT b.[BC]  FROM [BC] b
                            WHERE b.[BC] IN 
                                (SELECT SUM(mx.[BC]) FROM [BC] mx
                                    WHERE ProjectId=p.ProjectId)) 
                        + 
                        (SELECT sp.[ActualSpendAmount] FROM [ActualSpend] sp
                            WHERE sp.[DateSpent] IN 
                                (SELECT MAX(as.[DateSpent]) FROM [ActualSpend] as
                                    WHERE ProjectId=p.ProjectId)))

    FROM Projects p;                                

BC表:实际支出表:

ProjectId   Name    Budget                      
1   DeadRock    500000                      
2   HardRock    300000  

报告:

ProjectId   BCId    BC  ApprovalDate        ProjectId   ActualSpendId   ActualSpendAmount   DateSpent
1   1   5000    2015/02/01      1   1   "       15000"  "   2015/03/01"
1   2   3000    2015/03/10      1   2   "       33000"  "   2015/05/12"
1   3   15000   2015/05/01      1   3   "       45000"  "   2015/06/03"
1   4   5000    2015/07/01      1   4   "       75000"  "   2015/07/11"
2   5   2000    2015/03/19      2   5   "       5000"   "   2015/04/20"
2   6   6000    2015/05/20      2   6   "       19000"  "   2015/05/29"
2   7   25000   2015/08/01      2   7   "       42000"  "   2015/06/23"
                    2   8   "       85000"  "   2015/07/15"

2 个答案:

答案 0 :(得分:0)

BC的相关子查询没有任何意义:

, (SELECT b.[BC]  FROM [BC] b
            WHERE b.[BC] IN 
                (SELECT SUM(mx.[BC]) FROM [BC] mx
                    WHERE ProjectId=p.ProjectId)) AS 'BC'

如果我们专注于projectID 1,你可以在这里获得数据:

ProjectId   BCId    BC      ApprovalDate  
--------------------------------------------
1           1       5000    2015/02/01     
1           2       3000    2015/03/10      
1           3       15000   2015/05/01     
1           4       5000    2015/07/01     

因此这部分查询:

                (SELECT SUM(mx.[BC]) FROM [BC] mx
                    WHERE ProjectId=p.ProjectId)

将返回28,000。那么你的本质是:

, (SELECT b.[BC]  FROM [BC] b
            WHERE b.[BC] IN (28000)) AS 'BC'

除非在BC中具有该特定金额的任何项目中的1个且仅有1个记录,否则将返回null。如果存在多个错误,则会出现错误,因为子查询中返回了多个记录。

我怀疑,根据报告数据,您只需要总和,因此您只需使用:

SELECT  p.ProjectId, 
        p.Name, 
        p.Budget
        bc.BC
FROM    Projects p
        LEFT JOIN
        (   SELECT  bc.ProjectID, SUM(bc.BC) AS bc
            FROM    BC
            GROUP BY bc.ProjectID
        ) AS bc
            ON bc.ProjectID = P.ProjectID;

获取每个项目的BC的总和。

我在此假设您使用的是基于对象名称的方括号和前一个问题中的语法的SQL Server。在这种情况下,我会使用OUTER APPLY来获取最新的实际支出行,并给出最终查询:

SELECT  p.ProjectId, 
        p.Name, 
        p.Budget
        bc.BC,
        sp.ActualSpendAmount AS [Actual Spend],
        p.Budget - bc.BC + sp.ActualSpendAmount AS ETC
FROM    Projects p
        LEFT JOIN
        (   SELECT  bc.ProjectID, SUM(bc.BC) AS bc
            FROM    BC
            GROUP BY bc.ProjectID
        ) AS bc
            ON bc.ProjectID = P.ProjectID
        OUTER APPLY
        (   SELECT  TOP 1 sp.ActualSpendAmount
            FROM    [ActualSpend] AS sp
            WHERE   sp.ProjectID = p.ProjectID
            ORDER BY sp.DateSpent DESC
        ) AS sp;

答案 1 :(得分:0)

根据您的预期结果,您的查询过于复杂(并且不会在没有错误的情况下运行)。 假设您的DBMS支持Windowed Aggregates:

SELECT p.ProjectId, p.NAME, p.Budget,
   BC.BC,
   act.ActualSpendAmount,
   p.Budget - (BC.BC + act.ActualSpendAmount)
FROM Projects AS p
LEFT JOIN
 ( -- sum of BC per project
   SELECT ProjectId, SUM(BC) AS BC
   FROM BC
   GROUP BY ProjectId
) AS BC
ON ProjectId=bc.ProjectId
JOIN 
 ( -- latest amount per project
   SELECT ProjectId, ActualSpendAmount,
      ROW_NUMBER() 
      OVER (PARTITION BY ProjectId
            ORDER BY DateSpent DESC) AS rn
   FROM ActualSpend
 ) AS Act
ON Act.ProjectId=p.ProjectId
AND Act.rn = 1