0

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 drag and drop rows.
I try to get solution here where discussion goes wrong way and offered solution is not adequate since they need additional columns.

Here is well known table:

DROP TABLE IF EXISTS kalksad1;

CREATE TABLE kalksad1(
kalk_id     int PRIMARY KEY,
brkalk      integer, 
brred       integer, 
description text);

INSERT INTO kalksad1 VALUES
(12, 2, 5, 'text index 12 doc 2 row 5'),
(26, 2, 1, 'text index 26 doc 2 row 1'),
(30, 2, 2, 'text index 30 doc 2 row 2'),
(32, 4, 1, 'text index 32 doc 4 row 1'),
(36, 1, 1, 'text index 36 doc 1 row 1'),
(37, 1, 2, 'text index 37 doc 1 row 2'),
(38, 5, 1, 'text index 38 doc 5 row 1'),
(39, 5, 2, 'text index 39 doc 5 row 2'),
(42, 2, 3, 'text index 42 doc 2 row 3'),
(43, 2, 4, 'text index 43 doc 2 row 4'),
(46, 3, 1, 'text index 46 doc 3 row 1'),
(47, 3, 2, 'text index 47 doc 3 row 2');

Object of manipulation is reordering values of column "brred"(row) under same "brkalk"(doc).
Let "brkalk" be 2.

Now I would like to get reordering/swapping according to drag and drop needs where swapping just one row look unnatural. I have datagrid binded to kalksad1 table so I will describe situation looking in my datagrid filled with query "... ORDERED by brred".

If I could explain query by words that would be...
Example 1:
Under doc 2 I will drag row 4 and drop it to position of row 2.
For that is needed following steps:
1) Remember data of row 4.
2) In row 3 replace value of "brred" from 3 to 4.
3) In row 2 replace value of "brred" from 2 to 3.
4) In remembered data from step 1) change value of "brred" from 4 to 2.

Example 2:
Under doc 2 I will drag row 1 and drop it to position 3.
That can go like this:
1) Remember data of row 1.
2) In row 2 replace value of "brred" from 2 to 1.
3) In row 3 replace value of "brred" from 3 to 2.
4) In remembered data from step 1. change value of "brred" from 1 to 3.

Idea that this may be possible comes from elegant solution with swapping and SO questions like this, this and this. I make examples based on my thinkings but that shouldn't go that way if better exist.

Please if someone can write described query at way similar to those for swapping of user Roman Pekar.

EDIT: Solution based on Example1 from Tometzky  

Imports Npgsql

Public Class Form1
Dim dServer As String = "127.0.0.1"
Dim dPort As String = "5432"
Dim dUser As String = "postgres"
Dim dPass As String = yourpass
Dim ddatabase As String = yourdatabase
Private dragrect As Rectangle
Private dragindex, dropindex As Integer

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

    Dim conn As New NpgsqlConnection(String.Format( _
            "Server={0};Port={1};User Id={2};Password={3};Database={4};", _
             dServer, dPort, dUser, dPass, ddatabase))

    conn.Open()
    Using t As NpgsqlTransaction = conn.BeginTransaction()
        Using cmd As New NpgsqlCommand( _
            "DROP TABLE IF EXISTS kalksad1;", conn)
            cmd.ExecuteNonQuery()
        End Using

        Using cmd As New NpgsqlCommand( _
            "CREATE TABLE kalksad1(" & _
            "kalk_id     int PRIMARY KEY, " & _
            "brkalk      integer, " & _
            "brred       integer, " & _
            "description text);", conn)
            cmd.ExecuteScalar()
        End Using

        Using cmd As New NpgsqlCommand( _
            "INSERT INTO kalksad1 VALUES" & _
            "(12, 2, 5, 'text index 12 doc 2 row 5'), " & _
            "(26, 2, 1, 'text index 26 doc 2 row 1'), " & _
            "(30, 2, 2, 'text index 30 doc 2 row 2'), " & _
            "(32, 4, 1, 'text index 32 doc 4 row 1'), " & _
            "(36, 1, 1, 'text index 36 doc 1 row 1'), " & _
            "(37, 1, 2, 'text index 37 doc 1 row 2'), " & _
            "(38, 5, 1, 'text index 38 doc 5 row 1'), " & _
            "(39, 5, 2, 'text index 39 doc 5 row 2'), " & _
            "(42, 2, 3, 'text index 42 doc 2 row 3'), " & _
            "(43, 2, 4, 'text index 43 doc 2 row 4'), " & _
            "(46, 3, 1, 'text index 46 doc 3 row 1'), " & _
            "(47, 3, 2, 'text index 47 doc 3 row 2');", conn)
            cmd.ExecuteNonQuery()
        End Using
        t.Commit()
    End Using

    With DataGridView1
        .AllowDrop = True
        .MultiSelect = False
        .Dock = DockStyle.Fill
        .SelectionMode = DataGridViewSelectionMode.FullRowSelect
        .AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells
        .Columns.Add("col1", "ID")
        .Columns.Add("col2", "Doc")
        .Columns.Add("col3", "Row")
        .Columns.Add("col4", "Description")
    End With

    FillData(0)
End Sub

Private Sub FillData(ByVal dropindex As Integer)

    DataGridView1.Rows.Clear()

    Try
        Using mCon As New NpgsqlConnection(String.Format( _
                      "Server={0};Port={1};User Id={2};Password={3};Database={4};", _
                      dServer, dPort, dUser, dPass, ddatabase))

            mCon.Open()
            Using mCmd = New NpgsqlCommand( _
                      "SELECT kalk_id, brkalk, brred, description " & _
                      "FROM kalksad1 " & _
                      "WHERE brkalk='2' ORDER BY brred", mCon)

                Using reader As NpgsqlDataReader = mCmd.ExecuteReader()
                    While (reader.Read())
                        DataGridView1.Rows.Add(New String() _
                        {CStr(reader("kalk_id")), _
                         CStr(reader("brkalk")), _
                         CStr(reader("brred")), _
                         CStr(reader("description"))})
                    End While
                End Using
            End Using
        End Using
    Catch ex As Exception
        Debug.Print(ex.Message)
    End Try

    ''selecting a row
    If dropindex < 0 Then dropindex = 0
    With DataGridView1
        .Rows(dropindex).Selected = True
        .CurrentCell = .Item(0, dropindex)
    End With
End Sub

#Region "dragdrop"
Private Sub DataGridView1_DragDrop(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles DataGridView1.DragDrop
    Dim p As Point = Me.PointToClient(New Point(e.X, e.Y))
    dropindex = DataGridView1.HitTest(p.X, p.Y).RowIndex

    If (e.Effect = DragDropEffects.Move) Then
        Dim dragRow As DataGridViewRow = CType(e.Data.GetData(GetType(DataGridViewRow)), DataGridViewRow)

        Dim _from As Integer = dragindex + 1 ''grid is zero based, document is 1 based
        Dim _to As Integer = dropindex + 1

        Dim updown As String = ""
        If _from < _to Then    ''correction for up
            _to = _to + 1
            updown = "!"
        End If

        '' PROCEDURE HERE -----------------------------------------------------------------
        Dim affected As Integer = 0
        Try
            Using conn As New NpgsqlConnection(String.Format( _
                          "Server={0};Port={1};User Id={2};Password={3};Database={4};", _
                          dServer, dPort, dUser, dPass, ddatabase))

                conn.Open()
                Using t As NpgsqlTransaction = conn.BeginTransaction()
                    Using cmd As New NpgsqlCommand( _
                          "UPDATE kalksad1 SET brred=_brred " & _
                              "FROM (" & _
                              "  SELECT " & _
                              "    row_number() OVER (" & _
                              "      ORDER BY brred<" & _to.ToString & " DESC, brred" & updown & "=" & _from.ToString & " DESC, brred>=" & _to.ToString & " 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;", conn)

                        affected = CInt(cmd.ExecuteNonQuery())
                    End Using
                    If affected > 0 Then t.Commit()
                End Using
            End Using
        Catch ex As Exception
            Debug.Print(ex.Message)
        End Try
        ''---------------------------------------------------------------------------------
        FillData(dropindex) ''clear, fill and select dropped row
    End If
End Sub

Private Sub DataGridView1_DragOver(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles DataGridView1.DragOver
    e.Effect = DragDropEffects.Move
End Sub

Private Sub DataGridView1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles DataGridView1.MouseDown

    dragindex = DataGridView1.HitTest(e.X, e.Y).RowIndex
    If dragindex > -1 Then
        Dim dragSize As Size = SystemInformation.DragSize
        dragrect = New Rectangle(New Point(CInt(e.X - (dragSize.Width / 2)), CInt(e.Y - (dragSize.Height / 2))), dragSize)
    Else
        dragrect = Rectangle.Empty
    End If
End Sub

Private Sub DataGridView1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles DataGridView1.MouseMove

    If (e.Button And MouseButtons.Left) = MouseButtons.Left Then
        If (dragrect <> Rectangle.Empty AndAlso Not dragrect.Contains(e.X, e.Y)) Then
            Me.DoDragDrop(DataGridView1.Rows(dragindex), DragDropEffects.Move)
        End If
    End If
End Sub
#End Region

End Class
4

2 回答 2

1

这通过一组触发器+相关函数处理选项卡顺序。

  • 更新brred特定brkalk值将导致旧值和新值之间的所有值向上或向下旋转。
  • DELETE 将导致所有brred高于 OLD 值(对于相同brkalk)的值递减(下移
  • INSERT 将导致所有brred高于 NEW 值(相同brkalk)的值递增(上移

为了避免永远递归更新,每行需要一个额外的信息位flipflag:(它应该只被触发器触及,而不是应用程序代码。它可以通过视图对应用程序隐藏)

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;

结果:

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)
于 2013-10-19T14:07:59.510 回答
1

示例 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;

示例 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;

如果您有唯一索引,(brkalk,brred)那么它会更复杂,因为在重新编号期间会有重复brred


但是对于许多行,我建议使用在 8 位计算机上的 BASIC 语言时代非常有用的东西 - 用间隙编号你的行。

所以而不是:

(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'),

利用:

(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'),

那么您的示例将如下所示:

  • 示例 1: update kalksad1 set brred=(2048+1024)/2 where kalk_id=43,这会将其更改为:

    (26, 2, 1024, '文本索引 26 doc 2 row 1'),
    (43, 2, 1536, '文本索引 43 doc 2 第 4 行'),
    (30, 2, 2048, '文本索引 30 doc 2 row 2'),
    (42, 2, 3072, '文本索引 42 doc 2 第 3 行'),
    (12, 2, 5120, '文本索引 12 doc 2 第 5 行'),
    

  • 示例 2: update kalksad1 set brred=(4096+3072)/2 where kalk_id=43,这会将其更改为:

    (30, 2, 2048, '文本索引 30 doc 2 row 2'),
    (42, 2, 3072, '文本索引 42 doc 2 第 3 行'),
    (26, 2, 3584, '文本索引 26 doc 2 第 1 行'),
    (43, 2, 4096, '文本索引 43 doc 2 第 4 行'),
    (12, 2, 5120, '文本索引 12 doc 2 第 5 行'),
    

    只有当目标应该在的行之间没有间隙时,您才需要首先使用例如重新编号行:

    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;
    

    这比更改源和目​​标之间的每一行要快得多。但这仅在可能有很多行需要更改时才有意义。

  • 于 2013-10-18T22:13:38.177 回答