¿Cómo agregar una columna si no existe en PostgreSQL?


145

La pregunta es simple. ¿Cómo agregar una columna xa la tabla y, pero solo cuando la xcolumna no existe? Aquí solo encontré una solución para verificar si existe una columna.

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

Respuestas:


133

Aquí hay una versión corta y dulce que usa la declaración "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;
$$

No puede pasarlos como parámetros, deberá realizar una sustitución de variables en la cadena en el lado del cliente, pero esta es una consulta autónoma que solo emite un mensaje si la columna ya existe, agrega si no es así y continuará fallando en otros errores (como un tipo de datos no válido).

No recomiendo hacer CUALQUIERA de estos métodos si se trata de cadenas aleatorias que provienen de fuentes externas. Independientemente del método que utilice (cadenas dinámicas del lado de la hendidura o del lado del servidor ejecutadas como consultas), sería una receta para el desastre, ya que lo abre a los ataques de inyección SQL.


44
DO $$ BEGIN BEGIN CREATE INDEX type_idx ON table1 USING btree (type); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'Index exists.'; END; END;$$;el mismo enfoque en CREATE INDEX;) Gracias por su respuesta,
marioosh

No estoy seguro de por qué simplemente comienza el bloque de código anónimo con DO $$falla. Intenté lo DO $$;que falla también, hasta que recién comencé el bloque con el DO $$DECLARE r record;que se da un ejemplo en los documentos de postgres de desarrollo .
nemesisfixx

9
Cerrando con END; $$un error de sintaxis (Postgres 9.3), tuve que usar END $$;en su lugar
LightSystem

55
Buen enfoque, pero ¿por qué los bloques BEGIN / END anidados? Funciona bien con una sola capa para mí. También agregar un punto y coma al final ($$;) hace que la declaración sea inequívoca para psql.
Shane

1
Este enfoque ( EXCEPTION) es un poco más general y puede emplearse para tareas que no tienen IF NOT EXISTSsintaxis, por ejemplo ALTER TABLE ... ADD CONSTRAINT.
Tomasz Gandor

390

Con Postgres 9.6 esto se puede hacer usando la opciónif not exists

ALTER TABLE table_name ADD COLUMN IF NOT EXISTS column_name INTEGER;

44
Dulce. Lamentablemente, ADD CONSTRAINT IF NOT EXISTSaún no existe .
Tomasz Gandor

44
¿Por qué esta respuesta al final de la página es mucho mejor que las otras opciones?
Ecksters

Solo por curiosidad: ¿esto causará un bloqueo de acceso en la tabla (y por lo tanto requerirá una ventana de mantenimiento cuando se ejecute en tablas enormes en bases de datos de producción)?
Hassan Baig

44
El desbordamiento de pila realmente debería permitir cambiar la respuesta aceptada.
Henrik Sommerland

@HenrikSommerland: eso está permitido, pero solo por la persona que hizo la pregunta.
a_horse_with_no_name

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;

Llamada:

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

Devuelve TRUEen caso de éxito, de lo contrario FALSE(la columna ya existe).
Provoca una excepción para una tabla o nombre de tipo no válido.

¿Por qué otra versión?

  • Esto podría hacerse con una DOdeclaración, pero las DOdeclaraciones no pueden devolver nada. Y si es para uso repetido, crearía una función.

  • Utilizo los tipos de identificadores de objetos regclass y regtypepara _tbly _typeque a) evita la inyección de SQL yb) verifica la validez de ambos de forma inmediata (la forma más barata posible). El nombre de la columna _colaún no se ha desinfectado EXECUTEcon quote_ident(). Más explicación en esta respuesta relacionada:

  • format()requiere Postgres 9.1+. Para versiones anteriores concatene manualmente:

    EXECUTE 'ALTER TABLE ' || _tbl || ' ADD COLUMN ' || quote_ident(_col) || ' ' || _type;
  • Puede calificar esquemáticamente el nombre de su tabla, pero no tiene que hacerlo.
    Puede comillas dobles los identificadores en la llamada de función para preservar el caso de camello y las palabras reservadas (pero no debe usar nada de esto de todos modos).

  • Pregunto en pg_cataloglugar de la information_schema. Explicación detallada:

  • Los bloques que contienen una EXCEPTIONcláusula como la respuesta actualmente aceptada son sustancialmente más lentos. Esto es generalmente más simple y rápido. La documentación:

Consejo: Un bloque que contiene una EXCEPTIONcláusula es significativamente más costoso para entrar y salir que un bloque sin una. Por lo tanto, no lo use EXCEPTIONsin necesidad.


¡Me gusta más tu solución que la mía! Es mejor, más seguro y más rápido.
David S

¡La versión de Postgres con la que tengo que trabajar no tiene la DOdeclaración, una ligera modificación para aceptar DEFAULTy esto funcionó perfectamente!
renab

18

La siguiente consulta de selección volverá true/false, utilizando la EXISTS()función.

EXISTS () :
el argumento de EXISTS es una sentencia SELECT arbitraria, o subconsulta. La subconsulta se evalúa para determinar si devuelve alguna fila. Si devuelve al menos una fila, el resultado de EXISTS es "verdadero"; si la subconsulta no devuelve filas, el resultado de EXISTS es "falso"

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

y use la siguiente instrucción SQL dinámica para alterar su tabla

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
$$

2
Los nombres de tabla y columna duplicados pueden existir en múltiples esquemas.
Mike Sherrill 'Cat Recall'

1
Bueno, es posible que desee volver a escribir su código para tener en cuenta los esquemas.
Mike Sherrill 'Cat Recall'

2

Para aquellos que usan Postgre 9.5+ (creo que la mayoría de ustedes lo hacen), existe una solución bastante simple y limpia

ALTER TABLE if exists <tablename> add if not exists <columnname> <columntype>

1

la siguiente función verificará la columna si existe, devolverá el mensaje apropiado; de lo contrario, agregará la columna a la tabla.

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;
$$

Me parece una respuesta muy razonable, especialmente porque DO es una adición reciente a postgres
John Powell

1

Esta es básicamente la solución de sola, pero solo se limpió un poco. Es lo suficientemente diferente como para no solo querer "mejorar" su solución (además, creo que es grosero).

La principal diferencia es que usa el formato EXECUTE. Lo que creo que es un poco más limpio, pero creo que significa que debes estar en PostgresSQL 9.1 o más reciente.

Esto ha sido probado en 9.1 y funciona. Nota: generará un error si el esquema / nombre_tabla / o tipo_datos no son válidos. Eso podría "arreglarse", pero podría ser el comportamiento correcto en muchos 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)');

0

Se puede agregar a la función de invocación de scripts de migración y soltar cuando haya terminado.

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();

0

En mi caso, por cómo se creó, es un poco difícil para nuestros scripts de migración atravesar diferentes esquemas.

Para evitar esto, usamos una excepción que simplemente captó e ignoró el error. Esto también tuvo el agradable efecto secundario de ser mucho más fácil de ver.

Sin embargo, tenga cuidado de que las otras soluciones tengan sus propias ventajas que probablemente superen a esta solución:

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 $$;

-1

Puedes hacerlo de la siguiente manera.

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

Por lo tanto, soltará la columna si ya existe. Y luego agregue la columna a una tabla en particular.


17
¿Qué hay de perder datos?
Aliaksei Ramanau

48
Siempre puedes disculparte con tus clientes
konzo

Acababa de agregar la columna, así que esto fue muy conveniente para mí.
Noumenon

-4

Simplemente verifique si la consulta devolvió un column_name.

Si no, ejecute algo como esto:

ALTER TABLE x ADD COLUMN y int;

Donde pones algo útil para 'x' e 'y' y, por supuesto, un tipo de datos adecuado donde utilicé int.


¿En qué ambiente estás? ¿Tiene un lenguaje de scripting en su propuesta? ¿O estás usando PL / pgSQL? ¿Está ejecutando desde algún lenguaje como PHP / Java / etc.?
Erwin Moller

Sin lenguaje de script. Necesito hacer esto solo dentro de SQL . Tengo una aplicación Java que en la entrada obtiene un script SQL y ejecuta ese script en la base de datos seleccionada.
marioosh

2
Luego le aconsejo que busque en pl / pgsql: postgresql.org/docs/9.1/static/plpgsql.html Cree una función que tome column_name y table_name como argumentos.
Erwin Moller
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.