【问题标题】:PostgreSQL, drag and swapPostgreSQL,拖放交换
【发布时间】:2013-10-18 21:00:46
【问题描述】:

我已经成功解决了关于按值交换行的问题here 仍然有效。
但是通过使用此功能,我发现在拖放行方面缺乏一些功能。
我尝试获得解决方案here,其中讨论出错并且提供的解决方案不充分,因为他们需要额外的专栏。

这是众所周知的表:

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');

操作对象是在相同的“brkalk”(doc) 下重新排序“brred”(row) 列的值。
设“brkalk”为 2。

现在我想根据拖放需求进行重新排序/交换,其中仅交换一行看起来不自然。我已将数据网格绑定到 kalksad1 表,因此我将描述在我的数据网格中填充查询“... ORDERED by brred”的情况。

如果我可以用这样的词来解释查询...
示例 1:
在 doc 2 下,我将拖动第 4 行并将其放到第 2 行的位置。
为此需要以下步骤:
1) 记住第 4 行的数据。
2) 在第 3 行中,将“brred”的值从 3 替换为 4。
3) 在第 2 行中,将“brred”的值从 2 替换为 3。
4)在步骤1的记忆数据中)将“brred”的值从4更改为2。

示例 2:
在 doc 2 下,我将拖动第 1 行并将其放到位置 3。
可以这样:
1) 记住第 1 行的数据。
2) 在第 2 行中,将“brred”的值从 2 替换为 1。
3) 在第 3 行中,将“brred”的值从 3 替换为 2。
4) 在步骤 1 的记忆数据中。将“brred”的值从 1 更改为 3。

这可能来自带有交换和 SO 问题的优雅解决方案 like thisthisthis。我根据自己的想法做例子,但如果有更好的,就不应该那样做。

如果有人可以以类似于用户 Roman Pekar 的swapping 的方式编写所描述的查询,请。

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

【问题讨论】:

  • 那么,基本上你想旋转brred的值对于一定范围的行?
  • 我不知道“旋转”。基本上我想 TAKE,MAKE PLACE 然后插入到这个地方,我不知道解释更准确......是的,所有操作都应该只针对 brred 的值。
  • 是的,在 _from 和 _to 之间的位置值应该重新编号。如果您可以使用 NET 和 Npgsql,这里是可行的示例。它适用于向下拖动。
  • 好吧,您可以将预期结果添加到您的问题中。我无法阅读您的脚本语言,而且看起来相当冗长和程序化。如果您真的想旋转,我认为我的翻转旗触发器(在您的第二个链接中)可以适应您的需求。
  • 嗯,它部分是某种旋转加上一点点。这个例子对我来说有点复杂,因为我从不使用触发器和函数。但是这个可以适应在两个方向上正常工作。在最坏的情况下,我可以制作条件代码...感谢您对 PostgreSQL 的帮助!

标签: postgresql


【解决方案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;
    

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

  • 【讨论】:

    • 嗨,Tometzky,由于很多原因,这确实是不可接受的。希望强大的 PostgreSQL 中存在更好(更优雅)的解决方案。
    • 如你所愿 - 我添加了一个没有间隙的解决方案。
    • WOW Tometzky,这似乎真的按预期工作。谢谢!我尝试通过 PGAdmin。明天我将尝试将其应用于真实数据和程序。 example1 和 example2 之间几乎没有区别。可以将相同的查询用于向上拖放和向下拖放吗?
    • 您应该创建一个可以执行此操作的函数。
    • 尚未尝试使用真实数据,但在这里我添加了 VB.NET 代码以查看这些示例表。在向下拖动但不正确向上拖动时工作出色。也许这可以修复?为了尝试 Npgsql 应该可以访问,Form1 应该包含一个 datagridview。
    【解决方案2】:

    这通过一组触发器+相关函数来处理跳转顺序。

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

    为了避免永远递归更新,每行需要额外的一位信息: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)
    

    【讨论】:

    • 你为什么在一些不是比特的东西上调用“交替比特协议”?我确实需要插入和删除选项,但不需要触发器和附加列,因为我有很多表我应该添加它并进一步照顾它。你能把你的代码改进成类似于 Tometzky 的代码,这样我就可以通过 .NET 合理轻松地使用它了吗?
    • 我无法改进代码,因为它已经很完美了 ;-) 在 .net 中执行此操作只需要更新部分 (UPDATE kalksad1 SET brred = 2 WHERE brkalk = 2 AND brred = 4;) 以及重新获取结果表。 (这可能比您的 row_at_a_time 处理成本更少
    • 处理时间成本更低?
    猜你喜欢
    • 2016-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-26
    • 2019-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多