我有一张桌子,需要将我的物品存储在特定位置,用户可以“移动”物品的位置以为新物品“腾出空间”。
这是桌子的样子
CREATE TABLE keys (
key_name VARCHAR(128) UNIQUE NOT NULL PRIMARY KEY,
context VARCHAR(128) NOT NULL,
position INTEGER NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (context, position)
);
INSERT INTO keys (key_name, context, position)
VALUES
('A.1', 'ctx_A', 0),
('A.2', 'ctx_A', 1),
('A.3', 'ctx_A', 2),
('A.4', 'ctx_A', 3),
('B.1', 'ctx_B', 0),
('B.2', 'ctx_B', 1),
('B.3', 'ctx_B', 2),
('B.4', 'ctx_B', 3);
我希望能够在位置 1 插入一个键或移动position
一个 ( UNIQUE
) 键,并且position
对于每个大于 new 的值,它都会自动将整数增加 1 INSERT
。
这是我到目前为止尝试过的
UPDATE keys
SET position = position + 1
WHERE context = 'ctx_A' AND position >= 2;
错误:重复的键值违反了唯一约束“keys_context_position_key”详细信息:键(上下文,“位置”)=(ctx_A,3)已经存在。
但它不起作用。
编辑
我正在使用 Docker 映像postgres:12.7
我发现使用UNIQUE (context, position) DEFERRABLE
允许我执行
UPDATE keys SET position = position + 1 WHERE context = 'ctx_A' AND position > 1;
但是有点像
BEGIN;
UPDATE keys SET position=2 WHERE context='ctx_A' AND key_name='A.4';
UPDATE keys SET position=3 WHERE context='ctx_A' AND key_name='A.3';
COMMIT;
仍然无法正常工作!
首先,我将按要求回答问题,然后根据我的看法提出一些改进建议,然后建议您完全修改您的架构!下面的所有代码都可以在这里找到。我还为没有
PRIMARY KEY
更新的“简化”框架添加了一个新的单独小提琴 - 它使用GENERATED
列作为可能的简化策略 - 请参见此处。问的问题:
有几点需要注意:
我将名为“position”的列更改为“pos”,因为“position”这个词是 PostgreSQL 的关键字。
我在一个名为“marker”的列中放入了一个虚拟列,这样我就可以跟踪我的修改——当然,你可以删除它——但它有助于有一个易于识别的测试字段。
然后我按如下方式填充它:
然后,我跑了:
然后运行(检查)
SELECT * FROM keys ORDER BY key_name;
。结果:
因此,新记录就在那里,我们已经“提升”了
INTEGER
部分key_name
-PRIMARY KEY
即已修改并保留了顺序。您也可以这样做 - 更灵活,因为 SQL 做得不好的一件事是字符串操作 - 字符串(也许“曾经”会更好......)被认为是“原子的”并将它们拆分并重新组合在一起不是 SQL 的强项!话虽如此,近年来,正则表达式库等已经有了很大的改进。
因此,我们可以,例如:
并填充它:
并检查
SELECT * FROM keys_bis;
- 结果:然后我们运行更新:
这成功运行 - 我们检查 -
SELECT * FROM keys_bis ORDER BY key_alpha, key_num;
- 结果:因此,我们可以看到 的
INTEGER
部分PRIMARY KEY
已经增加并“向上” - 例如,我们可以看到,"marker 4 initial"
现在5
作为 . 的数字部分PRIMARY KEY
。架构更改:
你真的需要修改你的模式 - 放入一个
INTEGER
代理键。更新 a 的一部分PRIMARY KEY
有一种“不好的代码味道” ——你可以在更新PK
. 我强烈建议即使更容易,这也不是您应该定期做的事情-如果它是与另一个系统的一次性集成,很好,但是日常-您需要搜索另一个解决方案!PK
UPDATE
我知道您可能处于
I have to interface with a legacy system
或的情况I'm only the consultant and not allowed to make changes...
。你为什么不使用
context
和pos
作为PK
?两者都已经是NOT NULL
并且两者都在一起UNIQUE
- 因此是 a 的好候选人PK
!如果您想/可以更改表格,这里有几段代码可能会有所帮助。您可以使用以下部分/全部功能创建一个新列
UPDATE
或创建一个INSERT
新表(从这里查看小提琴底部):结果:
如果可能的话,你最好避免使用正则表达式——它们是资源密集型的!
为什么这是一场噩梦!
要在表格中间执行 an
UPDATE
,您必须手动跟踪所有前面的 s,以便知道toUPDATE
的哪些部分- 如下所示:PK
UPDATE
请参阅小提琴的结果(底部)。
只要您记得设置,它将起作用:
和
子句中的
pos
值比子句中UPDATE
的新值小1 !因此,一个很好的函数候选者?您在自己的答案中有一个功能,但您在这里手动执行此操作:pos
WHERE
如果你要使用一个函数,那么至少在函数的参数中设置它。如果您有 500 个值(或 5,000 或...)会怎样?
简化:
我为这部分做了一个单独的小提琴 - 见这里。
I reread the question and noticed that it wasn't in fact the
PK
that requiredUPDATE
ing - it was theUNIQUE
constraint - the principles however are very similar. For the purposes of a complete explanation of how I would go about this, and making use of an extremely useful feature of all of the major RDBMSs - that isGENERATED
fields/columns!GENERATED
is the SQL Standard name.These also go by the name
COMPUTED
,CALCULATED
,PERSISTED
,DERIVED
or evenVIRTUAL
- although theVIRTUAL
(best avoided) keyword is also (confusingly) used for the storage class of the column (STORED
orVIRTUAL
) - the first meaning that the value is actually stored on disk and the second meaning that it's calculated on the fly. As of the time of writing (28/10/2021), PostgreSQL doesn't supportVIRTUAL
columns.I created a new table as follows:
Populate as for the original keys table - then
SELECT * FROM keys_ter ORDER BY key_alpha, key_num;
- result:We can see the
GENERATED
fields - this makes things slightly easier whenUPDATING
if we happen to know theINTEGER
part of thePK
as follows:Update successful - we check
SELECT * FROM keys_ter ORDER BY context, pos;
- result:这可能是“方便”的,并且在某些情况下让生活更轻松 - 只是一个想法。À+ et +1 pour un question intéressante qui m'a fait réflechir!
我找到了解决方案
这是一个 sql 脚本,显示它是如何工作的
输出:
ps:我把它放在一个公共的github gist