Как добавить столбец, если его нет в PostgreSQL?

154

Вопрос простой. Как добавить столбец xв таблицу y, но только если xстолбец не существует? Я нашел здесь единственное решение, как проверить, существует ли столбец.

SELECT column_name 
FROM information_schema.columns 
WHERE table_name='x' and column_name='y';
Мариуш
источник

Ответы:

136

Вот краткая и приятная версия с использованием оператора "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;
$$

Вы не можете передать их в качестве параметров, вам нужно будет выполнить замену переменной в строке на стороне клиента, но это автономный запрос, который выдает сообщение только в том случае, если столбец уже существует, добавляет, если нет и продолжит сбой при других ошибках (например, недопустимый тип данных).

Я не рекомендую использовать ЛЮБОЙ из этих методов, если это случайные строки, поступающие из внешних источников. Независимо от того, какой метод вы используете (на стороне клиента или на стороне сервера динамические строки, выполняемые как запросы), это будет рецептом катастрофы, поскольку он откроет вам атаки 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;$$;такой же подход в CREATE INDEX;) Спасибо за Ваш ответ,
marioosh
Не знаю, почему просто запустить анонимный блок кода с DO $$ошибкой. Я тоже пробовал, DO $$;что тоже терпит неудачу, пока я просто не запустил блок, DO $$DECLARE r record;который приведен в примере в документации разработчиков postgres .
nemesisfixx
9
Закрытие с END; $$синтаксической ошибкой (Postgres 9.3), мне пришлось использовать END $$;вместо этого
LightSystem
5
Хороший подход, но почему вложенные блоки BEGIN / END? У меня он отлично работает с одним слоем. Также добавление точки с запятой в конце ($$;) делает утверждение недвусмысленным для psql.
Шейн
1
Этот подход ( EXCEPTION) является немного более общим и может использоваться, например, для задач, не имеющих IF NOT EXISTSсинтаксиса ALTER TABLE ... ADD CONSTRAINT.
Tomasz
418

В Postgres 9.6 это можно сделать с помощью опцииif not exists

ALTER TABLE table_name ADD COLUMN IF NOT EXISTS column_name INTEGER;
a_horse_with_no_name
источник
5
Милая. К сожалению, пока нет ADD CONSTRAINT IF NOT EXISTS.
Tomasz
4
Почему этот ответ внизу страницы, он НАМНОГО лучше других вариантов.
Ecksters
Просто из любопытства: вызовет ли это блокировку доступа к таблице (и, следовательно, потребуется окно обслуживания при запуске на огромных таблицах в производственных базах данных)?
Hassan
4
Переполнение стека действительно должно поддерживать изменение принятого ответа.
Henrik
1
@HenrikSommerland: что это разрешено - но только человек , который задал этот вопрос.
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;

Вызов:

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

Возврат TRUEв случае успеха, иначе FALSE(столбец уже существует).
Вызывает исключение для недопустимой таблицы или имени типа.

Почему другая версия?

  • Это можно сделать с помощью DOоператора, но DOоператоры ничего не могут вернуть. А если он для многократного использования, я бы создал функцию.

  • Я использую типы идентификаторов объектов regclass и regtypeдля _tblи _typeкоторый а) препятствует SQL инъекции и б) проверяет правильность обоих сразу (дешевый возможный путь). Имя столбца _colимеет еще продезинфицировать для EXECUTEс quote_ident(). Дополнительные объяснения в этом связанном ответе:

  • format()требуется Postgres 9.1+. Для более старых версий объедините вручную:

    EXECUTE 'ALTER TABLE ' || _tbl || ' ADD COLUMN ' || quote_ident(_col) || ' ' || _type;
    
  • Вы можете указать имя своей таблицы с помощью схемы, но это не обязательно.
    Вы можете заключить идентификаторы в двойные кавычки в вызове функции, чтобы сохранить верблюжий регистр и зарезервированные слова (но вы все равно не должны использовать это).

  • Я запрашиваю pg_catalogвместо information_schema. Детальное объяснение:

  • Блоки, содержащие EXCEPTIONпредложение, подобное принятому в настоящее время ответу, работают значительно медленнее. Как правило, это проще и быстрее. Документация:

Совет: блок, содержащий EXCEPTIONпредложение, значительно дороже для входа и выхода, чем блок без него. Поэтому не используйте EXCEPTIONбез надобности.

Эрвин Брандштеттер
источник
Мне ваше решение нравится больше, чем мое! Так лучше, безопаснее и быстрее.
Дэвид С.
Версия Postgres, с которой я должен работать, не имеет DOинструкции, небольшая модификация, которую нужно принять, DEFAULTи это сработало отлично!
renab
19

Следующий запрос выбора вернется true/falseс использованием EXISTS()функции.

EXISTS () :
аргументом EXISTS является произвольный оператор SELECT или подзапрос. Подзапрос оценивается, чтобы определить, возвращает ли он какие-либо строки. Если он возвращает хотя бы одну строку, результатом EXISTS будет «истина»; если подзапрос не возвращает строк, результатом EXISTS будет "ложь"

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

и используйте следующий динамический оператор SQL, чтобы изменить свою таблицу

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
Повторяющиеся имена таблиц и столбцов могут существовать в нескольких схемах.
Майк Шерилл 'Cat Recall'
1
Что ж, вы можете захотеть переписать свой код, чтобы учесть схемы.
Майк Шерилл 'Cat Recall'
1

приведенная ниже функция проверит столбец, если он существует, вернет соответствующее сообщение, иначе он добавит столбец в таблицу.

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
источник
Мне кажется, что это очень разумный ответ, тем более что DO - недавнее дополнение к postgres
Джон Пауэлл
1

Это в основном решение от sola, но немного поправленное. Это достаточно другое, что я не просто хотел «улучшить» его решение (плюс, я думаю, что это грубо).

Основное отличие состоит в том, что он использует формат EXECUTE. Я думаю, что это немного чище, но я считаю, что это означает, что вы должны быть на PostgresSQL 9.1 или новее.

Это было протестировано на 9.1 и работает. Примечание: это вызовет ошибку, если схема / имя_таблицы / или тип_данных недействительны. Это могло быть «исправлено», но во многих случаях могло быть правильным поведением.

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

Применение:

select add_column('public', 'foo', 'bar', 'varchar(30)');
Дэвид С.
источник
1

Для тех, кто использует Postgre 9.5+ (я думаю, что большинство из вас это делает), есть довольно простое и понятное решение.

ALTER TABLE if exists <tablename> add if not exists <columnname> <columntype>
Леон
источник
Этот синтаксис недействителен для PG 9.5.7 (или, похоже, любой версии PG)
BrDaHa
0

Может быть добавлен в сценарии миграции, вызывающие функцию и отбрасываемую по завершении.

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
источник
0

В моем случае, из-за того, как это было создано, нашим сценариям миграции немного сложно пересекать разные схемы.

Чтобы обойти это, мы использовали исключение, которое просто перехватило и проигнорировало ошибку. Это также имело приятный побочный эффект - на него было намного проще смотреть.

Однако имейте в виду, что другие решения имеют свои преимущества, которые, вероятно, перевешивают это решение:

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
источник
-1

Сделать это можно следующим образом.

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

Таким образом, он удалит столбец, если он уже существует. А затем добавьте столбец в конкретную таблицу.

парфивршах
источник
17
как насчет потери данных?
Aliaksei Ramanau,
48
Вы всегда можете извиниться перед своими клиентами
Konzo
Я только что добавил столбец, так что это было очень удобно для меня.
Noumenon
-4

Просто проверьте, вернул ли запрос имя_столбца.

Если нет, выполните что-то вроде этого:

ALTER TABLE x ADD COLUMN y int;

Где вы помещаете что-то полезное для 'x' и 'y' и, конечно же, подходящий тип данных, где я использовал int.

Эрвин Моллер
источник
В какой среде вы находитесь? Есть ли у вас по вашему предложению язык сценариев? Или вы используете PL / pgSQL? Вы выполняете работу с какого-либо языка, например PHP / Java / и т. Д.?
Эрвин Моллер,
Нет скриптового языка. Мне нужно делать это только в SQL . У меня есть приложение Java, которое на входе получает сценарий SQL и запускает этот сценарий на выбранной базе данных.
marioosh
2
Затем я советую вам заглянуть в pl / pgsql: postgresql.org/docs/9.1/static/plpgsql.html Создайте функцию, которая принимает в качестве аргументов имя_столбца и имя_таблицы.
Эрвин Моллер,