I have successfully solved situation regarding swapping rows by value here what still work excellent.
But by using this function I see some lacks of functionality in mean of
This handles the tabbing order by a set of triggers+ associated functions.
brred
for a particular brkalk
will cause all values between the old and new values to be rotated, either up or down.brred
values above the OLD value (for the same brkalk
) to be decremented (shifted down)brred
values above the NEW value (for the same brkalk
) to be incremented (shifted up)To avoid recursively updating forever, one extra bit of information per row is needed: the flipflag
(which should only be touched by the triggers, not by the application code. It could be hidden from the application by means of a view)
ALTER TABLE kalksad1 ADD COLUMN flipflag boolean DEFAULT false;
-- This should be an UNIQUE constraint
-- , but that would need to be deferrable.
CREATE INDEX ON kalksad1 (brkalk,brred);
-- Trigger functions for Insert/update/delete
CREATE function rotate_brred()
RETURNS TRIGGER AS $body$
BEGIN
UPDATE kalksad1 fr
SET brred = brred +1
, flipflag = NOT flipflag -- alternating bit protocol ;-)
WHERE NEW.brred < OLD.brred
-- AND OLD.flipflag = NEW.flipflag -- redundant condition
-- AND OLD.brkalk = NEW.brkalk
AND fr.brkalk = NEW.brkalk
AND fr.brred >= NEW.brred
AND fr.brred < OLD.brred
AND fr.kalk_id <> NEW.kalk_id -- exlude the initiating row
;
UPDATE kalksad1 fr
SET brred = brred -1
, flipflag = NOT flipflag
WHERE NEW.brred > OLD.brred
-- AND OLD.flipflag = NEW.flipflag
-- AND OLD.brkalk = NEW.brkalk
AND fr.brkalk = NEW.brkalk
AND fr.brred <= NEW.brred
AND fr.brred > OLD.brred
AND fr.kalk_id <> NEW.kalk_id
;
RETURN NEW;
END;
$body$
language plpgsql;
CREATE function shift_down()
RETURNS TRIGGER AS $body$
BEGIN
UPDATE kalksad1 fr
SET brred = brred -1
, flipflag = NOT flipflag -- alternating bit protocol ;-)
WHERE fr.brred > OLD.brred
AND fr.brkalk = OLD.brkalk
;
RETURN NEW;
END;
$body$
language plpgsql;
CREATE function shift_up()
RETURNS TRIGGER AS $body$
BEGIN
UPDATE kalksad1 fr
SET brred = brred +1
, flipflag = NOT flipflag -- alternating bit protocol ;-)
WHERE fr.brred >= NEW.brred
AND fr.brkalk = NEW.brkalk
;
RETURN NEW;
END;
$body$
language plpgsql;
-- Triggers for Insert/Update/Delete
-- ONLY for the case where brkalk is NOT CHANGED
CREATE TRIGGER shift_brred_u
AFTER UPDATE OF brred ON kalksad1
FOR EACH ROW
WHEN (OLD.flipflag = NEW.flipflag AND OLD.brkalk = NEW.brkalk AND OLD.brred <> NEW.brred)
EXECUTE PROCEDURE rotate_brred()
;
CREATE TRIGGER shift_brred_d
AFTER DELETE ON kalksad1
FOR EACH ROW
EXECUTE PROCEDURE shift_down()
;
CREATE TRIGGER shift_brred_i
BEFORE INSERT ON kalksad1
FOR EACH ROW
EXECUTE PROCEDURE shift_up()
;
-- Test it
UPDATE kalksad1
SET brred = 2
WHERE brkalk = 2
AND brred = 4;
SELECT * FROM kalksad1
ORDER BY brkalk, brred;
RESULT:
UPDATE 1
kalk_id | brkalk | brred | description | flipflag
---------+--------+-------+---------------------------+----------
36 | 1 | 1 | text index 36 doc 1 row 1 | f
37 | 1 | 2 | text index 37 doc 1 row 2 | f
26 | 2 | 1 | text index 26 doc 2 row 1 | f
43 | 2 | 2 | text index 43 doc 2 row 4 | f
30 | 2 | 3 | text index 30 doc 2 row 2 | t
42 | 2 | 4 | text index 42 doc 2 row 3 | t
12 | 2 | 5 | text index 12 doc 2 row 5 | f
46 | 3 | 1 | text index 46 doc 3 row 1 | f
47 | 3 | 2 | text index 47 doc 3 row 2 | f
32 | 4 | 1 | text index 32 doc 4 row 1 | f
38 | 5 | 1 | text index 38 doc 5 row 1 | f
39 | 5 | 2 | text index 39 doc 5 row 2 | f
(12 rows)
Example 1:
update kalksad1 set brred=_brred
from (
select
row_number() over (
order by brred<2 desc, brred=4 desc, brred>=2 desc, brred
) as _brred,
kalk_id as _kalk_id
from kalksad1
where brkalk=2
order by _kalk_id
) as _
where kalk_id=_kalk_id and brred!=_brred;
Example 2:
update kalksad1 set brred=_brred
from (
select
row_number() over (
order by brred<4 desc, brred!=1 desc, brred>=4 desc, brred
) as _brred,
kalk_id as _kalk_id
from kalksad1
where brkalk=2
order by _kalk_id
) as _
where kalk_id=_kalk_id and brred!=_brred;
If you have unique index on (brkalk,brred)
then it would be more complicated, as during renumbering there'll be duplicate brred
.
But for many rows I'd recommend using something which was very useful in the days of BASIC language on 8bit computers - number your rows with gaps.
So instead of:
(26, 2, 1, 'text index 26 doc 2 row 1'),
(30, 2, 2, 'text index 30 doc 2 row 2'),
(42, 2, 3, 'text index 42 doc 2 row 3'),
(43, 2, 4, 'text index 43 doc 2 row 4'),
(12, 2, 5, 'text index 12 doc 2 row 5'),
use:
(26, 2, 1024, 'text index 26 doc 2 row 1'),
(30, 2, 2048, 'text index 30 doc 2 row 2'),
(42, 2, 3072, 'text index 42 doc 2 row 3'),
(43, 2, 4096, 'text index 43 doc 2 row 4'),
(12, 2, 5120, 'text index 12 doc 2 row 5'),
Then your examples would just look like:
update kalksad1 set brred=(2048+1024)/2 where kalk_id=43
, which would change it to:
(26, 2, 1024, 'text index 26 doc 2 row 1'), (43, 2, 1536, 'text index 43 doc 2 row 4'), (30, 2, 2048, 'text index 30 doc 2 row 2'), (42, 2, 3072, 'text index 42 doc 2 row 3'), (12, 2, 5120, 'text index 12 doc 2 row 5'),
update kalksad1 set brred=(4096+3072)/2 where kalk_id=43
, which would change it to:
(30, 2, 2048, 'text index 30 doc 2 row 2'), (42, 2, 3072, 'text index 42 doc 2 row 3'), (26, 2, 3584, 'text index 26 doc 2 row 1'), (43, 2, 4096, 'text index 43 doc 2 row 4'), (12, 2, 5120, 'text index 12 doc 2 row 5'),
Only when there's no gap between rows where the target should be, you'd need to first renumber rows using for example:
update kalksad1 set brred=_brred*1024
from (
select row_number() over (order by brred) as _brred, kalk_id as _kalk_id
from kalksad1
where brkalk=2
order by _brred desc
) as _
where kalk_id=_kalk_id;
This would be much aster than changing every row between source and target. But this'll only matter when there may be many rows to change.