PostgreSQL, drag and swap

后端 未结 2 922
滥情空心
滥情空心 2021-01-26 12:55

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

相关标签:
2条回答
  • 2021-01-26 13:13

    This handles the tabbing order by a set of triggers+ associated functions.

    • UPDATING the brred for a particular brkalk will cause all values between the old and new values to be rotated, either up or down.
    • DELETE will cause all the brred values above the OLD value (for the same brkalk) to be decremented (shifted down)
    • INSERT will cause all the 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)
    
    0 讨论(0)
  • 2021-01-26 13:18

    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:

  • Example 1: 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'),
    

  • Example 2: 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.

0 讨论(0)
提交回复
热议问题