添加行时在列中重新编号 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 > new_number_in_route
。
【参考方案1】:
根据要求,当您向路线添加新停靠点时,您需要将它们正确插入到所需的顺序中,并将所有现有停靠点从该点向前推,以保持连续的顺序。当您插入一行时,这并不难(只是number_in_route + 1 where number_in_route > 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的主要内容,如果未能解决你的问题,请参考以下文章