【问题标题】:Stored procedure to re-order rows in a table对表中的行重新排序的存储过程
【发布时间】:2013-01-18 01:15:57
【问题描述】:

我的任务是创建自定义存储过程:

exec UPDATE_PROJECT_ORDER @PROJECTID=12, @UPDATEMODE=0

这将在表中的一行被更新后执行。

不幸的是,这让我很困惑(以至于我什至不确定这是否可能)我一直在努力简化我正在做的事情,并想知道我是否可以就代码本身向董事会寻求帮助...

因此,存储过程/代码的目的是通常按顺序重新排序项目列表,重新排序会根据传入的参数更改,该参数将保留在表并围绕它排序列表的其余部分或将其重新编号为序列中的下一个数字。我想我们可以假设大部分时间 UPDATEMODE=0 将是默认值。

我有一张如下所示的表格:

---------------------------------------
|   ID   |   POSITION   |   OLD_POS   |
---------------------------------------
|   10   |     1        |             |
|   11   |     2        |             |
|   12   |     3        |             |
|   13   |     4        |             |
---------------------------------------

如果用户决定给 ID 为 12 的记录(ID 与存储过程一起传入)更高的优先级,例如 1,那么应该发生的是其他记录 (10 / 11 / 13) 应该是围绕它按顺序重新排序(2 / 3 / 4),这将影响它们在前端的显示方式,例如

---------------------------------------
|   ID   |   POSITION   |   OLD_POS   |
---------------------------------------
|   10   |     2        |      1      |
|   11   |     3        |      2      |
|   12   |     1        |      3      |
|   13   |     4        |      4      |
---------------------------------------

另一个例子是记录 ID 12 的位置从 1 更改为 7,因此这个数据集:

---------------------------------------
|   ID   |   POSITION   |   OLD_POS   |
---------------------------------------
|   10   |     2        |      1      |
|   11   |     3        |      2      |
|   12   |     1/7      |      3      |
|   13   |     4        |      4      |
---------------------------------------

位置数据重新排序为:

---------------------------------------
|   ID   |   POSITION   |   OLD_POS   |
---------------------------------------
|   10   |     1        |      2      |
|   11   |     2        |      3      |
|   12   |     7        |      1      |
|   13   |     3        |      4      |
---------------------------------------

如上所述 - 在存储过程中,需要传入另一个参数(@UPDATEMODE,可以是 0 或 1),这会改变函数的行为,允许用户指定他们想要的位置并重新- 对列表进行排序而不是使其成为序列中的下一个数字,例如,它们将第 3 行中的优先级从值 1 更新为值 7

此数据集第 12 行位置值 = 1,但更改为 7,其中 UPDATEMODE 指定为 1:

---------------------------------------
|   ID   |   POSITION   |   OLD_POS   |
---------------------------------------
|   10   |     2        |      1      |
|   11   |     3        |      2      |
|   12   |     1/7      |      3      |
|   13   |     4        |      4      |
---------------------------------------

这会将列表重新排序如下:

---------------------------------------
|   ID   |   POSITION   |   OLD_POS   |
---------------------------------------
|   10   |     1        |      2      |
|   11   |     2        |      3      |
|   12   |     4        |      1      |
|   13   |     3        |      4      |
---------------------------------------

在本例中,存储过程将被称为:

exec UPDATE_PROJECT_ORDER @PROJECTID=12, @UPDATEMODE=1

这是我一直在使用的 SQL 代码:

-- Declare variables
DECLARE @PROJECTID INTEGER
DECLARE @CURRENTPOSITION INTEGER
DECLARE @ROLLBACKPOSITION INTEGER
DECLARE @STARTPOSITION INTEGER
DECLARE @ENDPOSITION INTEGER

-- For testing hardcode a REQUEST ID
SET @PROJECTID = 12

-- Start Position value
SET @STARTPOSITION = 1

-- End Position value
SELECT @ENDPOSITION = COUNT(ID) FROM PROJECT WHERE PROJECT_ORDER IS NOT NULL

-- Update Rollback column with current value    
UPDATE PROJECT SET OLD_POS = POSITION WHERE POSITION IS NOT NULL

DECLARE cursorProjectPositionUpdate CURSOR fast_forward
FOR 
    SELECT ID, POSITION, OLD_POS
    FROM PROJECT 
    WHERE ID = @PROJECTID
    AND POSITION IS NOT NULL
OPEN cursorProjectPositionUpdate
FETCH NEXT FROM cursorProjectPositionUpdate INTO @PROJECTID, @CURRENTPOSITION, @ROLLBACKPOSITION

WHILE @@FETCH_STATUS = 0
    BEGIN   
        WHILE (@STARTPOSITION <= @ENDPOSITION)
            IF @STARTPOSITION = 1
                UPDATE PROJECT
                SET POSITION = @STARTPOSITION
                WHERE ID = @PROJECTID
                    AND OLD_POSITION = @ROLLBACKPOSITION

            ELSE
                UPDATE PROJECT
                SET POSITION = @STARTPOSITION
                WHERE OLD_POS = @ROLLBACKPOSITION
                    AND ID <> @PROJECTID
        SET @STARTPOSITION = @STARTPOSITION + 1
        FETCH NEXT FROM cursorProjectPositionUpdate INTO @PROJECTID, @CURRENTPOSITION, @ROLLBACKPOSITION
    END
CLOSE cursorProjectPositionUpdate
DEALLOCATE cursorProjectPositionUpdate

我使用了游标,因为最多可以重新排序 25 条记录的硬性限制,所以我不太担心性能。虽然表中可能有超过 25 条记录,这就是为什么我尝试使用 AND POSITION IS NOT NULL 子句排除记录的原因,所以我希望这是可以接受的。

我脑海中的想法是计算我总共有多少条记录有一个位置,然后循环将第一个设置为位置 1,然后将其余的设置为下一个顺序。

我发现的最大问题是记录匹配,即我的 WHERE 子句,因为存储过程传入​​的唯一内容是我想要设置为位置 1 的 ID,所以我怎么知道哪个接下来是一个...逻辑是它应该是列表中的下一个最低 ID 号。

这是在 MS SQL Server 中完成的。

我试图避免创建任何临时表,因此需要看看这是否可以一次性完成。

希望这对某人有某种意义,我对解决方案持开放态度,并将尽可能多地分享信息。

非常感谢!


附加

根据以下答案,我一直在考虑这个问题,我再次尝试尽可能简单地开始,所以我有存储过程,它传入了一个 PROJECT ID 值,那么如果我循环遍历我拥有的与我的项目 ID 不匹配的所有值项目到 1。我知道这不适合我的更新模式选项,但我担心这只会增加很多复杂性。

就代码而言,您对以下内容有何看法:

-- Declare variables
DECLARE @PROJECTID INTEGER
DECLARE @STARTPOSITION INTEGER
DECLARE @ENDPOSITION INTEGER

-- Hardcoded for testing
SET @PROJECTID = 25061

-- Start Position value
SET @STARTPOSITION = 2

-- End Position value
SELECT @ENDPOSITION = COUNT(ID) FROM PROJECT WHERE PROJECT_ORDER IS NOT NULL AND ID <> @PROJECTID 

-- Update Rollback column with current value    
UPDATE PROJECT SET PROJECT_ORDER_RB = PROJECT_ORDER WHERE PROJECT_ORDER IS NOT NULL

-- Loop other records
WHILE (@STARTPOSITION <= @ENDPOSITION)
    UPDATE PROJECT SET PROJECT_ORDER = @STARTPOSITION WHERE ID <> @PROJECTID 
        AND PROJECT_ORDER IS NOT NULL AND (PROJECT_ORDER = 1 OR PROJECT_ORDER => @STARTPOSITION OR PROJECT_ORDER <= @ENDPOSITION)
SET @STARTPOSITION = @STARTPOSITION + 1

-- Finally set passed in Project to Position 1
UPDATE PROJECT SET PROJECT_ORDER = 1 WHERE ID = @PROJECTID

【问题讨论】:

  • 如果需要,您可以将变量添加到存储过程吗?为了完成第二部分,我认为您需要另一个变量来指定新位置,因为默认情况下它不会是 1。因此,对于您的示例,它将是 "exec UPDATE_PROJECT_ORDER @ID=12, @POSITION=7, @UPDATEMODE=0"
  • 所以基于我得到的答案是没有额外的变量不能添加到存储过程中。对于它的第二部分,我相信我需要排除使用 SQL 更新传入的 ID,即 WHERE ID @REQUEST,所以它会跳过它,但这对我来说没有多大意义,因为你不希望重复,所以代码必须忽略传入的变量,但不能在列表中重复使用它。
  • 也许这个答案会在某种程度上有所帮助:SQL Server custom record sort in table, allowing to delete records。基本上,查询会根据参数旋转排序列中的值(并且它的参数化方式与您的要求略有不同)。
  • ... 拥有基于布尔变量做两件事的过程是邪恶的。你真的应该编写两个不同的例程,名称不同。此外,如果您想避免必须更新表中的每一行,请使用“插入的项目在现有项目之间的中间”的策略(即第一个项目位于 0,下一个位于 2 ^ 16 等)。这可能有时需要重新平衡 - 但对于 20 亿个值,这将是一段时间。
  • 所以我正在查看下面提供的答案(顺便感谢大家),我看到了一些让我对它们感到担忧的事情。在这一点上,我不能假设我有任何额外的变量可以使用,所以例如没有 startpos / endpos 加上我必须传入 UPDATEMODE ,默认情况下将为 1 这意味着提供的 ID 变为位置 1 并且一切都重新-围绕它排序,如果它为 0,那么为该 ID 设置的值将被单独保留,并且所有内容都将围绕它重新排序。我知道这并不理想,但这是我给定的范围。

标签: sql sql-server stored-procedures


【解决方案1】:

您不需要模式,您可以设置默认的新位置 = 1,因此如果您想要执行第一个模式,您只需输入 ID,如果您想要执行第二个模式,您需要指定附加变量。

Create  Proc update_project_order (@ID Int, @NewPosition Int = 1)
As

Declare @OldPosition Int,
        @Direction Int

Select  @OldPosition = Position
From    tableName
Where   ID = @ID

Set     @Direction =    Case 
                        When    @OldPosition < @NewPosition Then 0
                        When    @OldPosition > @NewPosition Then 1
                        Else    -1
                        End

Update  t
Set     Old_Pos =   Position,
        Position =  Case
                    When    ID = @ID Then @NewPosition
                    When    @Direction = 0 And 
                            Position Between @OldPosition And @NewPosition Then Position - 1
                    When    @Direction = 1 And 
                            Position Between @NewPosition And @OldPosition Then Position + 1
                    Else    Position
                    End
From    tableName t

【讨论】:

  • 这已经过测试并且可以按需要工作,它几乎是@usr 解释的实现,除了它需要考虑方向,因为您想要根据哪个来增加或减少。
  • @andrew.reynolds81 这能满足您的需求吗?我可以提供我使用的测试数据,但我认为这应该可以满足您在此表单中的需要。
  • 我现在正在查看它...它看起来很接近这是我所看到的 - 如果我的表中有 4 行按位置 1 / 2 /3 /4 排序并且我想要行3 是位置 1 我的数据被重新排序为 2 / 3 / 1 / 4 这正是我正在寻找的,但是如果我的位置数据为 4 / 3 / 2 / 1 它会被重新排序为 4 / 3 / 1 / 2 所以只有最后两个被排序。
  • 那是因为您将第三行(即位置 2)移动到位置 1。这意味着只有位置 1 会增加。根据我对您问题的理解,这是正确的。在这种情况下,您的预期结果是什么?
  • 以下位置 4 / 3 / 2 / 1 的数据集的预期结果是,当将第 3 行(值 = 2)设置为 1 时,它将被视为 2 / 3 / 1 / 4
【解决方案2】:

您似乎可以将其实现为一个两步过程:

  1. 增加位置等于或大于目标位置且小于旧位置的所有项目的位置值
  2. 更新应移动到目标位置的项目

您可以使用两个更新语句来执行此操作,如果您在其中塞入一点 CASE 逻辑,甚至可以使用一个。

【讨论】:

  • 这对我来说似乎很有意义,我试图在我原来的问题中添加一些简单的代码来做到这一点,我唯一的问题是我将如何处理我的 updatemode 场景设置为另一个选项。另一个选项不会提升到位置,而是仅围绕用户设置的值对列表进行排序,但如果我至少可以解决其中的一些问题,那将是一个加号。
【解决方案3】:

这可以通过单个更新语句来完成,不需要 OLD_POS 列或任何类似的东西,也不需要任何循环、游标或其他面向非集合的工件:

CREATE Proc UPDATE_PROJECT_ORDER 
(
    @ProjectID      As INT, 
    @StartPosition  As INT,
    @EndPosition    As INT
) As

;With
  cteProjectOld As
(
    SELECT  ID, 
            POSITION,
            Case WHEN POSITION = @StartPosition 
                THEN @EndPosition 
                ELSE POSITION       END As TempPosition,
            Case WHEN POSITION = @StartPosition 
                THEN 1 
                ELSE 0              END As Moved
    FROM    PROJECT
)
, cteProjectNew As
(
    SELECT  *,
            ROW_NUMBER() OVER(PARTITION BY ID ORDER BY TempPosition, Moved) As NewPosition
    FROM    cteProjectOld
)
UPDATE  cteProjectNew
SET     POSITION = NewPosition
WHERE   ID = @ProjectID

【讨论】:

  • 如上所述,假设我可以传入一个我没有提前知道的开始/结束位置我需要从我拥有的数据中找出它们 - 如果我没有解释这一点,请道歉非常好,但我正在努力遵守我给定的范围。
  • 如果您不知道新职位应该是什么,那么您就无法完成这项任务,就这么简单。如果您确实知道它应该是什么,那么我的回答会告诉您如何去做。
  • @andrew.reynolds81 从您的帖子中“*.. 要求.. 函数允许 [] 用户指定他们想要的位置并围绕它重新排序列表..,例如,他们将优先级 3 更新为 7”我上面的示例就是这样做的。用户在没有附加参数的情况下如何将这些信息指定给存储过程?
  • 根据存储过程的调用方式,新位置将是 1 或数据库中设置的值,但您的代码不是期望它们由存储过程传入​​吗?所以我需要添加一些额外的代码来确定 startposition 是 1 还是 db 列值并获取 endposition 可以通过表中的记录数来计算。
  • @andrew.reynolds81 您说“新位置将是 1 或数据库中设置的值”。 数据库中设置了什么值?你在说什么价值?!?您不能同时在数据库中设置值并调用存储过程。它们是两个不同的东西。
【解决方案4】:

我认为这是两种不同的可能性,要么向上移动,要么向下移动。其余的只是位置的算术运算,可以在单个更新语句中完成。

create procedure update_project_order (@ProjectId int, @UpdateMode int default 1)
begin

    declare @ProjectIdPosition int;
    select @ProjectIdPostion = Position
    from t
    where ProjectId = @ProjectId;

    if (@UpdateMode < @ProjectIdPosition)  -- Moving up the list
        update t
            set position = (case when position < @UpdateMode
                                 then position
                                 when position < @ProjectIdPosition
                                 then position + 1
                                 when ProjectId = @ProjectId
                                 then @UpdateMode
                                 when position
                            end);
    else if @UpdateMode > @ProjectIdPosition  -- Moving down the list
        update t
            set position = (case when position < @ProjectIdPostion
                                 then position
                                 when position < @UpdateMode
                                 then position - 1
                                 when ProjectId = @ProjectId
                                 then @UpdateMode
                                 else position
                            end);
end;

注意:我没有对此进行测试。这是一个关于如何实现它的想法。

【讨论】:

    【解决方案5】:
    Create Procedure UPDATE_PROJECT_ORDER (@ID int,@Posi int) as
    begin
    
    Select Cast(ID as int) as ID,POSITION,Identity(int ,1,1) as NewPos
    into #tmp
    from Project
    where POSITION<=@Posi 
    order by POSITION
    
    Select Cast(ID as int) as ID,POSITION,Identity(int ,1,1)   as NewPos
    into #tmp2
    from Project
    where POSITION>=@Posi 
    order by POSITION
    
    
    
    Update Project set Project.POSITION=#tmp.NewPos 
    from #tmp 
    where #tmp.ID=Project.ID
    
    Update Project set Project.POSITION=#tmp2.NewPos + @Posi
    from #tmp2 
    where #tmp2.ID=Project.ID
    
    
    Update Project set POSITION=@Posi where ID=@ID
    
    Drop table #tmp
    Drop table #tmp2
    end
    

    【讨论】:

    • 谢谢 - 这是使用临时表,我不能假设我有权创建,因为范围确实声明我不应该使用它们。
    • 所有用户都有权在 TempDB 中创建本地或全局临时表,因为这是通过 GUEST 用户安全上下文分配给他们的。 simple-talk.com/sql/t-sql-programming/…
    • 感谢链接中提供了很多很好的信息...但是您的解决方案不需要将位置传递给存储过程吗?那不是我可以访问的东西,因此必须使用 PROJECT ID 字段从数据库中查找新位置,但是该位置最终不会与 WHERE 子句中的值相同,这意味着列表不会重新-订购?
    • 您应该知道项目 ID 和所需的位置,如果它应该是您可以使用的第一个位置:从项目中选择 @Posi=MIN(Position)。最后一个MAX。其他任何事情都取决于您
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-02
    • 1970-01-01
    相关资源
    最近更新 更多