Como adicionar uma coluna se não existir no PostgreSQL?

145

A pergunta é simples. Como adicionar coluna xà tabela y, mas somente quando a xcoluna não existe? Encontrei apenas solução aqui como verificar se existe coluna.

SELECT column_name 
FROM information_schema.columns 
WHERE table_name='x' and column_name='y';
marioosh
fonte

Respostas:

133

Aqui está uma versão curta e agradável usando a instrução "DO":

DO $$ 
    BEGIN
        BEGIN
            ALTER TABLE <table_name> ADD COLUMN <column_name> <column_type>;
        EXCEPTION
            WHEN duplicate_column THEN RAISE NOTICE 'column <column_name> already exists in <table_name>.';
        END;
    END;
$$

Você não pode passar isso como parâmetros, precisará fazer a substituição de variáveis ​​na string no lado do cliente, mas essa é uma consulta independente que somente emite uma mensagem se a coluna já existir, adiciona se não existir e continuará a falhar em outros erros (como um tipo de dados inválido).

Eu não recomendo executar QUALQUER desses métodos se forem seqüências aleatórias provenientes de fontes externas. Não importa qual método você use (cadeias dinâmicas cleint ou do lado do servidor executadas como consultas), seria uma receita para o desastre, pois abre para ataques de injeção de SQL.

Matthew Wood
fonte
4
DO $$ BEGIN BEGIN CREATE INDEX type_idx ON table1 USING btree (type); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'Index exists.'; END; END;$$;a mesma abordagem em CREATE INDEX;) Obrigado pela sua resposta,
marioosh
Não sei por que apenas iniciar o bloco de código anônimo com DO $$falha. Eu tentei o DO $$;que também falhou, até começar o bloco com o DO $$DECLARE r record;qual é dado um exemplo nos documentos do dev postgres .
Nemesisfixx
9
Fechando com END; $$é um erro de sintaxe (Postgres 9.3), eu tive que usar em END $$;vez disso
LightSystem
5
Boa abordagem, mas por que os blocos BEGIN / END aninhados? Funciona bem com uma única camada para mim. A adição de um ponto e vírgula no final ($$;) torna a declaração inequívoca para o psql.
Shane
1
Essa abordagem ( EXCEPTION) é um pouco mais geral e pode ser empregada para tarefas que não possuem IF NOT EXISTSsintaxe - por exemplo ALTER TABLE ... ADD CONSTRAINT.
Tomasz Gandor
390

Com o Postgres 9.6, isso pode ser feito usando a opçãoif not exists

ALTER TABLE table_name ADD COLUMN IF NOT EXISTS column_name INTEGER;
um cavalo sem nome
fonte
4
Doce. Infelizmente, ADD CONSTRAINT IF NOT EXISTSainda não há .
Tomasz Gandor
4
Por que essa resposta está na parte inferior da página, é MUITO melhor do que as outras opções.
Ecksters
Por curiosidade: isso causará um bloqueio de acesso na tabela (e, portanto, exigirá uma janela de manutenção quando executada em grandes tabelas nos bancos de dados de produção)?
Hassan Baig
4
O estouro de pilha realmente deve suportar a alteração da resposta aceita.
Henrik Sommerland
@ HenrikSommerland: isso é permitido - mas apenas pela pessoa que fez a pergunta.
a_horse_with_no_name 10/07/19
22
CREATE OR REPLACE function f_add_col(_tbl regclass, _col  text, _type regtype)
  RETURNS bool AS
$func$
BEGIN
   IF EXISTS (SELECT 1 FROM pg_attribute
              WHERE  attrelid = _tbl
              AND    attname = _col
              AND    NOT attisdropped) THEN
      RETURN FALSE;
   ELSE
      EXECUTE format('ALTER TABLE %s ADD COLUMN %I %s', _tbl, _col, _type);
      RETURN TRUE;
   END IF;
END
$func$  LANGUAGE plpgsql;

Ligar:

SELECT f_add_col('public.kat', 'pfad1', 'int');

Retorna TRUEcom sucesso, caso contrário FALSE(a coluna já existe).
Gera uma exceção para nome de tabela ou tipo inválido.

Por que outra versão?

  • Isso pode ser feito com uma DOinstrução, mas as DOinstruções não podem retornar nada. E se for para uso repetido, eu criaria uma função.

  • Eu uso os tipos de identificador de objeto regclass e regtypepara _tble _typeque a) impede a injeção de SQL eb) verifica a validade de ambos imediatamente (da maneira mais barata possível). O nome da coluna _colainda precisa ser limpo EXECUTEcom quote_ident(). Mais explicações nesta resposta relacionada:

  • format()requer o Postgres 9.1+. Para versões mais antigas concatenar manualmente:

    EXECUTE 'ALTER TABLE ' || _tbl || ' ADD COLUMN ' || quote_ident(_col) || ' ' || _type;
  • Você pode qualificar o nome da tabela com esquema, mas não precisa.
    Você pode citar duas vezes os identificadores na chamada de função para preservar palavras reservadas e em maiúsculas e minúsculas (mas não deve usar nada disso).

  • Eu pergunto em pg_catalogvez do information_schema. Explicação detalhada:

  • Blocos contendo uma EXCEPTIONcláusula como a resposta atualmente aceita são substancialmente mais lentos. Isso geralmente é mais simples e rápido. A documentação:

Dica: Um bloco contendo uma EXCEPTIONcláusula é significativamente mais caro para entrar e sair do que um bloco sem uma. Portanto, não use EXCEPTIONsem necessidade.

Erwin Brandstetter
fonte
Gosto mais da sua solução do que da minha! É melhor, mais seguro e mais rápido.
David S
A versão do Postgres com a qual tenho que trabalhar não possui a DOdeclaração, uma pequena modificação para aceitar DEFAULTe isso funcionou perfeitamente!
Renab #
18

A consulta de seleção a seguir retornará true/false, usando a EXISTS()função

EXISTS () :
O argumento EXISTS é uma instrução SELECT arbitrária ou subconsulta. A subconsulta é avaliada para determinar se retorna alguma linha. Se retornar pelo menos uma linha, o resultado de EXISTS será "verdadeiro"; se a subconsulta não retornar linhas, o resultado de EXISTS será "false"

SELECT EXISTS(SELECT  column_name 
                FROM  information_schema.columns 
               WHERE  table_schema = 'public' 
                 AND  table_name = 'x' 
                 AND  column_name = 'y'); 

e use a seguinte instrução SQL dinâmica para alterar sua tabela

DO
$$
BEGIN
IF NOT EXISTS (SELECT column_name 
                 FROM  information_schema.columns 
                WHERE  table_schema = 'public' 
                  AND  table_name = 'x' 
                  AND  column_name = 'y') THEN
ALTER TABLE x ADD COLUMN y int DEFAULT NULL;
ELSE
RAISE NOTICE 'Already exists';
END IF;
END
$$
Vivek S.
fonte
2
Os nomes de tabelas e colunas duplicados podem existir em vários esquemas.
Mike Sherrill 'Cat Recall'
1
Bem, você pode reescrever seu código para dar conta de esquemas.
Mike Sherrill 'Cat Recall'
2

Para quem usa o Postgre 9.5+ (acredito que a maioria de vocês usa), existe uma solução bastante simples e limpa

ALTER TABLE if exists <tablename> add if not exists <columnname> <columntype>
Leon
fonte
1

a função abaixo verificará a coluna, se existir, retorne a mensagem apropriada; caso contrário, a coluna será adicionada à tabela.

create or replace function addcol(schemaname varchar, tablename varchar, colname varchar, coltype varchar)
returns varchar 
language 'plpgsql'
as 
$$
declare 
    col_name varchar ;
begin 
      execute 'select column_name from information_schema.columns  where  table_schema = ' ||
      quote_literal(schemaname)||' and table_name='|| quote_literal(tablename) || '   and    column_name= '|| quote_literal(colname)    
      into   col_name ;   

      raise info  ' the val : % ', col_name;
      if(col_name is null ) then 
          col_name := colname;
          execute 'alter table ' ||schemaname|| '.'|| tablename || ' add column '|| colname || '  ' || coltype; 
      else
           col_name := colname ||' Already exist';
      end if;
return col_name;
end;
$$
solaimuruganv
fonte
Parece-me uma resposta muito razoável, especialmente como o fazem é uma adição recente ao postgres
John Powell
1

Esta é basicamente a solução da sola, mas apenas limpou um pouco. É diferente o suficiente para eu não apenas querer "melhorar" sua solução (mais, acho que isso é rude).

A principal diferença é que ele usa o formato EXECUTE. O que eu acho que é um pouco mais limpo, mas acredito que significa que você deve estar no PostgresSQL 9.1 ou mais recente.

Isso foi testado na 9.1 e funciona. Nota: Irá gerar um erro se o esquema / nome_tabela / ou tipo_de_dados for inválido. Isso pode "consertar", mas pode ser o comportamento correto em muitos casos.

CREATE OR REPLACE FUNCTION add_column(schema_name TEXT, table_name TEXT, 
column_name TEXT, data_type TEXT)
RETURNS BOOLEAN
AS
$BODY$
DECLARE
  _tmp text;
BEGIN

  EXECUTE format('SELECT COLUMN_NAME FROM information_schema.columns WHERE 
    table_schema=%L
    AND table_name=%L
    AND column_name=%L', schema_name, table_name, column_name)
  INTO _tmp;

  IF _tmp IS NOT NULL THEN
    RAISE NOTICE 'Column % already exists in %.%', column_name, schema_name, table_name;
    RETURN FALSE;
  END IF;

  EXECUTE format('ALTER TABLE %I.%I ADD COLUMN %I %s;', schema_name, table_name, column_name, data_type);

  RAISE NOTICE 'Column % added to %.%', column_name, schema_name, table_name;

  RETURN TRUE;
END;
$BODY$
LANGUAGE 'plpgsql';

uso:

select add_column('public', 'foo', 'bar', 'varchar(30)');
David S
fonte
0

Podem ser adicionados aos scripts de migração que invocam a função e descartam quando concluídos.

create or replace function patch_column() returns void as
$$
begin
    if exists (
        select * from information_schema.columns
            where table_name='my_table'
            and column_name='missing_col'
     )
    then
        raise notice 'missing_col already exists';
    else
        alter table my_table
            add column missing_col varchar;
    end if;
end;
$$ language plpgsql;

select patch_column();

drop function if exists patch_column();
user645527
fonte
0

No meu caso, pela forma como foi criado, é um pouco difícil para nossos scripts de migração atravessar esquemas diferentes.

Para contornar isso, usamos uma exceção que capturou e ignorou o erro. Isso também teve o bom efeito colateral de ser muito mais fácil de olhar.

No entanto, tenha cuidado para que as outras soluções tenham suas próprias vantagens que provavelmente superam essa solução:

DO $$
BEGIN
  BEGIN
    ALTER TABLE IF EXISTS bobby_tables RENAME COLUMN "dckx" TO "xkcd";
  EXCEPTION
    WHEN undefined_column THEN RAISE NOTICE 'Column was already renamed';
  END;
END $$;
ThinkBonobo
fonte
-1

Você pode fazer isso da seguinte maneira.

ALTER TABLE tableName drop column if exists columnName; 
ALTER TABLE tableName ADD COLUMN columnName character varying(8);

Portanto, ele eliminará a coluna se ela já existir. E, em seguida, adicione a coluna à tabela específica.

parthivrshah
fonte
17
que tal perder dados?
Aliaksei Ramanau
48
Você pode sempre pedir desculpas aos seus clientes
konzo
Acabei de adicionar a coluna, então isso foi muito conveniente para mim.
Noumenon
-4

Basta verificar se a consulta retornou um nome da coluna.

Caso contrário, execute algo como isto:

ALTER TABLE x ADD COLUMN y int;

Onde você coloca algo útil para 'x' e 'y' e, claro, um tipo de dados adequado onde eu usei int.

Erwin Moller
fonte
Em que ambiente você está? Você tem um idioma de script em sua proposta? Ou você está usando PL / pgSQL? Você está executando a partir de alguma linguagem como PHP / Java / etc?
precisa saber é o seguinte
Nenhuma linguagem de script. Eu preciso fazer isso apenas dentro do SQL . Eu tenho o aplicativo Java que, na entrada, obtém o script SQL e executa esse script no banco de dados selecionado.
Marioosh 26/09/12
2
Em seguida, aconselho que você procure pl / pgsql: postgresql.org/docs/9.1/static/plpgsql.html Crie uma função que use column_name e table_name como argumentos.
precisa saber é o seguinte