Given the updated requirement, you might be better off using a strategy like Erwin Brandstetter's solution that can easily be applied to an arbitrary number of columns, however I will leave my original answer with an update for doing the second column.
Original answer (requirement to shuffle one column):
Given how general your requirements are about the order of the shuffle, I'm not sure how much this will help practically, but I think it answers your question:
update test
SET firstname = t2.firstname
FROM
(
SELECT id, COALESCE(LAG(firstname, 1) OVER (ORDER BY id RANGE UNBOUNDED PRECEDING), LAST_VALUE(firstname) OVER (ORDER BY id RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)) firstname
FROM test t2
WHERE firstname IS NOT NULL
) t2
WHERE test.id = t2.id
The idea here is that the inner query gets the values shifted by one (ignoring nulls). The COALESCE
is used since the first one doesn't have a proceeding entry, so it falls back on some LAST_VALUE
logic to get the last value (i.e. it behaves as if the shift loops around).
The surrounding UPDATE
statement joins test to the subquery to actually update the data.
You can see it working in this Fiddle.
Updated (requirement to also shuffle a second column):
Given the updated requirement to also shuffle a second field, you could apply different logic there:
update test
SET firstname = t2.firstname,
lastname = t2.lastname
FROM
(
SELECT id,
COALESCE(LAG(firstname, 1) OVER (ORDER BY id), LAST_VALUE(firstname) OVER (ORDER BY id RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)) firstname,
COALESCE(LEAD(lastname, 1) OVER (ORDER BY id), FIRST_VALUE(lastname) OVER (ORDER BY id RANGE UNBOUNDED PRECEDING)) lastname
FROM test t2
WHERE firstname IS NOT NULL AND lastname IS NOT NULL
) t2
WHERE test.id = t2.id
This simply shuffles the lastname
in the opposite direction, so firstname
is grabbed from the previous non-null row (wrapping around) and lastname
is grabbed from the next non-null row (wrapping around). Both columns will be changed.
Here is a fiddle of it working