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.