How to add column if not exists on PostgreSQL?
Asked Answered
S

11

238

Question is simple. How to add column x to table y, but only when x column doesn't exist ? I found only solution here how to check if column exists.

SELECT column_name 
FROM information_schema.columns 
WHERE table_name='x' and column_name='y';
Sphericity answered 26/9, 2012 at 8:20 Comment(0)
R
157

Here's a short-and-sweet version using the "DO" statement:

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

You can't pass these as parameters, you'll need to do variable substitution in the string on the client side, but this is a self contained query that only emits a message if the column already exists, adds if it doesn't and will continue to fail on other errors (like an invalid data type).

I don't recommend doing ANY of these methods if these are random strings coming from external sources. No matter what method you use (client-side or server-side dynamic strings executed as queries), it would be a recipe for disaster as it opens you to SQL injection attacks.

Rourke answered 26/9, 2012 at 18:59 Comment(6)
DO $$ BEGIN BEGIN CREATE INDEX type_idx ON table1 USING btree (type); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'Index exists.'; END; END;$$; the same approach in CREATE INDEX ;) Thanks for Your answer,Sphericity
Am not sure why just starting the anonymous code block with DO $$ fails. I did try DO $$; which fails too, until I just started the block with DO $$DECLARE r record; which is given in an example on the dev postgres docs.Strawn
Closing with END; $$ is a syntax error (Postgres 9.3), I had to use END $$; insteadSchroeder
Good approach, but why the nested BEGIN/END blocks? It works fine with a single layer for me. Also adding a semicolon at the end ($$;) makes the statement unambiguous for psql.Ninfaningal
This approach (EXCEPTION) is a bit more general, and can be employed for tasks which have no IF NOT EXISTS syntax - for example ALTER TABLE ... ADD CONSTRAINT.Nuclei
@Matthew Wood, is it really necessary to have a nested BEGIN - END block? I tried it just with DO $$ BEGIN ... EXCEPTION ... END; $$; and it worked fineTucci
S
628

With Postgres 9.6 this can be done using the option if not exists

ALTER TABLE table_name ADD COLUMN IF NOT EXISTS column_name INTEGER;
Stoa answered 2/8, 2016 at 13:33 Comment(3)
Sweet. Unfortunately, there is no ADD CONSTRAINT IF NOT EXISTS yet.Nuclei
Just out of curiosity: will this cause an access lock on the table (and thus require a maintenance window when run on huge tables in production databases)?Talbott
@TomaszGandor But you can DROP CONSTRAINT IF EXISTS and then add it again.Spectral
R
157

Here's a short-and-sweet version using the "DO" statement:

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

You can't pass these as parameters, you'll need to do variable substitution in the string on the client side, but this is a self contained query that only emits a message if the column already exists, adds if it doesn't and will continue to fail on other errors (like an invalid data type).

I don't recommend doing ANY of these methods if these are random strings coming from external sources. No matter what method you use (client-side or server-side dynamic strings executed as queries), it would be a recipe for disaster as it opens you to SQL injection attacks.

Rourke answered 26/9, 2012 at 18:59 Comment(6)
DO $$ BEGIN BEGIN CREATE INDEX type_idx ON table1 USING btree (type); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'Index exists.'; END; END;$$; the same approach in CREATE INDEX ;) Thanks for Your answer,Sphericity
Am not sure why just starting the anonymous code block with DO $$ fails. I did try DO $$; which fails too, until I just started the block with DO $$DECLARE r record; which is given in an example on the dev postgres docs.Strawn
Closing with END; $$ is a syntax error (Postgres 9.3), I had to use END $$; insteadSchroeder
Good approach, but why the nested BEGIN/END blocks? It works fine with a single layer for me. Also adding a semicolon at the end ($$;) makes the statement unambiguous for psql.Ninfaningal
This approach (EXCEPTION) is a bit more general, and can be employed for tasks which have no IF NOT EXISTS syntax - for example ALTER TABLE ... ADD CONSTRAINT.Nuclei
@Matthew Wood, is it really necessary to have a nested BEGIN - END block? I tried it just with DO $$ BEGIN ... EXCEPTION ... END; $$; and it worked fineTucci
R
42

Postgres 9.6 added: IF NOT EXISTS

ALTER TABLE tbl
ADD COLUMN IF NOT EXISTS column_name;`

The following is mostly outdated now. You might use it in older Postgres versions, or a variation to check for more than just the column name.


CREATE OR REPLACE function f_add_col(_tbl regclass, _col  text, _type regtype)
  RETURNS bool
  LANGUAGE plpgsql AS
$func$
BEGIN
   IF EXISTS (SELECT 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$;

Call:

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

Returns true on success, else false (column already exists).
Raises an exception for invalid table or type name.

Why another version?

  • This could be done with a DO statement, but DO statements cannot return anything. And if it's for repeated use, I would create a function.

  • I use the object identifier types regclass and regtype for _tbl and _type which a) prevents SQL injection and b) checks validity of both immediately (cheapest possible way). The column name _col has still to be sanitized for EXECUTE with quote_ident(). See:

  • format() requires Postgres 9.1+. For older versions concatenate manually:

      EXECUTE 'ALTER TABLE ' || _tbl || ' ADD COLUMN ' || quote_ident(_col) || ' ' || _type;
    
  • You can schema-qualify your table name, but you don't have to.
    You can double-quote the identifiers in the function call to preserve camel-case and reserved words (but you shouldn't use any of this anyway).

  • I query pg_catalog instead of the information_schema. Detailed explanation:

  • Blocks containing an EXCEPTION clause are substantially slower.
    This is simpler and faster. The manual:

Tip

A block containing an EXCEPTION clause is significantly more expensive to enter and exit than a block without one. Therefore, don't use EXCEPTION without need.

Runkle answered 26/9, 2012 at 19:41 Comment(1)
The version of Postgres i have to work with doesn't have the DO statement, a slight modification to accept DEFAULT and this worked perfectly!Rightward
B
23

Following select query will return true/false, using EXISTS() function.

EXISTS():
The argument of EXISTS is an arbitrary SELECT statement, or subquery. The subquery is evaluated to determine whether it returns any rows. If it returns at least one row, the result of EXISTS is "true"; if the subquery returns no rows, the result of EXISTS is "false"

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

and use the following dynamic SQL statement to alter your table

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
$$
Brieta answered 3/12, 2014 at 8:5 Comment(2)
Duplicate table names and column names can exist in multiple schemas.Leprosy
Well, you might want to rewrite your code to account for schemas.Leprosy
D
8

For those who use Postgre 9.5+(I believe most of you do), there is a quite simple and clean solution

ALTER TABLE if exists <tablename> add if not exists <columnname> <columntype>
Doubletalk answered 20/2, 2020 at 1:41 Comment(2)
This syntax isn't valid on PG 9.5.7 (or any PG version, it would appear)Adage
Sorry I should mention I use PostgreSQL 11.10 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.3 20140911 (Red Hat 4.8.3-9), 64-bit. It is tested on my DB instance. @AdageDoubletalk
Z
1

the below function will check the column if exist return appropriate message else it will add the column to the table.

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;
$$
Zindman answered 26/9, 2012 at 10:28 Comment(0)
V
1

This is basically the solution from sola, but just cleaned up a bit. It's different enough that I didn't just want to "improve" his solution (plus, I sort of think that's rude).

Main difference is that it uses the EXECUTE format. Which I think is a bit cleaner, but I believe means that you must be on PostgresSQL 9.1 or newer.

This has been tested on 9.1 and works. Note: It will raise an error if the schema/table_name/or data_type are invalid. That could "fixed", but might be the correct behavior in many cases.

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

usage:

select add_column('public', 'foo', 'bar', 'varchar(30)');
Varix answered 26/9, 2012 at 14:23 Comment(0)
R
1

Can be added to migration scripts invoke function and drop when done.

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();
Radburn answered 12/3, 2014 at 13:57 Comment(0)
A
0

In my case, for how it was created reason it is a bit difficult for our migration scripts to cut across different schemas.

To work around this we used an exception that just caught and ignored the error. This also had the nice side effect of being a lot easier to look at.

However, be wary that the other solutions have their own advantages that probably outweigh this solution:

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 $$;
Apocopate answered 19/8, 2019 at 22:24 Comment(0)
S
-4

You can do it by following way.

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

So it will drop the column if it is already exists. And then add the column to particular table.

Salem answered 29/3, 2016 at 4:51 Comment(1)
what about loosing data?Jamnes
H
-5

Simply check if the query returned a column_name.

If not, execute something like this:

ALTER TABLE x ADD COLUMN y int;

Where you put something useful for 'x' and 'y' and of course a suitable datatype where I used int.

Hollis answered 26/9, 2012 at 8:23 Comment(3)
What environment are you in? Do you have a scriptinglanguage at your proposal? Or are you using PL/pgSQL? Are you executing from some language like PHP/Java/etc?Hollis
No scripting language. I need to do this only within SQL. I have Java application that on input get SQL script and run that script on selected db.Sphericity
Then I advise you to look into pl/pgsql: postgresql.org/docs/9.1/static/plpgsql.html Create a function that takes column_name and table_name as arguments.Hollis

© 2022 - 2024 — McMap. All rights reserved.