0

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

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 小时开始运行 - 我在查询中做错了什么还是有办法加快它的速度?
它不需要非常快,因为它是一次性任务,但对我来说几个小时似乎很长,因为我想学习(希望不会发生)下次我想要一些反馈如何改进......

4

3 回答 3

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。在这种情况下,也许你可以从许多可能的匹配中随机选择一个。由于您有三个没有 ORDER BY 的 TOP(1) 级别,您不妨将它们全部混合(加入)并在所有它们中取一个 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”,这也意味着应该使用 SUBSTRING2,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 较少支持,例如 Oracle。这是因为对于 Payments 中的单行(更新目标),我希望您可以看到很明显它可以从所有笛卡尔连接中选择许多 II.Id 选项。 你会得到一个随机的可能的 II.Id。

于 2012-10-02T11:48:01.050 回答
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
于 2012-10-02T11:38:35.507 回答
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. Payments 和 Invoices 之间的无条件连接(当然,您已经通过TOP 1语句抓住了这一点,但设置方面它仍然是无条件的) - 我不确定这是否真的是您的查询中的问题。不过会在我的:)。
  2. SUBSTRING包含在条件中的 10000 个字符的列 ( ) 上的连接。这是非常低效的。

如果您需要一次性加速,只需对每个表进行查询,尝试将中间结果存储在临时表中,在这些临时表上创建索引并使用临时表执行更新。

于 2012-10-02T11:43:07.077 回答