Postgresql - altera o tamanho de uma coluna varchar para um tamanho menor

153

Eu tenho uma pergunta sobre o ALTER TABLEcomando em uma tabela muito grande (quase 30 milhões de linhas). Uma de suas colunas é a varchar(255)e eu gostaria de redimensioná-la para a varchar(40). Basicamente, gostaria de alterar minha coluna executando o seguinte comando:

ALTER TABLE mytable ALTER COLUMN mycolumn TYPE varchar(40);

Não tenho nenhum problema se o processo for muito longo, mas parece que minha tabela não é mais legível durante o comando ALTER TABLE. Existe uma maneira mais inteligente? Talvez adicione uma nova coluna, copie valores da coluna antiga, solte a coluna antiga e finalmente renomeie a nova?

Qualquer pista será muito apreciada! Desde já, obrigado,

Nota: Eu uso o PostgreSQL 9.0.

Labynocle
fonte
11
Só para deixar claro: você sabe, isso resizingnão fará com que a mesa ocupe menos espaço?
AH
mesmo no meu caso? Quero dizer, a coluna terá um tamanho máximo de 40 caracteres (octetos) em vez de 255?
Labynocle
16
Se você disser varchar(255)ao PostgreSQL, ele não alocará 255 bytes para um valor cujo comprimento real seja 40 bytes. Alocará 40 bytes (mais alguma sobrecarga interna). A única coisa que be changed by the ALTER TABLE` é o número máximo de bytes que você pode armazenar nessa coluna sem obter um erro do PG.
AH
Sobre a sobrecarga AH mencionada: Qual é a sobrecarga para varchar (n)?
Erwin Brandstetter
Confira a resposta aqui para uma atualização dba.stackexchange.com/questions/189890/…
Evan Carroll

Respostas:

73

Há uma descrição de como fazer isso em Redimensionar uma coluna em uma tabela do PostgreSQL sem alterar os dados . Você precisa hackear os dados do catálogo do banco de dados. A única maneira de fazer isso oficialmente é com ALTER TABLE e, como você observou, as alterações travam e reescrevem a tabela inteira enquanto estiver em execução.

Leia a seção Tipos de caracteres dos documentos antes de alterar isso. Todos os tipos de casos estranhos que você deve conhecer aqui. A verificação do comprimento é feita quando os valores são armazenados nas linhas. Se você hackear um limite mais baixo, isso não reduzirá o tamanho dos valores existentes. Você deve fazer uma varredura em toda a tabela, procurando por linhas em que o comprimento do campo seja> 40 caracteres após a alteração. Você precisará descobrir como truncar esses manualmente - para recuperar alguns bloqueios apenas nos de tamanho grande - porque se alguém tentar atualizar algo nessa linha, ele será rejeitado como muito grande agora, no ponto vai armazenar a nova versão da linha. Hilaridade segue para o usuário.

VARCHAR é um tipo terrível que existe no PostgreSQL apenas para cumprir com a parte terrível associada do padrão SQL. Se você não se importa com a compatibilidade de vários bancos de dados, considere armazenar seus dados como TEXT e adicione uma restrição para limitar seu tamanho. Restrições que você pode alterar sem esse problema de bloqueio / reescrita da tabela e podem fazer mais verificações de integridade do que apenas a verificação de comprimento fraco.

Greg Smith
fonte
Obrigado pela resposta. Vou verificar o seu link. Não estou preocupado com a verificação manual do tamanho, porque todo o meu conteúdo tem um tamanho máximo de 40 caracteres. Eu preciso ler mais sobre restrição no texto, porque eu acreditava que VARCHAR era melhor para verificar lentgh :)
Labynocle
6
Alterar o comprimento do varchar não reescreve a tabela. Apenas verifique o comprimento da restrição em toda a tabela exatamente como CHECK CONSTRAINT. Se você aumentar o comprimento, não há nada a fazer, apenas a próxima inserção ou atualizações aceitarão um comprimento maior. Se você diminuir o comprimento e todas as linhas passarem pela nova restrição menor, a Pg não executará nenhuma outra ação além de permitir que as próximas inserções ou atualizações gravem apenas o novo comprimento.
Maniero 22/09
3
@ Bigown, apenas para esclarecer, sua declaração é verdadeira apenas para o PostgreSQL 9.2+ , não para as antigas.
MatheusOl
12
O link está morto agora.
raarts
Para obter mais informações sobre como isso funciona, consulte dba.stackexchange.com/questions/189890/…
Evan Carroll
100

No PostgreSQL 9.1, existe uma maneira mais fácil

http://www.postgresql.org/message-id/[email protected]

CREATE TABLE foog(a varchar(10));

ALTER TABLE foog ALTER COLUMN a TYPE varchar(30);

postgres=# \d foog

 Table "public.foog"
 Column |         Type          | Modifiers
--------+-----------------------+-----------
 a      | character varying(30) |
sir_leslie
fonte
6
Observe que ele funciona apenas porque você está especificando um tamanho maior (30> 10). Se o tamanho for menor, você receberá o mesmo erro que eu .
Matthieu
2
O Postgres não deve gerar um erro se você diminuir o tamanho do varchar por meio de uma consulta ALTER TABLE, a menos que uma ou mais linhas contenha um valor que exceda o novo tamanho.
Diga
@ Diga, interessante. Isso significa que o Postgres faz uma varredura completa da tabela ou, de alguma forma, mantém o tamanho máximo em suas estatísticas?
Matthieu
47

Ok, provavelmente estou atrasado para a festa, MAS ...

Não é necessário redimensionar a coluna no seu caso!

O Postgres, diferentemente de outros bancos de dados, é inteligente o suficiente para usar apenas espaço suficiente para caber na sequência (mesmo usando a compactação para cadeias mais longas), portanto, mesmo que sua coluna seja declarada como VARCHAR (255) - se você armazenar cadeias de caracteres de 40 caracteres em Na coluna, o uso do espaço será de 40 bytes + 1 byte de sobrecarga.

O requisito de armazenamento para uma sequência curta (até 126 bytes) é de 1 byte mais a sequência real, que inclui o preenchimento de espaço no caso de caractere. As cadeias mais longas têm 4 bytes de sobrecarga em vez de 1. As cadeias longas são compactadas pelo sistema automaticamente, portanto, o requisito físico no disco pode ser menor. Valores muito longos também são armazenados em tabelas em segundo plano para que não interfiram no acesso rápido a valores mais curtos da coluna.

( http://www.postgresql.org/docs/9.0/interactive/datatype-character.html )

A especificação de tamanho em VARCHAR é usada apenas para verificar o tamanho dos valores inseridos, não afeta o layout do disco. De fato, os campos VARCHAR e TEXT são armazenados da mesma maneira no Postgres .

Sergey
fonte
8
Nunca é tarde para adicionar mais informações sobre o "porquê"! Obrigado por todas essas informações
Labynocle 08/08
Em algum momento você precisa ser consistente na estrutura do seu banco de dados. Mesmo que duas colunas não tenham uma relação, elas podem ter uma relação no ponto de vista do conceito, por exemplo, faça o checkout do modelo EAV.
Alexandre
36

Eu estava enfrentando o mesmo problema ao tentar truncar um VARCHAR de 32 para 8 e obter o ERROR: value too long for type character varying(8). Quero ficar o mais próximo possível do SQL, porque estou usando uma estrutura semelhante a JPA, que talvez tenhamos que mudar para DBMS diferentes de acordo com as opções do cliente (o PostgreSQL é o padrão). Portanto, não quero usar o truque de alterar as tabelas do sistema.

Acabei usando a USINGinstrução no ALTER TABLE:

ALTER TABLE "MY_TABLE" ALTER COLUMN "MyColumn" TYPE varchar(8)
USING substr("MyColumn", 1, 8)

Como observou o @raylu, ALTERadquire um bloqueio exclusivo sobre a mesa, para que todas as outras operações sejam adiadas até que sejam concluídas.

Matthieu
fonte
2
o ALTERadquire um bloqueio exclusivo sobre a mesa e evita todas as outras operações
raylu
8

Adicionar uma nova coluna e substituir uma nova por antiga funcionou para mim, no redshift postgresql, consulte este link para obter mais detalhes https://gist.github.com/mmasashi/7107430

BEGIN;
LOCK users;
ALTER TABLE users ADD COLUMN name_new varchar(512) DEFAULT NULL;
UPDATE users SET name_new = name;
ALTER TABLE users DROP name;
ALTER TABLE users RENAME name_new TO name;
END;
espigas
fonte
7

Aqui está o cache da página descrita por Greg Smith. Caso isso também morra, a declaração alter é assim:

UPDATE pg_attribute SET atttypmod = 35+4
WHERE attrelid = 'TABLE1'::regclass
AND attname = 'COL1';

Onde sua tabela é TABLE1, a coluna é COL1 e você deseja configurá-la para 35 caracteres (o +4 é necessário para fins de legado de acordo com o link, possivelmente a sobrecarga mencionada por AH nos comentários).

Tom
fonte
7

se você colocar a alteração em uma transação, a tabela não deve ser bloqueada:

BEGIN;
  ALTER TABLE "public"."mytable" ALTER COLUMN "mycolumn" TYPE varchar(40);
COMMIT;

isso funcionou para mim rapidamente, alguns segundos em uma mesa com mais de 400 mil linhas.

jacktrade
fonte
5
Por que você esperaria que o wrapper de transação explícito alterasse o comportamento de bloqueio da ALTERinstrução? Não faz.
Erwin Brandstetter
tente você mesmo, com e sem o invólucro da transação, você notará uma enorme diferença.
jacktrade
2
Sua resposta está incorreta no principal. Qualquer instrução DDL sem wrapper de transação explícita é executada implicitamente em uma transação. O único efeito possível da transação explícita é que os bloqueios são mantidos por mais tempo - até o explícito COMMIT. O wrapper só faz sentido se você deseja colocar mais comandos na mesma transação.
Erwin Brandstetter 23/10
você está completamente certo, mas eu insisto: tente você mesmo, continue. e depois pergunte por que não está funcionando da mesma maneira.
jacktrade
Não ajudou no Postgres 9.3.
Julmen
1

Eu encontrei uma maneira muito fácil de alterar o tamanho, ou seja, a anotação @Size (min = 1, max = 50), que faz parte de "import javax.validation.constraints", ou seja, "import javax.validation.constraints.Size;"

@Size(min = 1, max = 50)
private String country;


when executing  this is hibernate you get in pgAdmin III 


CREATE TABLE address
(
.....
  country character varying(50),

.....

)
Tito
fonte
Obrigado pelo seu post! Por favor, não use assinaturas / slogans em suas postagens. Sua caixa de usuário conta como sua assinatura e você pode usar seu perfil para postar qualquer informação sobre você. Perguntas frequentes sobre assinaturas / slogans
Andrew Barber
0

Tente executar a seguinte tabela de alteração:

ALTER TABLE public.users 
ALTER COLUMN "password" TYPE varchar(300) 
USING "password"::varchar;
Никита Верёвкин
fonte