SQL重写以优化

SQL重写以优化,sql,sql-server,optimization,Sql,Sql Server,Optimization,我正在尝试优化或更改SQL,以使用内部连接,而不是独立调用 数据库:一张发票可以有多条付款记录和订单产品记录 原件: SELECT InvoiceNum, (SELECT SUM(Orders.Cost) FROM Orders WHERE Orders.Invoice = InvoiceNum and Orders.Returned <> 1 GROUP BY Orders.Invoice) as vat_only, (SELECT SUM(Orde

我正在尝试优化或更改SQL,以使用内部连接,而不是独立调用

数据库:一张发票可以有多条付款记录和订单产品记录

原件:

SELECT 
    InvoiceNum, 
    (SELECT SUM(Orders.Cost) FROM Orders WHERE Orders.Invoice = InvoiceNum  and Orders.Returned <> 1 GROUP BY Orders.Invoice) as vat_only, 
    (SELECT SUM(Orders.Vat) FROM Orders WHERE Orders.Invoice = InvoiceNum and Orders.Returned <> 1 GROUP BY Orders.Invoice) as sales_prevat, 
    (SELECT SUM(pay.Amount) FROM Payments as pay WHERE Invoices.InvoiceNum = pay.InvoiceNum ) as income
FROM 
    Invoices 
WHERE 
    InvoiceYear = currentyear
我相信我们可以通过另一种方式将表分组并连接在一起。当我尝试下面的SQL语句时,我没有得到相同数量的记录…我在考虑连接的类型或连接的位置!!但是在屏幕上看了3个小时后仍然无法让它工作

到目前为止我

SELECT 
    Invoices.InvoiceNum, 
    Sum(Orders.Cost) AS SumOfCost, 
    Sum(Orders.VAT) AS SumOfVAT, 
    SUM(distinct Payments.Amount) as money
FROM 
    Invoices
LEFT JOIN 
    Orders ON Orders.Invoice = Invoices.InvoiceNum 
LEFT JOIN 
    Payments ON Invoices.InvoiceNum = Payments.InvoiceNum
WHERE 
    Invoices.InvoiceYear = 11 
    AND Orders.Returned <> 1
GROUP BY 
    Invoices.InvoiceNum
很抱歉英文不好,我不确定要搜索什么才能找到是否已经在这里得到了回答:D


提前感谢所有帮助

假设每个发票的付款和订单都可以包含多个记录,您需要在子查询中进行聚合,以避免交叉连接:

    select
          PreQuery.InvoiceNum,
          PreQuery.VAT_Only,
          PreQuery.Sales_Prevat,
          SUM( Pay.Amount ) as Income
       from
          ( select
                  I.InvoiceNum,
                  SUM( O.Cost ) as VAT_Only,
                  SUM( O.Vat ) as sales_prevat
               from 
                  Invoice I
                     Join Orders O
                        on I.InvoiceNum = O.Invoice
                       AND O.Returned <> 1
               where
                  I.InvoiceYear = currentYear 
               group by
                  I.InvoiceNum ) PreQuery
          JOIN Payments Pay
             on PreQuery.InvoiceNum = Pay.InvoiceNum
   group by
      PreQuery.InvoiceNum,
      PreQuery.VAT_Only,
      PreQuery.Sales_Prevat
SELECT  Invoices.InvoiceNum, o.Cost, o.VAT, p.Amount
FROM    Invoices
        LEFT JOIN
        (   SELECT  Invoice, Cost = SUM(Cost), VAT = SUM(VAT)
            FROM    Orders
            WHERE   Orders.Returned <> 1
            GROUP BY Invoice
        ) o
            ON o.Invoice = Invoices.InvoiceNum
        LEFT JOIN
        (   SELECT  InvoiceNum, Amount = SUM(Amount)
            FROM    Payments
            GROUP BY InvoiceNum
        ) P
            ON P.InvoiceNum = Invoices.InvoiceNum
WHERE   Invoices.InvoiceYear = 11;
付款

当您像这样加入这些表时:

SELECT  Orders.*, Payments.Amount
FROM    Invoices
        LEFT JOIN Orders 
            ON Orders.Invoice = Invoices.InvoiceNum 
        LEFT JOIN Payments 
            ON Invoices.InvoiceNum = Payments.InvoiceNum;
你最终会得到:

Orders.Invoice  Orders.Cost Orders.Vat  Payments.Amount
1               15.00       3.00        15.00                       
1               10.00       2.00        15.00
1               15.00       3.00        10.00
1               10.00       2.00        10.00
i、 e.每种付款/订单组合,因此对于每一张发票,您将得到比要求多得多的行,这会扭曲您的总数。因此,尽管原始数据中有25英镑的付款,但由于订单表中有两条记录,这一数字翻了一番,达到50英镑。这就是为什么每个表需要单独聚合,如果一张发票上有多个相同金额的付款/订单,则使用DISTINCT将不起作用

关于优化的最后一点,如果您运行查询并显示实际执行计划,您可能应该为表编制索引,SSMS将为您建议索引,但猜测一下,以下内容应该会提高性能:

CREATE NONCLUSTERED INDEX IX_Orders_InvoiceNum ON Orders (Invoice) INCLUDE(Cost, VAT, Returned);
CREATE NONCLUSTERED INDEX IX_Payments_InvoiceNum ON Payments (InvoiceNum) INCLUDE(Amount);

这应该允许两个子查询仅使用每个表上的索引,而不需要书签循环/聚集索引扫描。

假设每个发票的付款和订单都可以包含多个记录,则需要在子查询中进行聚合,以避免交叉连接:

SELECT  Invoices.InvoiceNum, o.Cost, o.VAT, p.Amount
FROM    Invoices
        LEFT JOIN
        (   SELECT  Invoice, Cost = SUM(Cost), VAT = SUM(VAT)
            FROM    Orders
            WHERE   Orders.Returned <> 1
            GROUP BY Invoice
        ) o
            ON o.Invoice = Invoices.InvoiceNum
        LEFT JOIN
        (   SELECT  InvoiceNum, Amount = SUM(Amount)
            FROM    Payments
            GROUP BY InvoiceNum
        ) P
            ON P.InvoiceNum = Invoices.InvoiceNum
WHERE   Invoices.InvoiceYear = 11;
付款

当您像这样加入这些表时:

SELECT  Orders.*, Payments.Amount
FROM    Invoices
        LEFT JOIN Orders 
            ON Orders.Invoice = Invoices.InvoiceNum 
        LEFT JOIN Payments 
            ON Invoices.InvoiceNum = Payments.InvoiceNum;
你最终会得到:

Orders.Invoice  Orders.Cost Orders.Vat  Payments.Amount
1               15.00       3.00        15.00                       
1               10.00       2.00        15.00
1               15.00       3.00        10.00
1               10.00       2.00        10.00
i、 e.每种付款/订单组合,因此对于每一张发票,您将得到比要求多得多的行,这会扭曲您的总数。因此,尽管原始数据中有25英镑的付款,但由于订单表中有两条记录,这一数字翻了一番,达到50英镑。这就是为什么每个表需要单独聚合,如果一张发票上有多个相同金额的付款/订单,则使用DISTINCT将不起作用

关于优化的最后一点,如果您运行查询并显示实际执行计划,您可能应该为表编制索引,SSMS将为您建议索引,但猜测一下,以下内容应该会提高性能:

CREATE NONCLUSTERED INDEX IX_Orders_InvoiceNum ON Orders (Invoice) INCLUDE(Cost, VAT, Returned);
CREATE NONCLUSTERED INDEX IX_Payments_InvoiceNum ON Payments (InvoiceNum) INCLUDE(Amount);

这应该允许两个子查询仅使用每个表上的索引,而不需要书签循环/聚集索引扫描。

您的问题是订单有多行发票,有时发票上有多笔付款。这会对给定的订单产生叉积效应。您可以通过预先汇总表来解决此问题

一个相关的问题是,如果没有付款,连接将失败,因此您需要左外连接


两条注释:订单的键字段是真正的发票还是同时也是发票编号?此外,您是否有现场发票。发票年份?或者您想在where子句中使用yeari.InvoiceDate吗?

您的问题是,订单有多行发票,有时发票上有多笔付款。这会对给定的订单产生叉积效应。您可以通过预先汇总表来解决此问题

一个相关的问题是,如果没有付款,连接将失败,因此您需要左外连接


两条注释:订单的键字段是真正的发票还是同时也是发票编号?此外,您是否有现场发票。发票年份?或者您想在where子句中使用yeari.InvoiceDate吗?

试试这个,注意我没有测试它,只是在记事本上把它擦掉了。如果您的任何发票可能不存在于任何子表中,则使用LEFT JOIN


试试这个,注意我没有测试它,只是在记事本上擦了擦。如果您的任何发票可能不存在于任何子表中,则使用LEFT JOIN


你的where从句在语态和顺序上明显不同,在n-n中;n-m;m-n,还是m-m关系?@mee什么是n-n?你是说1-1吗?@mee发票和订单是n-m 1对多,发票和付款也是n-m 1对多。你的where子句在语音和订单中明显不同,是n-n;n-m;m-n,还是m-m关系?@mee什么是n-n?你的意思是1-1吗?@mee发票和订单是n-m 1对多,发票和付款也是n-m 1对多。你在外部查询中缺少GROUP BY子句,无法工作。为了解决这个问题,Payments表应该在它自己的派生表中,就像GarethD和Gordon Linoff的解决方案一样。不幸的是,与MySQL不同,MySQL是您的主要数据库平台,在SQL Server中,您不能仅按一列进行分组,而仍然进行选择*

其中包括其他非分组和非聚合列。要正确解决此问题,付款表应位于其自己的派生表中,就像GarethD和Gordon Linoff的解决方案一样。@user2486512,修改为修复组,因为这是SQL Server。。。其中一个引擎需要在非group by列上使用聚合/min/max等。只有一个子查询肯定会进行测试…感谢您刚刚测试过…这也没有按照原始查询的预期返回整个数据集。我想我们必须使用上述两个查询。您在外部查询中缺少group by子句将无法工作。为了解决这个问题,Payments表应该在它自己的派生表中,就像GarethD和Gordon Linoff的解决方案一样。不幸的是,与我看到的MySQL不同,MySQL是您的主要数据库平台,在SQL Server中,您不能仅按一列进行分组,而仍然选择*,其中包括其他未分组和未聚合的列。要正确解决此问题,付款表应位于其自己的派生表中,就像GarethD和Gordon Linoff的解决方案一样。@user2486512,修改为修复组,因为这是SQL Server。。。其中一个引擎需要在非group by列上使用聚合/min/max等。只有一个子查询肯定会对其进行测试…感谢您刚刚进行了测试…这也没有按照原始查询的预期返回整个数据集我猜我们必须使用上述两个查询,因为您首先得到了正确答案,多一点解释可能会让你更快地获得选票。比较Gordon Linoff的答案。@ErikE Fair point,我已经更新了答案,详细说明了交叉连接的问题,以及为什么每个表都需要单独聚合。好东西,我会给你另一个+1,但已经提前编辑了。thanx为了解释,我可以问一下,它是否能够将此查询优化到某一点!!我相信使用两个子查询会让它慢一点……或者这是实现这一点的最佳方式这是最好的方式,我看不到另一种获取正确数据并删除子查询的方式。正如我在最新编辑中所建议的,如果这仍然是一个问题,索引应该有助于提高性能。因为你是第一个得到正确答案的人,多做一点解释可能会让你更快地获得选票。比较Gordon Linoff的答案。@ErikE Fair point,我已经更新了答案,详细说明了交叉连接的问题,以及为什么每个表都需要单独聚合。好东西,我会给你另一个+1,但已经提前编辑了。thanx为了解释,我可以问一下,它是否能够将此查询优化到某一点!!我相信使用两个子查询会让它慢一点……或者这是实现这一点的最佳方式这是最好的方式,我看不到另一种获取正确数据并删除子查询的方式。索引,正如我在最新编辑中所建议的,如果这仍然是一个问题,则应该有助于提高性能。感谢回复,在订单中,其发票是关键字段我不是开发数据库的人,抱歉,是的,它有明确的年份字段来确定产品发布年份…感谢回复,在订单中,发票是关键字段我不是开发数据库的人,抱歉,是的,它有明确的年份字段来确定产品发布年份。。。
SELECT InvoiceNum, vat_only, sales_prevat, income
FROM Invoices i
INNER JOIN (SELECT Invoice, SUM(Cost) [vat_only], SUM(Vat) [sales_prevat]
            FROM Orders
            WHERE Returned <> 1
            GROUP BY Invoice) o
    ON i.InvoiceNum = o.Invoice
INNER JOIN (SELECT SUM(Amount) [income]
            FROM Payments) p
    ON i.InvoiceNum = p.InvoiceNum 
WHERE i.InvoiceYear = currentyear