如何提高SQL查询性能

时间:2012-10-02 11:03:45

标签: sql-server sql-server-2008 tsql

我有以下数据库结构(简化):

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


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


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

每个客户订单都有一个发票,每个发票可以有多个付款。 ExternalStoreOrderNumber是对我们导入订单的外部合作伙伴商店的订单的引用,以及ApprovalDate导入发生时的时间戳。

现在我们遇到的问题是,根据以下逻辑,我们有一个错误的输入需要将一些付款更改为其他发票(几个亨德特,因此太难以手工完成): 搜索订单的发票,其外部编号与当前编号相同,但以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行)在测试系统上启动了查询,现在它从16h开始运行 - 我在查询中做了一些完全错误的事情,或者有办法加快一点?
它不是必须非常快,因为它是一次性任务,但几个小时对我来说似乎很长,因为我想学习(希望不会发生)下次我想要一些反馈如何改进...

3 个答案:

答案 0 :(得分: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。

答案 1 :(得分: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

答案 2 :(得分: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. 付款和发票之间的非正式联接(当然,你已经通过TOP 1声明解决了这个问题,但是它仍然是无条件的 - 但我不确定这是否真的是一个问题在您的查询中。虽然会在我的地方:)。
  2. 10000个字符列(SUBSTRING)上的连接,体现在条件中。这是非常低效的。
  3. 如果您需要一次性加速,只需对每个表进行查询,尝试将结果存储在临时表中,在这些临时表上创建索引并使用临时表执行更新