Eu tenho uma tabela que requer armazenar meu item em uma posição específica, e o usuário pode "mover" as posições dos itens para "dar espaço" para o novo item.
Aqui está a aparência da mesa
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);
Eu gostaria de poder inserir uma chave na posição 1 ou mover a chave position
de uma ( UNIQUE
), e isso incrementa automaticamente o position
inteiro em 1 para cada valor maior que o novo INSERT
.
Aqui está o que eu tentei até agora
UPDATE keys
SET position = position + 1
WHERE context = 'ctx_A' AND position >= 2;
ERRO: valor de chave duplicado viola restrição exclusiva "keys_context_position_key" DETALHE: A chave (context, "position")=(ctx_A, 3) já existe.
Mas não está funcionando.
EDITAR
Estou usando uma imagem do Dockerpostgres:12.7
Eu descobri que usar UNIQUE (context, position) DEFERRABLE
me permite executar
UPDATE keys SET position = position + 1 WHERE context = 'ctx_A' AND position > 1;
Mas algo como
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;
ainda não está funcionando!
Primeiro, responderei à pergunta conforme solicitado , depois sugiro algumas melhorias como vejo e, em seguida, sugiro que você revise completamente seu esquema! Todo o código abaixo está disponível no violino aqui . Eu também incluí um novo violino separado para uma estrutura "simplificada" sem
PRIMARY KEY
atualização - ele faz uso deGENERATED
colunas como uma possível estratégia de simplificação - veja aqui .Pergunta como feita:
Alguns pontos a serem observados:
Mudei a coluna chamada "position" para "pos" porque a palavra 'position' é uma palavra- chave do PostgreSQL.
Eu coloquei uma coluna chamada "marcador" como uma coluna fictícia para que eu pudesse acompanhar minhas modificações - você pode, é claro, removê-lo - mas ajuda ter um campo prontamente identificável para teste.
Então eu preenchi da seguinte forma:
Então, corri:
e, em seguida, execute (check)
SELECT * FROM keys ORDER BY key_name;
.Resultado:
Então, o novo registro está lá e nós "aumentamos" a
INTEGER
parte dokey_name
- ou seja, oPRIMARY KEY
foi modificado e a ordem foi preservada.Você também pode fazer isso - mais flexível porque a única coisa que o SQL não faz bem é a manipulação de strings - as strings são (talvez "were" seria melhor ...) consideradas "atômicas" e dividindo-as e reunindo-as novamente não é o forte do SQL ! Dito isto, nos últimos anos, houve grandes melhorias com bibliotecas de expressões regulares e similares.
Assim, poderíamos, por exemplo:
e preenchê-lo:
E verifique com
SELECT * FROM keys_bis;
- resultado:Em seguida, executamos a atualização:
Isso é executado com sucesso - e verificamos -
SELECT * FROM keys_bis ORDER BY key_alpha, key_num;
- resultado:Assim, podemos ver que a
INTEGER
parte doPRIMARY KEY
foi incrementada e "aumentada" - podemos ver, por exemplo, que"marker 4 initial"
agora tem5
como parte numérica doPRIMARY KEY
.Mudança de esquema:
Você realmente precisa revisar seu esquema - coloque uma
INTEGER
chave substituta. Atualizar partes de umPRIMARY KEY
tem um "cheiro de código ruim" - você pode ver isso nas contorções que precisam ser feitas para atualizar o arquivoPK
. Eu sugeriria fortemente que, mesmo que fosse maisPK
fácilUPDATE
, não é algo que você deveria fazer regularmente - se for uma integração única com outro sistema, tudo bem, mas no dia-a-dia - você precisa procurar outro solução!Eu sei que você pode estar em uma situação onde
I have to interface with a legacy system
ouI'm only the consultant and not allowed to make changes...
.Por que você não usa
context
epos
como oPK
? Ambos já sãoNOT NULL
e ambos juntos sãoUNIQUE
- portanto, bons candidatos para umPK
!Aqui estão alguns pedaços de código que podem ajudar se você quiser / puder alterar a tabela. Você pode criar uma nova coluna e
UPDATE
ouINSERT
em uma nova tabela usando algumas/todas as funções abaixo (a partir daqui e veja a parte inferior do violino ):Resultado:
É melhor evitar expressões regulares, se possível - elas consomem muitos recursos!
Por que isso é um pesadelo!
To perform an
UPDATE
in the middle of the table, you have to manually keep track of all of the previousUPDATE
s so you know which parts of thePK
toUPDATE
- as follows:See fiddle for result (at bottom).
It will work, as long as you remember to set:
and
The
pos
value in theUPDATE
clause to 1 less than the new value ofpos
in theWHERE
clause! A good candidate for a function therefore? You have a function in your own answer,but you're doing this manually here:If you're going to use a function, then at least set it in the parameters to the function. What happens if you have 500 values (or 5,000 or...)?
A simplification:
I did a separate fiddle for this part - see here.
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:Isso pode ser "útil" e tornar a vida um pouco mais fácil sob certas circunstâncias - apenas um pensamento. À+ et +1 pour un question intéressante qui m'a fait réflechir!
descobri a solução
aqui está um script sql que mostra como funciona
RESULTADO:
ps: eu coloquei em um gist público do github