UPDATE statement: re-evaluate subquery after each SET

﹥>﹥吖頭↗ 提交于 2019-12-22 09:24:24

问题


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);

I want to make the numbers in test unique (like {1,2,3,4,5,6,7} - i.e. remove doubles) using this query:

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
    ); 

The above query updates the table to {1,2,3,4,5,6,6}. It replaces the second 2 to 6 correctly, but the second 4 becomes 6 also, which should be 7. Existing non-duplicates should not be replaced, but only the second-onward duplicates.

The problem with the above update statement is that (SELECT max(nn.id) FROM test nn) is evaluated once and cached, but it actually depends on what is updated. Is it possible to force re-evaluation of (SELECT max(nn.id) FROM test nn) after each SET? I tried some hints like /*+NOCACHE*/ without success.

In other words, during update, you need to take into account the fields that already have been updated.

Anyone ideas?

I assume this could be solved with NON-DETERMINISTIC function, but I don't want to make functions. EDIT: if I try to compute the id with a function i get ORA-04091: table TEST is mutating, trigger/function may not see it. Using PRAGMA AUTONOMOUS_TRANSACTION; gives the same result as the above query.

Solution with precalculated ids, bypassing query re_evaluation

AudriyM solved this (cf. comments) for MS SQL Server 2008 using CTEs. There is no alternative in Oracle for CTEs as far as I know, but because AudriyM's solution was based on precalculated values for ids, I though I could translate it in Oracle with subqueries. And here it is:

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 );

Solution without precalculated ids, using query re_evaluation

Still not found and the question remains unanswered.


回答1:


Try to use the following statement. Your table doesn't allow to identify a row for example with ID=2 so to link table with ROW_NUMBER you can use 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



来源:https://stackoverflow.com/questions/23994409/update-statement-re-evaluate-subquery-after-each-set

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!