Se eu tivesse uma tabela com 3 colunas - digamos A, B e D - e tivesse que introduzir uma nova - digamos C para substituir a posição atual de D. Eu usaria o seguinte método:
- Introduzir 2 novas colunas como C e D2.
- Copie o conteúdo de D para D2.
- Excluir D.
- Renomeie D2 para D.
A nova ordem seria A, B, C e D.
Eu pensei que esta era uma prática legítima, pois (até agora) não produziu problemas.
Porém, hoje me deparei com um problema quando uma função realizando um comando na mesma tabela retornou o seguinte erro:
table row type and query-specified row type do not match
E o seguinte detalhe:
Query provides a value for a dropped column at ordinal position 13
Tentei reiniciar o PostgreSQL, fazer um VACUUM FULL
e finalmente excluir e recriar a função conforme sugerido aqui e aqui, mas essas soluções não funcionaram (além do fato de tentarem resolver uma situação em que uma tabela do sistema foi alterada).
Tendo o luxo de trabalhar com um banco de dados muito pequeno, exportei-o, excluí-o e importei-o novamente e isso corrigiu o problema com minha função.
Eu estava ciente do fato de que não se deve mexer com a ordem natural das colunas modificando as tabelas do sistema (sujando as mãos com pg_attribute
, etc.) como visto aqui:
É possível alterar a ordem natural das colunas no Postgres?
A julgar pelo erro lançado pela minha função, agora percebo que mudar a ordem das colunas com meu método também é um não-não. Alguém pode esclarecer por que o que estou fazendo também está errado?
A versão do Postgres é 9.6.0.
Aqui está a função:
CREATE OR REPLACE FUNCTION "public"."__post_users" ("facebookid" text, "useremail" text, "username" text) RETURNS TABLE (authentication_code text, id integer, key text, stripe_id text) AS '
-- First, select the user:
WITH select_user AS
(SELECT
users.id
FROM
users
WHERE
useremail = users.email),
-- Second, update the user (if user exists):
update_user AS
(UPDATE
users
SET
authentication_code = GEN_RANDOM_UUID(),
authentication_date = current_timestamp,
facebook_id = facebookid
WHERE EXISTS (SELECT * FROM select_user)
AND
useremail = users.email
RETURNING
users.authentication_code,
users.id,
users.key,
users.stripe_id),
-- Third, insert the user (if user does not exist):
insert_user AS
(INSERT INTO
users (authentication_code, authentication_date, email, key, name, facebook_id)
SELECT
GEN_RANDOM_UUID(),
current_timestamp,
useremail,
GEN_RANDOM_UUID(),
COALESCE(username, SUBSTRING(useremail FROM ''([^@]+)'')),
facebookid
WHERE NOT EXISTS (SELECT * FROM select_user)
RETURNING
users.authentication_code,
users.id,
users.key,
users.stripe_id)
-- Finally, select the authentication code, ID, key and Stripe ID:
SELECT
*
FROM
update_user
UNION ALL
SELECT
*
FROM
insert_user' LANGUAGE "sql" COST 100 ROWS 1
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
Realizei a renomeação/reordenação em ambas as colunas facebook_id
e stripe_id
(uma nova coluna foi adicionada antes delas, que é o motivo da renomeação, mas não é tocada por esta consulta).
Ter as colunas em uma determinada ordem é puramente interessante para a ordem. No entanto, o motivo de fazer essa pergunta é a preocupação de que uma simples renomeação e exclusão de uma coluna possa desencadear problemas reais para alguém que usa funções no modo de produção (como aconteceu comigo).
Provável bug em 9.6 e 9.6.1
Isso parece completamente um bug para mim ...
Não sei por que isso acontece, mas posso confirmar que acontece. Esta é a configuração mais simples encontrada que reproduz o problema (na versão 9.6.0 e 9.6.1).
Após esta configuração, a próxima instrução simplesmente funciona
Neste ponto, DROP uma coluna:
Essa alteração faz com que a próxima instrução gere um erro
que é o mesmo mencionado por @Andy:
Soltar e recriar a função NÃO resolve o problema.
VACUUM FULL (a tabela ou todo o banco de dados) não resolve o problema.
O relatório do bug foi passado para a lista de discussão apropriada do PostgreSQL e tivemos uma resposta muito rápida :
Versão 9.6.2
Em 2017-03-06, posso confirmar que não consigo reproduzir esse comportamento na versão 9.6.2. Ou seja, o bug parece ter sido corrigido nesta versão.
ATUALIZAR
Por comentário de @Jana: "Posso confirmar que o bug está presente em 9.6.1 e foi corrigido em 9.6.2. A correção também está listada no site de lançamento do postgres : Corrija erros espúrios de "consulta fornece um valor para uma coluna descartada" durante INSERT ou UPDATE em uma tabela com uma coluna descartada"
Eu sei que você provavelmente já ouviu isso antes, mas é uma ideia horrível.
SELECT *
Portanto, se não importar nada não o dissuade e reconhecemos que estamos apenas brincando com o Photoshop com estrutura de linha e obcecados com a exibição, vamos explicar mais algumas coisas.
CREATE TABLE
(embora seja uma prioridade muito maior)Portanto, o PostgreSQL é uma camada de exibição ruim. Além de tudo isso, embora
ALTER
funcione bem, você não deve tentar o dragão. AmbosALTER TABLE
, e a seção CAVEAT mencionam isso,E, se tudo isso não for suficiente, e você ainda quiser fingir que esta é uma boa ideia, em vez de uma ideia horrível. Então tente isso,
pg_dump -t
BEGIN
uma transaçãoDROP
a velha mesa inteiramente,RENAME
a tabela temporária para a tabela prod.COMMIT
Se tudo isso parecer excessivo, lembre-se de que atualizar linhas no banco de dados requer reescrever as linhas (supondo que não sejam TOAST . Você está tendo que analisar os dados e reconstruir o esquema da tabela, mas de qualquer forma você tem que reescrever a linha. Se eu tivesse que fazer esta tarefa, é assim que eu faria.
Mas, tudo isso falando de forma geral. Ninguém reproduziu seus resultados.
Você deu um caso de teste que não podemos executar
E você não nos disse a versão exata em que está.
Contornei esse bug fazendo backup e restaurando meu banco de dados.
Passos para Heroku
heroku maintenance:on
heroku pg:backups:capture
heroku pg:backups:restore
heroku restart
heroku maintenance:off
Também me deparei com esse bug. Para aqueles que não desejam fazer backup/restaurar totalmente seu banco de dados. Saiba que simplesmente copiar a tabela funciona. Porém, não existe uma maneira "mágica" de copiar uma tabela. Eu fiz isso usando:
Depois disso, você ainda precisará recriar manualmente seus índices, chaves estrangeiras e padrões. Recriar a tabela dessa forma fez com que o bug desaparecesse.