Як додати стовпчик, якщо його немає на PostgreSQL?


145

Питання просте. Як додати стовпчик xдо таблиці y, але лише тоді, коли xстовпець не існує? Я знайшов тільки рішення тут , як перевірити , якщо стовпець існує.

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

Відповіді:


133

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

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

Я не рекомендую робити ЯКЩОсь із цих методів, якщо це випадкові рядки, що надходять із зовнішніх джерел. Незалежно від того, яким методом ви користуєтесь (динамічні рядки на стороні cleint або на сервері, виконані як запити), це був би рецептом катастрофи, оскільки він відкриває вас для атак ін'єкції 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;якого наведено в прикладі на документах dev postgres .
nemesisfixx

9
Закриття з END; $$- це синтаксична помилка (Postgres 9.3), я повинен був використати END $$;замість цього
LightSystem

5
Хороший підхід, але чому вкладені блоки BEGIN / END? Це чудово працює з одним шаром для мене. Також додавання крапки з комою в кінці ($$;) робить заяву однозначним для psql.
Шейн

1
Цей підхід ( EXCEPTION) трохи загальніший, і його можна використовувати для завдань, які не мають IF NOT EXISTSсинтаксису - наприклад ALTER TABLE ... ADD CONSTRAINT.
Томаш

390

З Postgres 9.6 це можна зробити за допомогою параметраif not exists

ALTER TABLE table_name ADD COLUMN IF NOT EXISTS column_name INTEGER;

4
Солодке. На жаль, поки ADD CONSTRAINT IF NOT EXISTSнемає.
Томаш

4
Чому ця відповідь внизу сторінки, це ТАК набагато краще, ніж інші варіанти.
Ecksters

Лише з цікавості: чи це спричинить блокування доступу до столу (і, таким чином, вимагатиме вікна технічного обслуговування при запуску на величезних таблицях у виробничих базах даних)?
Хасан Байг

4
Переповнення стека дійсно повинно підтримувати зміну прийнятої відповіді.
Генрік

@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без потреби.


Мені подобається ваше рішення краще, ніж моє! Це краще, безпечніше і швидше.
David S

Версія Postgres, з якою я маю працювати, не має DOтвердження, незначна зміна прийняття, DEFAULTі це спрацювало чудово!
renab

18

Після вибору запит повернеться true/false, використовуючи EXISTS()функцію.

EXISTS () :
Аргумент EXISTS - це довільний оператор SELECT або підзапит. Підзапит оцінюється, щоб визначити, чи повертає він будь-які рядки. Якщо він повертає щонайменше один рядок, результат EXISTS - "true"; якщо підзапит не повертає рядків, результат EXISTS - "false"

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
Дублюючі назви таблиць та назви стовпців можуть існувати у кількох схемах.
Майк Шеррілл 'Відкликання котів'

1
Ну, можливо, ви захочете переписати свій код для обліку схем.
Майк Шеррілл 'Відкликання котів'

2

Для тих, хто використовує Postgre 9.5+ (я вважаю, що більшість з вас це робить), є досить просте і чисте рішення

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

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

Вважає мене дуже розумною відповіддю, тим більше, що DO - це нещодавнє доповнення до постгресів
Джон Пауелл,

1

Це в основному рішення від соли, але просто трохи очищене. Настільки різні, що я не просто хотів "вдосконалити" його рішення (плюс, я думаю, що це грубо).

Основна відмінність полягає в тому, що він використовує формат EXECUTE. Я думаю, що трохи чистіше, але я вважаю, що ви повинні бути на PostgresSQL 9.1 або новішій версії.

Це було перевірено на 9.1 і працює. Примітка. Якщо помилка схеми / table_name / або data_type недійсна, це призведе до помилки. Це може бути "виправленим", але у багатьох випадках може бути правильною поведінкою.

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

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

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

-1

Зробити це можна наступним чином.

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

Тож він скине колонку, якщо вона вже існує. А потім додайте стовпець до конкретної таблиці.


17
що з втратою даних?
Аліаксей Раманау

48
Ви завжди можете вибачитися перед клієнтами
konzo

Я щойно додав стовпчик, тому це було дуже зручно для мене.
Номенон

-4

Просто перевірте, чи запит повертав ім'я стовпця.

Якщо ні, виконайте щось подібне:

ALTER TABLE x ADD COLUMN y int;

Де ви помістили щось корисне для 'x' та 'y' і, звичайно, відповідний тип даних, де я використовував int.


У якому середовищі ви знаходитесь? Чи є у вас пропозиція мовного сценарію? Або ви використовуєте PL / pgSQL? Ви виконуєте якусь мову, наприклад PHP / Java / тощо?
Ервін Моллер

Немає мови сценаріїв. Мені потрібно це робити тільки в межах SQL . У мене є програма Java, яка на вході отримує скрипт SQL і запускає цей скрипт на вибраному db.
marioosh

2
Тоді я раджу розібратися в pl / pgsql: postgresql.org/docs/9.1/static/plpgsql.html Створіть функцію, яка приймає ім'я стовпця та ім'я таблиці_назвами як аргументи.
Ервін Моллер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.