PostgreSQL,删除具有重新编号列值的行

Posted

技术标签:

【中文标题】PostgreSQL,删除具有重新编号列值的行【英文标题】:PostgreSQL, delete a row with renumbering column values 【发布时间】:2013-11-28 11:40:26 【问题描述】:

Here 和 here 是一些与此类似的问题,但不太相同,我可以将它们应用于我的解决方案。

我需要使用单个查询在文档中删除一行并重新编号行(从删除的行开始)。 我得到一个答案here,它不在单个查询中,因此不适合简单使用。

这是我的示例表,其中包含所有文档的所有行。 工作文件将是编号为 2 的“brkalk”。 例如,我们将删除第 'brred' 行 3。

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

当然我可以用 SQL 轻松删除一行:

DELETE FROM kalksad1 WHERE brkalk=2 AND brred=3

但是为了我的程序的良好功能,需要在一个文档内的行编号中没有“漏洞”。

删除后,“brred”列中的行编号为 1、2、4、5。

我想在删除第 3 行后立即对第 4 行和第 3 行和第 5 到 4 行重新编号(实际上是在“brred”列中重新编号),如果可能的话,在单个 SQL 命令中进行查询,以便我可以将其应用于所有我的文档并为此类删除创建函数。

因为像swap and renumber 这样更复杂的事情是可能的,我认为也有可能。

如果有人可以开发描述的查询,请。

【问题讨论】:

不可能在一条 SQL 语句中做你想做的事。但这可能在单个事务或存储过程中是可能的。但是,通常,这种要求表明您需要重新设计表格。 (另外,您需要对 brkalk, brred 的唯一约束吗?) 嗨,迈克,我不知道我是否说得好“单一 SQL 语句”,但我给出了我们之前解决的示例,哪些是功能性的(交换和重新编号)所以我想“单一事务”会没事。不幸的是,现在我无法重新设计表格。由于客户端程序这样做,因此不需要关注唯一约束。只是为了重新编号文档“brkalk”中的“brred”列。 【参考方案1】:

如果您准备使用 PostgreSQL 扩展(如可写common table expressions)来嵌套多个语句,则可以使用单个语句执行此操作。

事实上,甚至不需要 CTE。 http://sqlfiddle.com/#!15/6f9c0/11 。尽管您可以将全部内容塞进一个(非常丑陋的)声明中,例如:

WITH deleted AS (
    DELETE FROM kalksad1 WHERE brkalk=2 AND brred=3
    RETURNING kalk_id, brkalk
)
UPDATE kalksad1
SET brred = k.brred_new
FROM
(
  SELECT
      k2.kalk_id, 
      row_number() OVER (PARTITION BY k2.brkalk ORDER BY k2.brred) AS brred_new
  FROM (
    SELECT k3.kalk_id, k3.brkalk, k3.brred
    FROM kalksad1 k3
    INNER JOIN deleted ON (k3.brkalk = deleted.brkalk)
    FOR UPDATE
  ) k2
) k
WHERE kalksad1.kalk_id = k.kalk_id
RETURNING kalksad1.kalk_id, kalksad1.brkalk, kalksad1.brred;

这样做非常低效,看看the query plan 就会告诉你。适当的索引会有所帮助,但仍会涉及多个表扫描。

虽然FOR UPDATE 子查询应该防止并发DELETE,但在一个语句中完成所有操作也不会解决所有并发问题。在运行语句之前LOCK TABLE kalksad1 IN EXCLUSIVE MODE 是最安全的,以防止并发插入/更新/删除。

需要这样做的设计通常被认为是相当糟糕的。您通常应该仅在表格中进行排序,并根据需要生成编号序列,例如:

SELECT x, row_number() OVER (ORDER BY x) FROM my_table;

而不是期望物理顺序是一致的。如果你必须有这样的排序,你应该用物化视图来做,这样你就可以用数据生成一个新的边表,而不是就地更新原始数据。

【讨论】:

嗨,克雷格,现在我需要一些时间来应用它。 丑吗?我不会这么说的!我见过的最好的和功能齐全的查询之一。一站式解决方案。我会记住锁定。非常感谢。【参考方案2】:

如果你真的需要重新编号这些项目,你可以使用这样的东西:

with renumber as (
  select kalk_id
         brred, 
         row_number() over (partitioin by brkalk order by brred) as rn
  from kalksad1
)
update kalksad1
  set brred = r.rn
from renumber r
where kalksad1.kalk_id = r.kalk_id;

正如 Craig 所指出的,没有必要在单个语句中执行此操作。只需将delete 和以上内容放在一个事务中即可。

【讨论】:

嗯。除了子查询与 CTE 措辞之外,完全相同的解决方案。 @CraigRinger:当我写的时候,你的答案中没有更新 - 否则我不会有。 是的,我编辑的时候你的还没有发布。有时候就是这样。 非常感谢“no_name”。那保证,我需要一些时间在 NET 环境中应用您的解决方案,然后我会参考情况。

以上是关于PostgreSQL,删除具有重新编号列值的行的主要内容,如果未能解决你的问题,请参考以下文章

如何提取具有非零列值的行?

连接具有相同值的行的列值(不同列的)

使用名为查询的数据 jpa 返回具有不同列值的行

根据另一列的字段值选择具有相同列值的行

如何在MySql中返回具有相同列值的行

在 SQL 中选择具有唯一列值的行