16

我有一个应用程序,我在其中找到一组记录的数据库列的 sum(),然后在单独的查询中使用该总和,类似于以下内容(组成表,但想法是相同的):

SELECT Sum(cost)
INTO v_cost_total
FROM materials
WHERE material_id >=0
AND material_id <= 10; 

[a little bit of interim work]

SELECT material_id, cost/v_cost_total
INTO v_material_id_collection, v_pct_collection
FROM materials
WHERE material_id >=0
AND material_id <= 10
FOR UPDATE; 

但是,理论上有人可以在两个查询之间更新材料表上的成本列,在这种情况下,计算的百分比将被关闭。

理想情况下,我只会在第一个查询中使用 FOR UPDATE 子句,但是当我尝试这样做时,会出现错误:

ORA-01786: FOR UPDATE of this query expression is not allowed

现在,解决方法不是问题 - 只需在找到 Sum() 之前执行一个额外的查询来锁定行,但该查询除了锁定表之外没有其他用途。虽然这个特定的例子并不耗时,但额外的查询可能会在某些情况下导致性能下降,而且它不是那么干净,所以我想避免这样做。

有谁知道不允许这样做的特定原因?在我看来,FOR UPDATE 子句应该只锁定与 WHERE 子句匹配的行——我不明白为什么我们对这些行所做的事情很重要。

编辑:看起来 SELECT ... FOR UPDATE 可以与分析函数一起使用,如下面的 David Aldridge 所建议的那样。这是我用来证明这有效的测试脚本。

SET serveroutput ON;

CREATE TABLE materials (
    material_id NUMBER(10,0),
    cost        NUMBER(10,2)
);
ALTER TABLE materials ADD PRIMARY KEY (material_id);
INSERT INTO materials VALUES (1,10);
INSERT INTO materials VALUES (2,30);
INSERT INTO materials VALUES (3,90);

<<LOCAL>>
DECLARE
    l_material_id materials.material_id%TYPE;
    l_cost        materials.cost%TYPE;
    l_total_cost  materials.cost%TYPE;

    CURSOR test IS
        SELECT material_id,
            cost,
            Sum(cost) OVER () total_cost
        FROM   materials
        WHERE  material_id BETWEEN 1 AND 3
        FOR UPDATE OF cost;
BEGIN
    OPEN test;
    FETCH test INTO l_material_id, l_cost, l_total_cost;
    Dbms_Output.put_line(l_material_id||' '||l_cost||' '||l_total_cost);
    FETCH test INTO l_material_id, l_cost, l_total_cost;
    Dbms_Output.put_line(l_material_id||' '||l_cost||' '||l_total_cost);
    FETCH test INTO l_material_id, l_cost, l_total_cost;
    Dbms_Output.put_line(l_material_id||' '||l_cost||' '||l_total_cost);
END LOCAL;
/

这给出了输出:

1 10 130
2 30 130
3 90 130
4

4 回答 4

26

该语法select . . . for update锁定表中的记录以准备更新。当您进行聚合时,结果集不再引用原始行。

换句话说,数据库中没有要更新的记录。只有一个临时结果集。

于 2013-08-27T02:13:10.840 回答
3

您可以尝试以下方法:

<<LOCAL>>
declare
  material_id materials.material_id%Type;
  cost        materials.cost%Type;
  total_cost  materials.cost%Type;
begin
  select material_id,
         cost,
         sum(cost) over () total_cost
  into   local.material_id,
         local.cost,
         local.total_cost 
  from   materials
  where  material_id between 1 and 3
  for update of cost;

  ...

end local;

第一行给出了总成本,但它选择了所有行,理论上它们可以被锁定。

我不知道这是否被允许,请注意——听听是否允许很有趣。

于 2013-08-27T07:13:26.057 回答
1

为此,您可以使用该WITH命令。

示例:

WITH result AS (
  -- your select
) SELECT * FROM result GROUP BY material_id;
于 2021-01-06T16:32:22.047 回答
0

您的问题是“但是,理论上有人可以在两个查询之间更新材料表上的成本列,在这种情况下,计算的百分比将被关闭。”?

在这种情况下,您可能可以简单地使用内部查询:

SELECT material_id, cost/(SELECT Sum(cost)
  FROM materials
  WHERE material_id >=0
  AND material_id <= 10)
INTO v_material_id_collection, v_pct_collection
FROM materials
WHERE material_id >=0
AND material_id <= 10;

为什么要锁定表?如果其他应用程序在此期间尝试更新该表,它们可能会失败,对吗?

于 2013-08-27T10:13:19.697 回答