添加行时在列中重新编号 sql

Posted

技术标签:

【中文标题】添加行时在列中重新编号 sql【英文标题】:renumbering in a column when adding a row sql 【发布时间】:2021-12-23 00:20:31 【问题描述】:

对于像这样的表

create table Stations_in_route
(
ID_station_in_route int primary key,
ID_route int,
ID_station int,
Number_in_route int not null
)

有以下触发器在向路由添加新行后更改 Number_in_route 列中的值。路线中的号码列表必须保持一致。

create trigger stations_in_route_after_insert on Stations_in_route
after insert
as

if exists
(select *from Stations_in_route
where Stations_in_route.ID_station_in_route not in (select ID_station_in_route from inserted)
and Stations_in_route.ID_route in (select ID_route from inserted) 
and Stations_in_route.Number_in_route in (select Number_in_route from inserted))

begin
update Stations_in_route
set Number_in_route = Number_in_route + 1
where Stations_in_route.ID_station_in_route not in (select ID_station_in_route from inserted)
and Stations_in_route.ID_route in (select ID_route from inserted) 
and Stations_in_route.Number_in_route >= (select Number_in_route from inserted where Stations_in_route.ID_route = inserted.ID_route)
end

如果执行插入到一个 ID_route 中,此触发器将引发错误:

子查询返回超过 1 个值。当子查询跟随 =、!=、、>= 或子查询用作表达式时,这是不允许的。

例如,

Insert into Stations_in_route values(25, 4, 11, 3),(26, 4, 10, 5)

如何解决?

ID_station_in_route ID_route ID_station Number_in_route
1 4 1 1
2 4 2 2
3 4 3 3
4 4 4 4
5 4 5 5
6 4 6 6
7 4 7 7
8 4 8 8

我希望添加后的列表会变成这样

ID_station_in_route ID_route ID_station Number_in_route
1 4 1 1
2 4 2 2
25 4 11 3
3 4 3 4
26 4 10 5
4 4 4 6
5 4 5 7
6 4 6 8
7 4 7 9
8 4 8 10

这不是整张桌子,因为还有其他路线

【问题讨论】:

“这个触发器会抛出一个错误”,错误是什么?如果我运行上述内容,我不会收到任何错误:db<>fiddle @Larnu 子查询返回超过 1 个值。当子查询跟随 =、!=、、>= 或子查询用作表达式时,这是不允许的。即添加到同一条路线时它不起作用 那是由于触发器中WHERE 中的最后一个子句;该查询只能返回一个标量值。然而,你给我们的 SQL 并没有产生那个错误。 @AaronBertrand 我添加了一个示例。我认为我将停止添加重复项。我将回滚更新。 仍然不清楚如果两个新行都被赋予相同的位置(例如 3)会发生什么,是否有其他因素迫使其中一个获胜?此外,一般来说,如果您将插入限制为单行,那么它可能更有意义,并且对于更简单得多的触发器,那么逻辑就是 number_in_route + 1 where number_in_route &gt; new_number_in_route 【参考方案1】:

根据要求,当您向路线添加新停靠点时,您需要将它们正确插入到所需的顺序中,并将所有现有停靠点从该点向前推,以保持连续的顺序。当您插入一行时,这并不难(只是number_in_route + 1 where number_in_route &gt; new_number_in_route),但是当您插入更多行时,您基本上需要为每个 新行将整个后续停靠点集推1。为了说明,假设你从这个开始:

如果我们插入两个新行,比如:

INSERT dbo.Stations_in_route
(
  ID_station_in_route,
  ID_route,
  ID_station,
  Number_in_route
)
VALUES (25, 4, 11, 3),(26, 4, 10, 5);
-- add a stop at 3 ^              ^
----------------- add a stop at 5 ^

我们可以通过将其减慢为单独的步骤来说明这一点。首先,我们需要在 #3 位置添加这一行:

我们通过将所有 > 3 的行向下推 1 来做到这一点:

但是现在当我们在位置 #5 添加这一行时:

那是 new 位置 #5, 上一个班次之后,所以它看起来像这样:

我们可以使用下面的触发器来做到这一点,这可能比它必须的要复杂一些,但恕我直言,它比可能需要的繁琐循环更好。

CREATE TRIGGER dbo.tr_ins_Stations_in_route ON dbo.Stations_in_route
FOR INSERT AS
BEGIN
    ;WITH x AS 
    (
      SELECT priority = 1, *, offset = ROW_NUMBER() OVER 
        (PARTITION BY ID_route ORDER BY Number_in_route)
      FROM inserted AS i
      UNION ALL 
      SELECT priority = 2, s.*, offset = NULL FROM dbo.Stations_in_route AS s
        WHERE s.ID_route IN (SELECT ID_route FROM inserted)
    ), 
    y AS 
    (
      SELECT *, rough_rank = Number_in_route 
          + COALESCE(MAX(offset) OVER (PARTITION BY ID_Route 
            ORDER BY Number_in_route ROWS UNBOUNDED PRECEDING),0) 
          - COALESCE(offset, 0),
        tie_break = ROW_NUMBER() OVER 
          (PARTITION BY ID_route, ID_station_in_route ORDER BY priority)
      FROM x
    ),
    z AS 
    (
      SELECT *, new_Number_in_route = ROW_NUMBER() OVER 
        (PARTITION BY ID_Route ORDER BY rough_rank, priority) 
      FROM y WHERE tie_break = 1
    )
    UPDATE s SET s.Number_in_route = z.new_Number_in_route
      FROM dbo.Stations_in_route AS s
      INNER JOIN z ON s.ID_route = z.ID_route
        AND s.ID_station_in_route = z.ID_station_in_route;
END
工作示例db<>fiddle

我曾多次提到您可能想要处理新行的关系,例如如果插入恰好是:

Insert into Stations_in_route values(25, 4, 11, 3),(26, 4, 10, 3)

为此,您可以在此子句中添加额外的平局标准:

  new_Number_in_route = ROW_NUMBER() OVER 
    (PARTITION BY ID_Route ORDER BY rough_rank, priority) 

例如:

  new_Number_in_route = ROW_NUMBER() OVER 
    (PARTITION BY ID_Route ORDER BY rough_rank, priority, 
     ID_station_in_route DESC) 

【讨论】:

使用这样的触发器,我有一个数字为 6 的重复行(加上它上升到第 6 位而不是 5)并且插入的记录必须适合正确的位置,同时必须保留列表中的顺序 @1i10 请再试一次。 谢谢,您的解决方案按预期工作。但是我需要一段时间才能理解它。看起来很复杂) @1i10 是的,因为您的要求很复杂。 :-) 老实说,就像我在上面所说的那样,如果您一次强制一个新站点,那会简单得多。【参考方案2】:

我无法使用问题中的测试代码/数据重现异常,但是我猜想问题出在触发器中的这段代码上:

AND Stations_in_route.Number_in_route >= 
(
SELECT Number_in_route
FROM inserted
WHERE Stations_in_route.ID_route = inserted.ID_route
)

那里的引擎会隐含地期望 >= 运算符右侧的子查询返回标量结果(单行单列结果),但是插入的表实际上是一个表......这可能包含多条记录(如示例中所述的多行插入/更新/等类型语句中的情况)。鉴于该子查询中的过滤器(即 WHERE 子句)不能保证是唯一的(ID_route 似乎不是唯一的,并且在您的示例中,您有一个插入语句实际上插入了具有相同 ID_route 值的多行),那么查询肯定有可能返回非标量结果。

要解决此问题,您需要调整该子查询以保证结果为标量值(单行单列)。您已经使用选择器保证了单列......现在您需要添加逻辑来保证单个结果/记录。这可能包括以下一项或多项(或可能还有其他内容):

将选定的 Number_in_route 列包装在一个聚合函数中(例如,一个 MAX() 可能吗?) 添加一个带有 ORDER BY 的 TOP 1 以获得您想要比较的记录 向 WHERE 子句添加额外的过滤器以确保返回单个结果

【讨论】:

以上是关于添加行时在列中重新编号 sql的主要内容,如果未能解决你的问题,请参考以下文章

我想在列中的值中添加“%”单位

插入数据并在列中添加前一行数据

如何使用 Pandas 在列中添加值的超链接?

我不能在列中添加具有 2 个相同值的行

将重置计数器(在列的值更改时重置)添加到视图中的 PLSQL 行

excel 中如何向一列中自动添加序号啊