【问题标题】:Multiple parameterized Delphi SQL updates within a transaction based on previous select query基于先前选择查询的事务中的多个参数化 Delphi SQL 更新
【发布时间】:2015-07-14 17:53:40
【问题描述】:

我正在使用 Delphi XE8 中的参数化查询在同一循环中更新两个不同的 SQL 表。整个事情都包装在一个事务中,因此如果循环中的任何内容失败,则两个表都不会更新。

我的原始查询已发布here,这是本站帮助后的当前简化代码:

begin
  Query1:=TSQLQuery.Create(nil);
  try
    Query1.SQLConnection:=Connection;
    Query1.SQL.Text:='UPDATE TABLE A SET QUANTITY = :Quantity 
                      WHERE PRODUCT_NAME = :Product_name'; 

    Query2:=TSQLQuery.Create(nil);
    try
      Query2.SQLConnection:=Connection;
      Query2.SQL.Text:= 'UPDATE TABLE B SET QUANTITY = :Quantity 
                         WHERE PRODUCT_NAME = :Product_name'; 

      Transaction:=Connection.BeginTransaction;
      try
        for I := 1 to whatever to
        begin
          { fill params here and execute the commands }
          Query1.ExecSQL;
          Query2.ExecSQL;
        end;
        Connection.CommitFreeAndNil(Transaction);
      except
        Connection.RollbackFreeAndNil(Transaction);
        raise;
      end;
      .... etc.    

我刚刚意识到我可能有问题,表 B 的更新部分(即查询 2)涉及首先从表 B 中检索一条记录,然后根据返回的值之一对其进行更新。

所以,如果我充实上面的循环:

for I:= 1 to whatever do
begin
  //Retrieve relevant values from file being read 
  Product_name:=Product_name[I]; 
  Quantity:=Value[I];

  //Execute query 1, no problems here 
  SQL_query1.Params.ParamByName('Product_name').AsString:=
                                                   Product_name;
  SQL_query1.Params.ParamByName('Quantity').AsString:=
                                                   Quantity;      
  Query1.ExecSQL;

  //Interim get from Table B 
  //I am using datasets here that are already open in my actual code, 
  //but it could also be a SQL_query3 component; I am simply showing 
  //the logic here of what's going on
  SQL_dataset1.CommandType:=ctQuery; 
  SQL_dataset1.CommandText:=
      'SELECT QUANTITY FROM TABLE B WHERE PRODUCT_NAME = '+Product_name;
  SQL_dataset1.Open; 

  Old_quantity:=SQL_dataset1.FieldByName('Quantity').AsString;

  New_quantity:=Old_quantity+Quantity;

  //Execute query 2
  SQL_query2.Params.ParamByName('Product_name').AsString:=
                                                   Trim_str(Product_name);
  SQL_query2.Params.ParamByName('Quantity').AsString:=
                                                   Trim_str(Quantity);   
  Query2.ExecSQL; 
  ... etc.
end;

所以整个循环理论上可以更新同一个产品的数量,并且更新的数量是基于之前的数量。

这是否可能,还是我必须一次满足于一个更新? 对不起,如果这是一个愚蠢的问题。

此外,虽然表 A 当然可以使用上面的代码进行更新,因为它没有与表 B 相同的问题,但如果表 B 更新有任何问题,我根本不希望它更新。

第 2 部分:简化示例

表 A 是我实际上试图从这个问题中理解的噪音,对不起,所以让我只用表 B 重新表述。正在发生的事情是一个文件被顺序读取,并且基于每一行的信息,表B 的总和必须更新。

假设表 B 有以下记录:

产品名称数量
红色小部件 3
蓝色小部件 5

我们依次读入一个小部件购买文件,该文件是随机顺序的,包含许多混合的红色和蓝色购买。

例如:

  1. 红色小部件 +6
  2. 红色小部件 +2
  3. 蓝色小部件 +1
  4. 红色小部件 +2 ...等等。

看看下面的代码示例......

Query2.SQL.Text:= 'UPDATE TABLE B SET QUANTITY = :Quantity 
                   WHERE PRODUCT_NAME = :Product_name'; 

for I := 1 to file length do
begin
  //get the current quantity for that widget
  //add the quantity purchased in that row in the file to the 
  //quantity just retrieved 

  SQL_query2.Params.ParamByName('Product_name').AsString:=
                                                   Trim_str(Product_name);
  SQL_query2.Params.ParamByName('Quantity').AsString:=
                                                   Trim_str(Quantity); 
  Query2.ExecSQL;
end;

....我的问题是:循环遍历像这样的参数化查询会更新运行总数吗?这可能是一个愚蠢的问题,但只是想弄清楚那个和这个之间的区别......

for I := 1 to file length do
begin
  //get the current quantity for that widget
  //add the quantity purchased in that row in the file to the  
  //quantity just retrieved 

  Query2.SQL.Text:= 'UPDATE TABLE B SET QUANTITY = ' + Quantity + 
                    'WHERE PRODUCT_NAME = 'Red widget'; 
  Query2.ExecSQL;
end; 

...随着您的进行,它肯定会更新。只是想确保我正确理解这些参数化查询。根据我的阅读,如果您使用参数,肯定会有一些优化?我认为我不清楚/正确的地方是我的印象是使用参数时的“优化”并不意味着更少的数据库访问? 对数据库的调用减少 = 运行总数不同步,这是我脑海中的一个问题!

显然,表和文件比这更复杂,我们将 ID 设置为键等。我的坏例子只是为了问题的逻辑。一旦我理解了这一点,我就可以运用我有限的知识来改进查询!

【问题讨论】:

  • 那么你想要什么,你正在迭代的数据集以反映 UPDATE 命令的结果?如果是这样,您将需要重新运行 SELECT 查询(如您在示例 2 中所写)。但是,除非您有一些奇怪的逻辑需要在客户端执行,否则尝试在一个大型 SQL 语句中完成所有这些操作会更有效率。数据库不喜欢在循环(或游标)中运行大量更新。
  • 关键的{ fill params here and execute the commands } 位于for I := 1 to whatever to 循环之外是否正确? IOW 您一次又一次地使用完全相同的参数执行完全相同的查询。你不应该改变循环内的参数吗?
  • @Matt Allwood 我以为我已经通过参数化更新来解决这个问题。问题是第二次更新取决于我们在循环中的更新结果,因此我不确定参数化在这种情况下是否有效?
  • @Arioch '对不起,填充参数在循环内,只是循环外的评论。已编辑。
  • 为了澄清您的简化示例,您是否基本上得到了包含数量变化列表的表 A 和包含需要更新的数量列表的表 B?如果是这样,这可以作为单个 DB 调用中的集合操作而不是循环来完成。如果是这种情况,那么我们可以说明如何做到这一点。或者,您不能编写查询 2 来考虑存储在那里的当前数量而不需要 SELECT 吗?

标签: sql-server delphi parameters sqlite transactions


【解决方案1】:

作为对您尝试做什么的猜测,我认为以下 SQL 方法比您尝试循环更新更好。我将此基于包含数量变化列表的表 A 和包含当前数量的表 B(即,如果表 B 有 3 foo、2 bar 并且表 A 有 +2 foo、-1 foo、+1 bar 之后的结果操作将是具有 4 foo 和 3 bar 的表 B)

UPDATE TableA
SET Quantity = TableA.Quantity + 
   (SELECT sum(tableB.Change)
    FROM TableB
    WHERE TableA.ID = TableB.ID)

这适用于 SQL Server 的 Fiddle,YMMV (http://sqlfiddle.com/#!6/017df/7/0)

顺便说一句,您可能希望加入 ProductID 主键而不是产品名称。如果您想了解原因,请查看 DB 规范化

为了解决您的实际问题,我看不出它不起作用的任何原因(它会比上面的单个 SQL 语句慢很多)。该事务有效地“冻结”了您使用 UPDATE 语句触及的任何记录。您将能够使用(SELECT 和 UPDATE)任何被操纵的记录,就好像没有事务一样,但其他人可能会或可能无法看到它们,具体取决于其他数据库设置(并且绝对不能更新/删除它们),所以只要您不在单独的 SQLConnection 中运行其他查询就可以了。

修改问题编辑:

是的,这应该可以正常工作,但我强烈建议这样做

UPDATE TableB 
SET Quantity = Quantity + :QuantityChange 
WHERE PRODUCT_NAME = :Product_name

那么您不需要运行其他 SELECT 查询(除非您需要更新的总客户端,例如在写出日志时)。参数的最大好处是可以防止 SQL 注入攻击,但它也有助于数据库端的查询优化。就 DB 的访问而言,您每次 EXECUTE 都会获得其中之一,优化只是意味着 DB 每次都花更少的时间考虑它。

【讨论】:

  • 是的,这里完全缺少标准化。您在不向客户端获取任何内容的情况下执行语句的观点绝对是要走的路。
  • 感谢@Matt Allwood 的所有帮助。我在原始问题中添加了“第 2 部分”,试图去除一些噪音并尝试澄清我实际要问的问题。如果我理解正确,您会在上述答案的后半部分回答这个问题吗?
  • @Matt Allwood 花了我一段时间,但我终于明白你的意思了!不需要 get 使用您的语法,这很棒。非常感谢。虽然我正在考虑它,但我认为我可以在 SQL Server 合并查询中使用相同的逻辑? SQLite 怎么样,我们目前正在使用“插入或替换”....?
  • @Alex 我对 MERGE 不太熟悉,因为我从未使用过它们,也从未使用过 SQLite。找出答案的最简单方法是用一个点头的例子来尝试一下。如果它不起作用,我会感到惊讶,尽管这是一个相当基本的概念。我之前链接的 sqlfiddle 网站非常适合快速尝试,无需安装完整的数据库服务器。
  • @Matt Allwood 我已经让 MERGE 在 SQL 服务器中运行良好,相关位与您的示例非常相似:当匹配时,更新设置数量 = 数量 + :QuantityChange。 SQLite 还没有,已经发布了一个单独的问题here。再次感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-16
相关资源
最近更新 更多