Eu tenho o seguinte código, que gera o erro:
ERRO: não há nenhuma restrição exclusiva correspondente às chaves fornecidas para a tabela referenciada "pessoa"
DROP TABLE IF EXISTS person;
CREATE TABLE person (
sn serial PRIMARY KEY,
id int NOT NULL,
modified timestamp,
name text
);
CREATE UNIQUE INDEX person_id_key ON person (id) WHERE modified IS NULL;
DROP TABLE IF EXISTS account;
CREATE TABLE account (
id int NOT NULL REFERENCES person(id),
name text
);
Existe um índice exclusivo para id
:
Table "public.person"
Column | Type | Collation | Nullable | Default
----------+-----------------------------+-----------+----------+------------------------------------
sn | integer | | not null | nextval('person_sn_seq'::regclass)
id | integer | | not null |
modified | timestamp without time zone | | |
name | text | | |
Indexes:
"person_pkey" PRIMARY KEY, btree (sn)
"person_id_key" UNIQUE, btree (id) WHERE modified IS NULL
Por que não é suficiente para a chave estrangeira?
Isso é meio monstro - <TL:DR> Vá para o adendo </TL:DR>
Sua pergunta:
Porque seu índice
person_id_key UNIQUE, btree (id) WHERE modified IS NULL
cobre apenas a exclusividade de uma fração dosperson.id
registros. Para que um campo sejaFOREIGN KEY
, ele deve abranger todos osperson.id
registros!Você tem que fazer as coisas de maneira um pouco diferente e ativar
CREATE
seuUNIQUE
índiceperson.id
no momento da criação da tabela eCREATE
depois seu índice parcial (e com um nome diferente, obviamente).Todo o código está no violino aqui .
O que você faz é isto:
e então
CREATE
sua tabela de contas:e quando isso for bem-sucedido, agora temos
CREATE
nosso índice parcial:e então executamos a seguinte consulta, apenas para verificar:
Resultado:
Et voilá! Você pode obter o SQL acima executando
\set ECHO_HIDDEN on
epsql
executando\di
. Definir aECHO_HIDDEN
variável significa que quando você emite umpsql
meta-comando (ou seja, não SQL), o SQL gerado por esse comando é fornecido - é uma ótima maneira de aprender sobre o PostgreSQL!Alguns pontos:
posso apenas recomendar que você use
TIMESTAMPTZ
(com fuso horário) em vez de apenasTIMESTAMP
- é apenas uma prática melhor - mesmo que você nunca planeje se mudar para um país/região diferente, ainda há questões de horário de verão (horário de verão) a serem consideradas. E, mesmo que você não more em uma área que não tenha horário de verão agora , isso poderá ocorrer no futuro.Além disso, posso sugerir que você dê nomes significativos aos seus índices, como sufixos
UNIQUE
de índices com_uq
ou similares.Isso também funciona!
Adendo - em resposta à pergunta do OP nos comentários:
Para aqueles que esqueceram, o primeiro parágrafo foi:
Eu realmente nunca pensei sobre isso profundamente até que sua pergunta me levou a investigar - e estou muito satisfeito por ter feito isso. O que descobri surpreendeu-me, mas também estou grato por ter tido uma espécie de epifania como resultado da minha análise.
Minha primeira pista veio aqui de @ErwinBrandstetter, onde ele escreveu:
Agora, observe como há uma distinção entre a
UNIQUE CONSTRAINT
e aUNIQUE INDEX
- levei mais pesquisas, violinos e mais algumas leituras para finalmente compreendê-la.Ele continua dizendo:
Então, novamente uma distinção - não tenho certeza exatamente o que ele quis dizer com "sem... tipo de índice".
A partir daqui (@EB novamente), temos:
Então, a
CONSTRAINT
tem um subjacenteINDEX
, mas não é simplesmente um índice em si - é mais!Uma pista adicional pode ser encontrada aqui :
e EB (primeiro link) diz:
Portanto, são duas diferenças documentadas entre
UNIQUE
restrições e índices.Finalmente tudo se encaixou para mim quando me deparei com esta jóia - prova que há uma grande diferença entre os dois:
Então, com relação à sua situação, modifiquei o violino original e incluí o que aprendi.
Eu entrei na toca do coelho com os violinos, então você pode ignorar tudo, exceto o último trecho que é o SQL acima - observe como em vez de 3 registros, agora existem 2!
Resultado do SQL (apenas os dois primeiros campos mostrados):
Um deles representa o
PRIMARY KEY
daperson
tabela, e o outro é o (geral)UNIQUE CONSTRAINT
- o que "falta" é o parcialUNIQUE INDEX
, e isso porque NÃO é uma restrição!A maneira como racionalizei para mim mesmo é que
CONSTRAINT
existe um recurso para impedir que coisas entrem nas tabelas - uma vez passadas todas as restrições, é simplesmente aINDEX
tarefa de fornecer ponteiros para que possam ser acessados mais rapidamente se o otimizador acha que deveria ser usado - ou seja, um mero detalhe de implementação.Fim do violino - mudei
ALTER TABLE ADD CONSTRAINT...
paraCREATE INDEX...
e agora há apenas 1 registro noCONSTRAINT
SQL - mas a contaFK
não falha - porque cobre a tabela inteira .CONSTRAINT
s = proteçãoINDEX
s = organização (exceto onde cobrem toda a tabela)CONSTRAINT
s - exigido pelo padrão SQLINDEX
es - não obrigatórioEntão, para responder às perguntas
referências? Sim - veja links
dois tipos de
UNIQUE
índice? Na verdade, seria mais correto dizer que existem três tiposUNIQUE CONSTRAINT
s - pode ser usado emFK
sUNIQUE INDEX
es (não qualificado) - pode ser usado emFK
sUNIQUE INDEX
es (parcial) - não pode ser usado emFK
sEt voilà - e um +1 por realmente me fazer pensar!
Outros links úteis:
https://stackoverflow.com/questions/53602863/how-do-i-see-the-comment-on-a-postgresql-constraint
https://stackoverflow.com/questions/44274080/postgres-hash-index-with-unique-constraint
https://stackoverflow.com/questions/44274080/postgres-hash-index-with-unique-constraint