我有以下数据库结构(简化):
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开始运行 - 我在查询中做了一些完全错误的事情,或者有办法加快一点?
它不是必须非常快,因为它是一次性任务,但几个小时对我来说似乎很长,因为我想学习(希望不会发生)下次我想要一些反馈如何改进...
答案 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')
如您所见,有两个问题突出:
TOP 1
声明解决了这个问题,但是它仍然是无条件的 - 但我不确定这是否真的是一个问题在您的查询中。虽然会在我的地方:)。SUBSTRING
)上的连接,体现在条件中。这是非常低效的。 如果您需要一次性加速,只需对每个表进行查询,尝试将结果存储在临时表中,在这些临时表上创建索引并使用临时表执行更新强>