【问题标题】:UPDATE statement: re-evaluate subquery after each SETUPDATE 语句:在每个 SET 之后重新评估子查询
【发布时间】:2014-07-22 13:31:34
【问题描述】:
CREATE TABLE test (id NUMBER(3));

INSERT INTO test VALUES (1);
INSERT INTO test VALUES (2);
INSERT INTO test VALUES (2);
INSERT INTO test VALUES (3);
INSERT INTO test VALUES (4);
INSERT INTO test VALUES (4);
INSERT INTO test VALUES (5);

我想使用此查询使 test 中的数字唯一(如 {1,2,3,4,5,6,7} - 即删除双精度数):

UPDATE test u SET u.id = (SELECT max(nn.id) FROM test nn) + 1 
  WHERE 1 < (
    SELECT tt.rown 
      FROM (SELECT rowid, row_number() over ( partition by t.id order by t.id) AS rown FROM test t) tt
      WHERE tt.rowid = u.rowid
    ); 

上述查询将表更新为{1,2,3,4,5,6,6}。它正确地将第二个2 替换为6,但第二个4 也变为6,应该是7。不应替换现有的非重复项,而应仅替换第二次以后的重复项。

上述更新语句的问题是(SELECT max(nn.id) FROM test nn)被评估一次并被缓存,但它实际上取决于更新的内容。是否可以在每次 SET 后强制重新评估 (SELECT max(nn.id) FROM test nn)?我尝试了一些提示,例如/*+NOCACHE*/,但没有成功。

换句话说,在更新过程中,您需要考虑已经更新的字段。

有什么想法吗?

我认为这可以通过 NON-DETERMINISTIC 函数解决,但我不想创建函数。编辑:如果我尝试使用函数计算 id,我得到 ORA-04091: table TEST is mutating, trigger/function may not see it。使用PRAGMA AUTONOMOUS_TRANSACTION; 得到与上述查询相同的结果。

使用预先计算的 id 的解决方案,绕过查询 re_evaluation

AudriyM 使用 CTE 为 MS SQL Server 2008 解决了这个问题(参见 cmets)。据我所知,Oracle 中没有 CTE 的替代方案,但由于 AudriyM 的解决方案是基于预先计算的 id 值,我虽然可以在 Oracle 中使用子查询对其进行翻译。这里是:

UPDATE test u SET u.id = ( SELECT newIDs.newID
                           FROM ( SELECT  ranked.rowid,
                                          ranked.m + row_number() over (order by ranked.id, ranked.r) as newID 
                                  FROM ( SELECT t.rowid, t.id, row_number() over ( partition by t.id order by t.id) AS r, max(id) over() AS m 
                                        FROM test t ) ranked
                                  WHERE ranked.r > 1 ) newIDs
                           WHERE u.rowid = newIDs.rowid )
WHERE u.rowid IN ( SELECT ranked.rowid
                   FROM ( SELECT t.rowid, t.id, row_number() over ( partition by t.id order by t.id) AS r, max(id) over() AS m 
                          FROM test t ) ranked
                   WHERE ranked.r > 1 );

没有预先计算的 id 的解决方案,使用查询 re_evaluation

仍未找到,问题仍未得到解答。

【问题讨论】:

  • 我很难理解这个查询的意义。请你能解释一下你为什么使用它吗?你的预期结果是什么?
  • 我已经编辑了这个问题。我只是想让每个后续的已经存在的数字等于表中的最大数字 + 1。问题是(SELECT max(nn.id) FROM test nn) 的缓存,我想知道是否可以禁用这种缓存机制。是否有可能因为表正在变异?
  • 这最好是一次性(或特殊维护)查询,因为为了线程安全SELECT MAX(...) + 1 样式的东西必须锁定整个表。为了使这种事情起作用,数据库需要某种方法来区分重复的行 - 是否还有其他一些独特的列?或者,您是否需要保持匹配的行 - deleting duplicates 怎么样?
  • 第一次复制是什么意思?有没有办法区分第一个实例和其他实例?
  • 作为评论发布,因为我还没有弄清楚如何将其翻译成 Oracle:sqlfiddle.com/#!3/b2634/2 这使用了可更新 CTE(WITH 子句)的概念。也许使用 ROWID 可以在某种程度上有所帮助,我只是不确定如何过滤掉不能更改的行正确修改其余的行。

标签: sql oracle oracle11g


【解决方案1】:

尝试使用以下语句。您的表不允许识别例如 ID=2 的行,因此要将表与 ROW_NUMBER 链接,您可以使用 ROWID

UPDATE
Test 
SET id = (SELECT RN FROM
                     (SELECT ROW_NUMBER() 
                             OVER (ORDER BY ID) as RN 
                             FROM Test
                     ) T1
                     WHERE T1.RowID=Test.RowID
          )

SQLFiddle demo

【讨论】:

  • 你也可以在这里使用ROWNUM。尽管如此,问题仍然存在:现有的非重复项应保持不变,并且只应替换第二个以后的重复项。我会坚持原来的 SQL 并专注于 (SELECT max(nn.id) FROM test nn) 是否可以在每次 SET 操作后重新评估的问题。
  • 但是第二个 2 必须替换为 3 因为它在下一个值之前。但是在这种情况下,您应该如何处理下一行 3 以避免重复 3?不清楚。
  • 不,第二个2 应替换为(SELECT max(nn.id) FROM test nn),如原始查询中所述。原始查询确实做了它应该做的事情。唯一的问题是(SELECT max(nn.id) FROM test nn)的缓存
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多