Come aggiungere colonna se non esiste su PostgreSQL?


145

La domanda è semplice Come aggiungere una colonna xalla tabella y, ma solo quando la xcolonna non esiste? Ho trovato l'unica soluzione qui come verificare se esiste la colonna.

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

Risposte:


133

Ecco una versione breve e dolce usando l'istruzione "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;
$$

Non puoi passarli come parametri, dovrai fare una sostituzione variabile nella stringa sul lato client, ma questa è una query autonoma che emette un messaggio solo se la colonna esiste già, aggiunge se non lo fa e continuerà a non riuscire su altri errori (come un tipo di dati non valido).

Non consiglio di fare QUALUNQUE di questi metodi se si tratta di stringhe casuali provenienti da fonti esterne. Indipendentemente dal metodo utilizzato (stringhe dinamiche lato client o lato server eseguite come query), sarebbe una ricetta per il disastro poiché ti apre agli attacchi di iniezione SQL.


4
DO $$ BEGIN BEGIN CREATE INDEX type_idx ON table1 USING btree (type); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'Index exists.'; END; END;$$;lo stesso approccio in CREATE INDEX;) Grazie per la risposta,
marioosh,

Non sono sicuro del perché l'avvio del blocco di codice anonimo abbia DO $$esito negativo. Ho provato anche ciò DO $$;che fallisce, fino a quando non ho appena iniziato il blocco con DO $$DECLARE r record;cui è riportato in un esempio sui documenti dev postgres .
nemesisfixx,

9
La chiusura con END; $$è un errore di sintassi (Postgres 9.3), ho dovuto usare END $$;invece
LightSystem

5
Buon approccio, ma perché i blocchi BEGIN / END nidificati? Funziona bene con un singolo strato per me. L'aggiunta di un punto e virgola alla fine ($$;) rende l'istruzione univoca per psql.
Shane,

1
Questo approccio ( EXCEPTION) è un po 'più generale e può essere impiegato per attività che non hanno IF NOT EXISTSsintassi, ad esempio ALTER TABLE ... ADD CONSTRAINT.
Tomasz Gandor,

390

Con Postgres 9.6 questo può essere fatto usando l'opzioneif not exists

ALTER TABLE table_name ADD COLUMN IF NOT EXISTS column_name INTEGER;

4
Dolce. Sfortunatamente, non c'è ADD CONSTRAINT IF NOT EXISTSancora.
Tomasz Gandor,

4
Perché questa risposta è nella parte inferiore della pagina, è molto meglio delle altre opzioni.
Ecksters

Solo per curiosità: questo causerà un blocco dell'accesso alla tabella (e quindi richiederà una finestra di manutenzione quando eseguito su enormi tabelle nei database di produzione)?
Hassan Baig,

4
Stack-overflow dovrebbe davvero supportare la modifica della risposta accettata.
Henrik Sommerland,

@HenrikSommerland: è consentito, ma solo dalla persona che ha posto la domanda.
a_horse_with_no_name il

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;

Chiamata:

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

Restituisce TRUEin caso di successo, altrimenti FALSE(la colonna esiste già).
Solleva un'eccezione per nome di tabella o tipo non valido.

Perché un'altra versione?

  • Questo potrebbe essere fatto con una DOdichiarazione, ma le DOdichiarazioni non possono restituire nulla. E se è per un uso ripetuto, creerei una funzione.

  • Uso i tipi di identificatore di oggetto regclass e regtypeper _tble _typeche a) impedisce SQL injection e b) controlli validità sia immediatamente (miglior modo possibile). Il nome della colonna _coldeve ancora essere disinfettato EXECUTEcon quote_ident(). Ulteriori spiegazioni in questa risposta correlata:

  • format()richiede Postgres 9.1+. Per le versioni precedenti concatenare manualmente:

    EXECUTE 'ALTER TABLE ' || _tbl || ' ADD COLUMN ' || quote_ident(_col) || ' ' || _type;
  • Puoi qualificare lo schema del nome della tabella, ma non è necessario.
    È possibile racchiudere tra virgolette gli identificativi nella chiamata di funzione per preservare il caso di cammello e le parole riservate (ma non si deve comunque usare nulla di tutto ciò).

  • Chiedo pg_cataloginvece di information_schema. Spiegazione dettagliata:

  • I blocchi contenenti una EXCEPTIONclausola come la risposta attualmente accettata sono sostanzialmente più lenti. Questo è generalmente più semplice e veloce. La documentazione:

Suggerimento: un blocco contenente una EXCEPTIONclausola è significativamente più costoso per entrare e uscire di un blocco senza uno. Pertanto, non utilizzare EXCEPTIONsenza necessità.


Mi piace la tua soluzione meglio della mia! È meglio, più sicuro e più veloce.
David S,

La versione di Postgres con cui devo lavorare non ha la DOdichiarazione, una leggera modifica da accettare DEFAULTe questo ha funzionato perfettamente!
Renab,

18

Dopo la selezione selezionare verrà restituito true/false, utilizzando la EXISTS()funzione.

EXISTS () :
l'argomento di EXISTS è un'istruzione SELECT arbitraria o una sottoquery. La sottoquery viene valutata per determinare se restituisce righe. Se restituisce almeno una riga, il risultato di EXISTS è "true"; se la sottoquery non restituisce righe, il risultato di EXISTS è "false"

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

e utilizzare la seguente istruzione SQL dinamica per modificare la tabella

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
Nomi di tabella duplicati e nomi di colonna possono esistere in più schemi.
Mike Sherrill "Cat Recall",

1
Bene, potresti voler riscrivere il tuo codice per tenere conto degli schemi.
Mike Sherrill "Cat Recall",

2

Per coloro che usano Postgre 9.5+ (credo che molti di voi lo facciano), esiste una soluzione abbastanza semplice e pulita

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

1

la funzione seguente controllerà la colonna se esiste restituirà il messaggio appropriato altrimenti aggiungerà la colonna alla tabella.

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

Mi sembra una risposta molto ragionevole, soprattutto perché DO è una recente aggiunta a Postgres
John Powell,

1

Questa è fondamentalmente la soluzione della sola, ma è stata semplicemente ripulita. È abbastanza diverso che non volevo solo "migliorare" la sua soluzione (in più, penso che sia scortese).

La differenza principale è che utilizza il formato EXECUTE. Il che penso sia un po 'più pulito, ma credo significhi che devi essere su PostgresSQL 9.1 o versioni successive.

Questo è stato testato su 9.1 e funziona. Nota: genererà un errore se lo schema / table_name / o data_type non sono validi. Questo potrebbe "risolto", ma potrebbe essere il comportamento corretto in molti casi.

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

utilizzo:

select add_column('public', 'foo', 'bar', 'varchar(30)');

0

Può essere aggiunto agli script di migrazione invocare la funzione e rilasciarla al termine.

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

Nel mio caso, per come è stato creato, è un po 'difficile per i nostri script di migrazione attraversare schemi diversi.

Per aggirare il problema, abbiamo utilizzato un'eccezione che ha appena rilevato e ignorato l'errore. Ciò ha avuto anche il piacevole effetto collaterale di essere molto più facile da guardare.

Tuttavia, fai attenzione che le altre soluzioni abbiano i loro vantaggi che probabilmente superano questa soluzione:

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

Puoi farlo nel modo seguente.

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

Quindi lascerà cadere la colonna se esiste già. E quindi aggiungi la colonna a una tabella particolare.


17
che dire di perdere dati?
Aliaksei Ramanau,

48
Puoi sempre scusarti con i tuoi clienti
konzo,

Avevo appena aggiunto la colonna, quindi questo è stato molto conveniente per me.
Noumenon,

-4

Controlla semplicemente se la query ha restituito un nome_colonna.

In caso contrario, eseguire qualcosa del genere:

ALTER TABLE x ADD COLUMN y int;

Dove metti qualcosa di utile per 'x' e 'y' e ovviamente un tipo di dati adatto dove ho usato int.


In che ambiente ti trovi? Hai una lingua di scripting alla tua proposta? O stai usando PL / pgSQL? Stai eseguendo da una lingua come PHP / Java / etc?
Erwin Moller,

Nessun linguaggio di scripting. Devo farlo solo con SQL . Ho un'applicazione Java che su input ottiene script SQL ed esegue quello script su db selezionato.
marioosh,

2
Quindi ti consiglio di esaminare pl / pgsql: postgresql.org/docs/9.1/static/plpgsql.html Creare una funzione che accetta column_name e table_name come argomenti.
Erwin Moller,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.