0

私は次のDB構造を持っています(簡略化):

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行)を使用してテストシステムでそのクエリを開始し、16時間から実行されています-クエリで完全に間違ったことをしましたか、それとも少しスピードアップする方法がありますか?
1回限りの作業であるため、それほど高速である必要はありませんが、数時間は私には長いように思えます。次回は(うまくいけば起こらない)学習したいので、改善方法についてフィードバックをお願いします...

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'

次に進むと、もう1つの間違いは、ORDERBYなしでTOPを使用することです。それはランダムな結果を求めています。結果が1つしかないことがわかっている場合は、TOPも必要ありません。この場合、多くの可能な一致からランダムに1つを選択しても大丈夫かもしれません。ORDER BYのないTOP(1)には3つのレベルがあるので、それらをすべてマッシュアップ(結合)して、すべてのTOP(1)を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'

UPDATE ..FROM ..JOINSQL Serverでは、Oracleなどの他のDBMSではサポートされていないものが許可されていることに注意してください。これは、Payments(更新ターゲット)の1つの行について、すべてのデカルト結合から選択できる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')

ご覧のとおり、2つの問題が際立っています。

  1. 支払いと請求書の間の不思議な結合(もちろん、あなたはこれをTOP 1声明で捕らえましたが、設定的にはそれでも無条件です)-これが本当にあなたのクエリの問題であるかどうかは本当にわかりません。でも私の中にいるでしょう:)。
  2. SUBSTRING条件で具体化された10000文字の列()での結合。これは非常に非効率的です。

1回限りの高速化が必要な場合は、各テーブルでクエリを実行し、中間結果を一時テーブルに格納し、それらの一時テーブルにインデックスを作成し、一時テーブルを使用して更新を実行します。

于 2012-10-02T11:43:07.077 に答える