1

入庫を発注書と照合して、最初に注文された品目と、入庫によって予約された数を確認する次のクエリがあります。たとえば、10 個のバナナ ミルクセーキを注文すると、その注文書でこれらのミルクセーキを 5 個受け取ったことを示す商品受領書が生成されます。

SELECT t.PONUM, t.ITMNUM, t.ordered, 
       SUM(t.received) as received, 
       t.ordered - ISNULL(SUM(t.received),0) as remaining, 
       SUM(t.orderedcartons) as orderedcartons, 
       SUM(t.cartonsreceived) as cartonsreceived, 
       SUM(t.remainingcartons) as remainingcartonsFROM(SELECT pod.PONUM, 
       pod.ITMNUM, pod.QTY as ordered, ISNULL(grd.QTYRECEIVED, 0) as received, 
       pod.DELIVERYSIZE as orderedcartons, 
       ISNULL(grd.DELIVERYSIZERECEIVED, 0) as cartonsreceived, 
       (pod.DELIVERYSIZE - ISNULL(grd.DELIVERYSIZERECEIVED, 0)) as remainingcartons
FROM TBLPODETAILS pod 
  LEFT OUTER JOIN TBLGRDETAILS grd 
    ON pod.PONUM = grd.PONUM and pod.ITMNUM = grd.ITMNUM) t
GROUP BY t.ITMNUM, t.PONUM, t.ordered
ORDER BY t.PONUM

次のデータを返します。

PONUM   ITMNUM  ordered received remaining orderedcartons cartonsreceived remainingcartons

1       1     5.0000    3.0000      2.0000   5.0000         3.0000          2.0000

次に、上記のクエリから返されたデータに基づいて更新クエリを生成する C# ループがあります。

foreach (DataRow POUpdate in dt.Rows) {...

query += "UPDATE MYTABLE SET REMAININGITEMS=" + remainingQty.ToString() 
       + ", REMAININGDELIVERYSIZE=" + remainingBoxes.ToString() + " WHERE ITMNUM=" 
       + itemNumber + " AND PONUM=" + poNumber + ";";

次に、DB に対して各更新クエリを実行します。これは私のローカル開発マシンでうまく動作します。

ただし、本番サーバーにデプロイすると、最初のクエリで 150,000 を超えるレコードが引き戻されます。

そのため、非常に多くの行をループすると、SQL とアプリがロックされます。それはforeachですか?そのすべてのデータをメモリにロードするのは元の選択ですか? 両方?このクエリを 1 つのクエリにして、C# ループを切り取ることはできますか? もしそうなら、これを達成するための最も効率的な方法は何ですか?

4

3 に答える 3

5

SQLの目標は、テーブル全体に対する操作を一度に作成することです。SQL Serverはこれを行うのに非常に効率的ですが、一貫性やトランザクションのアトミック性などを処理する必要があるため、対話にかなりのオーバーヘッドが必要になります。したがって、ある意味で、トランザクションあたりの固定コストは高くなります。サーバーがその処理を実行しますが、トランザクション内の追加行の限界費用は非常に低く、100万行の更新は10の更新の1/2の速度である可能性があります。

これは、foreachによってSQLサーバーがアプリケーションと絶えず行き来し、トランザクションのロック/ロック解除と実行の固定コストが毎回発生することを意味します。

C#でデータを操作する代わりに、SQLで動作するクエリを記述できますか?selectステートメントに基づいて比較的単純な更新を記述したいようです(たとえば、IDの一致に基づいたあるテーブルから別のテーブルへのSQL更新を参照してください) 。

次のようなものを試してください(データベース構造にアクセスできないため、コードテストされていません)。

UPDATE MYTABLE 
  SET REMAININGITEMS = remainingQty, 
  REMAININGDELIVERYSIZE=remainingBoxes
From 
(SELECT t.PONUM, t.ITMNUM, t.ordered, 
       SUM(t.received) as received, 
       t.ordered - ISNULL(SUM(t.received),0) as remaining, 
       SUM(t.orderedcartons) as orderedcartons, 
       SUM(t.cartonsreceived) as cartonsreceived, 
       SUM(t.remainingcartons) as remainingcartonsFROM(SELECT pod.PONUM, 
       pod.ITMNUM, pod.QTY as ordered, ISNULL(grd.QTYRECEIVED, 0) as received, 
       pod.DELIVERYSIZE as orderedcartons, 
       ISNULL(grd.DELIVERYSIZERECEIVED, 0) as cartonsreceived, 
       (pod.DELIVERYSIZE - ISNULL(grd.DELIVERYSIZERECEIVED, 0)) as remainingcartons
FROM TBLPODETAILS pod 
  LEFT OUTER JOIN TBLGRDETAILS grd 
    ON pod.PONUM = grd.PONUM and pod.ITMNUM = grd.ITMNUM) t
GROUP BY t.ITMNUM, t.PONUM, t.ordered
ORDER BY t.PONUM ) as x

join MYTABLE on MYTABLE.ITMNUM=x.itmnum AND MYTABLE.PONUM=i.ponum
于 2012-07-12T13:34:36.920 に答える
3

KM がコメントで述べているように、ここでの問題はクライアント アプリに戻ってきて、別のデータベース トリップで各行を操作することです。それは遅く、ばかげた小さなバグにつながる可能性があり、偽のデータを引き起こす可能性があります.

また、あなたが行っているように文字列を SQL に連結することは、一般的に非常に悪い考えと考えられています - SQL インジェクション (Joel Coehoorn が書いているように) は現実的な可能性です。

どうですか:

create view OrderBalance 
as 
SELECT t.PONUM, t.ITMNUM, t.ordered, 
       SUM(t.received) as received, 
       t.ordered - ISNULL(SUM(t.received),0) as remaining, 
       SUM(t.orderedcartons) as orderedcartons, 
       SUM(t.cartonsreceived) as cartonsreceived, 
       SUM(t.remainingcartons) as remainingcartonsFROM(SELECT pod.PONUM, 
       pod.ITMNUM, pod.QTY as ordered, ISNULL(grd.QTYRECEIVED, 0) as received, 
       pod.DELIVERYSIZE as orderedcartons, 
       ISNULL(grd.DELIVERYSIZERECEIVED, 0) as cartonsreceived, 
       (pod.DELIVERYSIZE - ISNULL(grd.DELIVERYSIZERECEIVED, 0)) as remainingcartons
FROM TBLPODETAILS pod 
  LEFT OUTER JOIN TBLGRDETAILS grd 
    ON pod.PONUM = grd.PONUM and pod.ITMNUM = grd.ITMNUM) t
GROUP BY t.ITMNUM, t.PONUM, t.ordered

これには、「MYTABLE」が持っているデータが正確に含まれているようです。おそらく、MYTABLE はもう必要なく、ビューを使用できます。

MYTABLE に他のデータがある場合、更新は次のようになります。

UPDATE MYTABLE 
SET REMAININGITEMS       = ob.remainingitems, 
    REMAININGDELIVERYSIZE = ob.remainingBoxes
from MYTABLE mt 
   join OrderBalance ob 
on mt.ITMNUM = ob.itemNumber 
AND mt.PONUM = ob.poNumber

(ただし、David Mannheim が書いているように、ビューを使用せず、彼が提案するものと同様のソリューションを使用する方がよい場合があります)。

于 2012-07-12T13:36:00.800 に答える
0

他の回答は、更新全体をRDBMSで完全に実行するための優れた方法を示しています。そのようにできるのであれば、それは完璧な解決策です。余分なラウンドトリップとデータ転送の問題があるため、C#とRDBMSの組み合わせでそれを打ち負かすことはできません。

ただし、何らかの理由でRDBMSで実行できない計算が更新に必要な場合、コードを変更して、現在作成している巨大な150000行の更新の代わりに単一のパラメーター化された更新を作成する必要があります。

using (var upd = conn.CreateCommand()) {
    upd.CommandText = @"
        UPDATE MYTABLE SET
            REMAININGITEMS=@remainingQty
        ,   REMAININGDELIVERYSIZE=@remainingBoxes
        WHERE ITMNUM=@itemNumber AND PONUM=@poNumber";
    var remainingQtyParam = upd.CreateParameter();
    remainingQtyParam.ParameterName = "@remainingQty";
    remainingQtyParam.DbType = DbType.Int64; // <<== Correct for your specific type
    upd.Parameters.Add(remainingQtyParam);
    var remainingBoxesParam = upd.CreateParameter();
    remainingBoxesParam.ParameterName = "@remainingBoxes";
    remainingBoxesParam.DbType = DbType.Int64; // <<== Correct for your specific type
    upd.Parameters.Add(remainingBoxesParam);
    ...
    foreach (DataRow POUpdate in dt.Rows) {
        remainingQtyParam.Value = ...
        remainingBoxesParam.Value = ...
        upd.ExecuteNonQuery();
    }
}

アイデアは、実際には単一のステートメントである単一のパラメーター化された更新に同じように見える150,000の更新を行うことです。

于 2012-07-12T13:35:09.047 に答える