2012-10-02 59 views
0

我有以下DB结构(简体):如何提高SQL查询性能

Payments 
---------------------- 
Id  | int 
InvoiceId | int 
Active | bit 
Processed | bit 


Invoices 
---------------------- 
Id    | int 
CustomerOrderId | int 


CustomerOrders 
------------------------------------ 
Id      | int 
ApprovalDate    | DateTime 
ExternalStoreOrderNumber | nvarchar 

每个客户订单都有发票,每张发票可以有多个付款。 ExternalStoreOrderNumber是来自我们从外部合作伙伴商店导入订单的订单的参考,以及导入发生时的时间戳ApprovalDate

现在我们根据以下逻辑有,我们有一个错误的进口的需要更改为其他发票的某些款项(几个hundert,这样太马赫做手工)问题:
搜索订单的发票它具有与当前数字相同的外部数字,但以0开头而不是当前数字。

为了做到这一点,我创建了以下查询:

UPDATE DB.dbo.Payments 
    SET InvoiceId= 
     (SELECT TOP 1 I.Id FROM DB.dbo.Invoices AS I 
      WHERE I.CustomerOrderId= 
       (SELECT TOP 1 O.Id FROM DB.dbo.CustomerOrders AS O 
        WHERE O.ExternalOrderNumber='0'+SUBSTRING(
         (SELECT TOP 1 OO.ExternalOrderNumber FROM DB.dbo.CustomerOrders AS OO 
         WHERE OO.Id=I.CustomerOrderId), 1, 10000))) 
    WHERE Id IN (
     SELECT P.Id 
      FROM DB.dbo.Payments AS P 
      JOIN DB.dbo.Invoices AS I ON I.Id=P.InvoiceId 
      JOIN DB.dbo.CustomerOrders AS O ON O.Id=I.CustomerOrderId 
     WHERE P.Active=0 AND P.Processed=0 AND O.ApprovalDate='2012-07-19 00:00:00' 

现在我使用实时数据开始测试系统上查询(〜每个表250.000行),因为16小时,它现在正在运行 - 我在查询中做了一些完全错误的事情,还是有办法加快它的速度?
由于这是一次性任务,因此不需要很快,但几个小时对我来说似乎很长,并且我希望下一次我希望学习(希望不会发生),我希望有一些反馈如何改进...

回答

3

你可能会杀死查询。您的更新子查询与正在更新的表完全不相关。从它的外观来看,当它完成时,每一个dbo.payments记录都会具有相同的值。

要打破您的查询,您可能会发现子查询自行运行良好。

SELECT TOP 1 I.Id FROM DB.dbo.Invoices AS I 
      WHERE I.CustomerOrderId= 
       (SELECT TOP 1 O.Id FROM DB.dbo.CustomerOrders AS O 
        WHERE O.ExternalOrderNumber='0'+SUBSTRING(
         (SELECT TOP 1 OO.ExternalOrderNumber FROM DB.dbo.CustomerOrders AS OO 
         WHERE OO.Id=I.CustomerOrderId), 1, 10000)) 

这总是一个很大的担心。

接下来就是它为表中的每条记录逐行运行。

您也可以通过选择从哪里... id从一个涉及自己的联合双向支付。您可以使用以下模式在JOIN子句中引用更新表:

UPDATE P 
.... 
    FROM DB.dbo.Payments AS P 
    JOIN DB.dbo.Invoices AS I ON I.Id=P.InvoiceId 
    JOIN DB.dbo.CustomerOrders AS O ON O.Id=I.CustomerOrderId 
WHERE P.Active=0 AND P.Processed=0 AND O.ApprovalDate='2012-07-19 00:00:00' 

继续前进,另一个错误是使用没有ORDER BY的TOP。这是要求随机结果。如果你知道只有一个结果,你甚至不需要TOP。在这种情况下,也许你可以从很多可能的匹配中随机选择一个。既然你有三个级别的TOP(1)没有ORDER BY,你可能只是将它们混合起来(加入),并在它们之间取一个TOP(1)。这将使它看起来像这样

SET InvoiceId= 
    (SELECT TOP 1 I.Id 
    FROM DB.dbo.Invoices AS I 
    JOIN DB.dbo.CustomerOrders AS O 
     ON I.CustomerOrderId=O.Id 
    JOIN DB.dbo.CustomerOrders AS OO 
     ON O.ExternalOrderNumber='0'+SUBSTRING(OO.ExternalOrderNumber,1,100) 
      AND OO.Id=I.CustomerOrderId) 

但是,正如我在很早的时候提到的,这根本不与主要的FROM子句相关。我们将整个搜索移到主查询中,以便我们可以使用基于JOIN的集合操作,而不是逐行子查询。

在我展示最终查询(完全评论)之前,我认为你的SUBSTRING应该解决这个逻辑but starts with 0 instead of the current digit。但是,如果这意味着我如何阅读它,这意味着对于订单号“5678”,您正在查找“0678”,这也意味着SUBSTRING应该使用2,10000而不是1,10000

UPDATE P 
SET InvoiceId=II.Id 
FROM DB.dbo.Payments AS P 
-- invoices for payments 
JOIN DB.dbo.Invoices AS I ON I.Id=P.InvoiceId 
-- orders for invoices 
JOIN DB.dbo.CustomerOrders AS O ON O.Id=I.CustomerOrderId 
-- another order with '0' as leading digit 
JOIN DB.dbo.CustomerOrders AS OO 
    ON OO.ExternalOrderNumber='0'+substring(O.ExternalOrderNumber,2,1000) 
-- invoices for this other order 
JOIN DB.dbo.Invoices AS II ON OO.Id=II.CustomerOrderId 

-- conditions for the Payments records 
WHERE P.Active=0 AND P.Processed=0 AND O.ApprovalDate='2012-07-19 00:00:00' 

值得注意SQL Server允许UPDATE ..FROM ..JOIN其较少受到其他DBMS,例如支持甲骨文。这是因为对于Payments中的单行(更新目标),我希望你能看到很明显它可以有许多II.Id的选择,从所有的笛卡尔连接中进行选择。 你会得到一个随机可能的II.Id.

+0

非常感谢 - 您的查询在<1s中完成了这项工作,并感谢您的解释,我也学到了很多东西。 – ChrFin

0

我认为这样的事情会更有效率,如果我理解你的查询的权利。正如我手写它并没有运行它,它可能有一些语法错误。

UPDATE DB.dbo.Payments 
set InvoiceId=(SELECT TOP 1 I.Id FROM DB.dbo.Invoices AS I 
     inner join DB.dbo.CustomerOrders AS O ON I.CustomerOrderId=O.Id 
     inner join DB.dbo.CustomerOrders AS OO On OO.Id=I.CustomerOrderId 
     and O.ExternalOrderNumber='0'+SUBSTRING(OO.ExternalOrderNumber, 1, 10000))) 
FROM DB.dbo.Payments 
      JOIN DB.dbo.Invoices AS I ON I.Id=Payments.InvoiceId and 
      Payments.Active=0 
      AND Payments.Processed=0 
      AND O.ApprovalDate='2012-07-19 00:00:00' 
      JOIN DB.dbo.CustomerOrders AS O ON O.Id=I.CustomerOrderId 
+0

感谢您的输入,但新的InvoiceID将始终为空(错误允许为nto)。 – ChrFin

0

尝试使用JOIN重写。这将突出一些问题。以下功能是否也会一样? (该查询有所不同,但我想这是你想要做什么大概)

UPDATE Payments 
    SET InvoiceId= I.Id 
FROM DB.dbo.Payments 
CROSS JOIN DB.dbo.Invoices AS I 
INNER JOIN DB.dbo.CustomerOrders AS O 
    ON I.CustomerOrderId = O.Id 
INNER JOIN DB.dbo.CustomerOrders AS OO 
    ON O.ExternalOrderNumer = '0' + SUBSTRING(OO.ExternalOrderNumber, 1, 10000) 
    AND OO.Id = I.CustomerOrderId 
WHERE P.Active=0 AND P.Processed=0 AND O.ApprovalDate='2012-07-19 00:00:00') 

正如你看到的,有两个问题比较突出:

  1. 的undonditional付款和发票之间的连接(当然,你已经通过TOP 1声明发现了这种情况,但是在集合上它仍然是无条件的) - 我不确定这是否真的成为查询中的问题。将在我的虽然:)。
  2. 加入一个10000字符的列(SUBSTRING),体现在一个条件中。这是非常低效的。

如果你需要一个一次性的加速,只需要在每个表的查询,尽量存放在-结果之间,在临时表中,创建这些临时表的索引,并使用临时表来执行更新。