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 + ";";

然后我对数据库执行每个更新查询。这在我的本地开发机器上运行良好。

但是,部署到生产服务器会在第一次查询中撤回超过 150,000 条记录。

所以循环这么多行会锁定 SQL 和我的应用程序。是前锋吗?是将所有数据加载到内存中的原始选择吗?两个都?我可以将此查询合并为一个查询并删除 C# 循环吗?如果是这样,实现这一目标的最有效方法是什么?

4

3 回答 3

5

在 SQL 中,目标应该是一次在整个表上编写操作。SQL 服务器可以非常高效地执行此操作,但在任何交互上都需要大量开销,因为它需要处理事务的一致性、原子性等。所以在某种程度上,每个事务的固定成本很高,因为服务器来做它的事情,但是您在事务中额外行的边际成本非常低 - 更新 1m 行可能是更新 10 行的 1/2。

这意味着 foreach 将导致 SQL 服务器不断地与您的应用程序来回切换,并且每次都会发生锁定/解锁和执行事务的固定成本。

您可以编写查询以在 SQL 中操作,而不是在 C# 中操作数据吗?您似乎想根据您的选择语句编写一个相对简单的更新(例如,参见SQL update from one Table to another based on a ID match

尝试以下操作(未测试代码,因为我无权访问您的数据库结构等):

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 回答