使此SQL查询更有效

时间:2012-07-12 13:22:31

标签: c# sql foreach

我有以下查询,根据采购订单检查收货,以查看最初订购的商品以及通过收货预订的商品数量。例如,我下了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和我的应用程序。是foreach吗?是原始选择将所有数据加载到内存中吗?都?我可以将此查询转换为单个查询并删除C#循环吗?如果是这样,最有效的方法是什么?

3 个答案:

答案 0 :(得分:5)

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

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

您可以编写查询以在SQL中运行,而不是在C#中操作数据吗?您似乎希望根据select语句编写相对简单的更新(例如,请参阅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

答案 1 :(得分: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写的那样,最好不要使用视图并使用类似于他提出的解决方案)。

答案 2 :(得分: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个看起来相同的更新整合到一个参数化更新中,这个更新实际上只是一个声明。