Bagaimana cara menambahkan kolom jika tidak ada di PostgreSQL?

145

Pertanyaannya sederhana. Bagaimana cara menambahkan kolom xke tabel y, tetapi hanya ketika xkolom tidak ada? Saya hanya menemukan solusi di sini cara memeriksa apakah kolom ada.

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

Jawaban:

133

Berikut ini versi singkat dan manis menggunakan pernyataan "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;
$$

Anda tidak dapat melewatkan ini sebagai parameter, Anda harus melakukan substitusi variabel dalam string di sisi klien, tetapi ini adalah kueri mandiri yang hanya memancarkan pesan jika kolom sudah ada, menambahkan jika tidak dan akan terus gagal pada kesalahan lain (seperti tipe data yang tidak valid).

Saya tidak menyarankan melakukan salah satu dari metode ini jika ini adalah string acak yang berasal dari sumber eksternal. Tidak peduli metode apa yang Anda gunakan (string dinamis sisi-sisi atau sisi-server dieksekusi sebagai kueri), itu akan menjadi resep untuk bencana karena membuka Anda terhadap serangan injeksi SQL.

Matthew Wood
sumber
4
DO $$ BEGIN BEGIN CREATE INDEX type_idx ON table1 USING btree (type); EXCEPTION WHEN duplicate_table THEN RAISE NOTICE 'Index exists.'; END; END;$$;pendekatan yang sama di CREATE INDEX;) Terima kasih atas jawaban Anda,
marioosh
Saya tidak yakin mengapa memulai blok kode anonim dengan DO $$gagal. Saya mencoba DO $$;yang gagal juga, sampai saya baru memulai blok dengan DO $$DECLARE r record;yang diberikan dalam contoh di doc postgres dev .
nemesisfixx
9
Menutup dengan END; $$adalah kesalahan sintaks (Postgres 9.3), saya harus menggunakan END $$;sebagai gantinya
LightSystem
5
Pendekatan yang baik, tetapi mengapa blok BEGIN / END bersarang? Ini berfungsi baik dengan satu layer untuk saya. Juga menambahkan tanda titik koma di akhir ($$;) membuat pernyataan tidak ambigu untuk psql.
Shane
1
Pendekatan ini ( EXCEPTION) sedikit lebih umum, dan dapat digunakan untuk tugas-tugas yang tidak memiliki IF NOT EXISTSsintaksis - misalnya ALTER TABLE ... ADD CONSTRAINT.
Tomasz Gandor
390

Dengan Postgres 9.6 ini dapat dilakukan menggunakan opsiif not exists

ALTER TABLE table_name ADD COLUMN IF NOT EXISTS column_name INTEGER;
seekor kuda tanpa nama
sumber
4
Manis. Sayangnya, ADD CONSTRAINT IF NOT EXISTSbelum ada .
Tomasz Gandor
4
Mengapa jawaban ini ada di bagian bawah halaman, ini jauh lebih baik daripada opsi lainnya.
Ecksters
Hanya ingin tahu: apakah ini akan menyebabkan kunci akses di atas meja (dan karenanya memerlukan jendela pemeliharaan saat dijalankan di atas meja besar di basis data produksi)?
Hassan Baig
4
Stack-overflow benar-benar harus mendukung perubahan jawaban yang diterima.
Henrik Sommerland
@HenrikSommerland: bahwa yang diperbolehkan - tetapi hanya oleh orang yang mengajukan pertanyaan.
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;

Panggilan:

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

Pengembalian TRUEsukses, kalau tidak FALSE(kolom sudah ada).
Menimbulkan pengecualian untuk tabel atau nama jenis tidak valid.

Mengapa versi lain?

  • Ini bisa dilakukan dengan DOpernyataan, tetapi DOpernyataan tidak bisa mengembalikan apa pun. Dan jika itu untuk penggunaan berulang, saya akan membuat fungsi.

  • Saya menggunakan jenis pengidentifikasi objek regclass dan regtypeuntuk _tbldan _typeyang) mencegah injeksi SQL dan b) memeriksa validitas keduanya segera (cara termurah yang mungkin). Nama kolom _colmasih harus dibersihkan untuk EXECUTEdengan quote_ident(). Penjelasan lebih lanjut dalam jawaban terkait ini:

  • format()membutuhkan Postgres 9.1+. Untuk versi lama digabungkan secara manual:

    EXECUTE 'ALTER TABLE ' || _tbl || ' ADD COLUMN ' || quote_ident(_col) || ' ' || _type;
  • Anda dapat memenuhi kualifikasi skema nama tabel Anda, tetapi Anda tidak harus melakukannya.
    Anda dapat mengutip dua kali pengidentifikasi dalam pemanggilan fungsi untuk menjaga kasing dan unta kata-kata yang dilindungi (tapi toh Anda tidak boleh menggunakan semua ini).

  • Saya meminta pg_catalogbukan information_schema. Penjelasan detail:

  • Blok yang mengandung EXCEPTIONklausa seperti jawaban yang saat ini diterima jauh lebih lambat. Ini umumnya lebih sederhana dan lebih cepat. Dokumentasi:

Tip: Blok yang mengandung EXCEPTIONklausa jauh lebih mahal untuk masuk dan keluar daripada blok tanpa klausa. Karena itu, jangan gunakan EXCEPTIONtanpa perlu.

Erwin Brandstetter
sumber
Saya suka solusi Anda lebih baik daripada milik saya! Lebih baik, lebih aman dan lebih cepat.
David S
Versi Postgres yang saya harus kerjakan tidak memiliki DOpernyataan, sedikit modifikasi untuk menerima DEFAULTdan ini bekerja dengan sempurna!
renab
18

Permintaan pilih berikut akan kembali true/false, menggunakan EXISTS()fungsi.

EXISTS () :
Argumen EXISTS adalah pernyataan SELECT sewenang-wenang, atau subquery. Subquery dievaluasi untuk menentukan apakah ia mengembalikan baris. Jika mengembalikan setidaknya satu baris, hasil dari EXISTS adalah "true"; jika subquery tidak mengembalikan baris, hasil dari EXISTS adalah "false"

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

dan gunakan pernyataan SQL dinamis berikut untuk mengubah tabel Anda

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
$$
Vivek S.
sumber
2
Nama tabel duplikat dan nama kolom bisa ada di beberapa skema.
Mike Sherrill 'Cat Recall'
1
Nah, Anda mungkin ingin menulis ulang kode Anda ke akun untuk skema.
Mike Sherrill 'Cat Recall'
2

Bagi mereka yang menggunakan Postgre 9.5+ (saya percaya sebagian besar dari Anda melakukannya), ada solusi yang cukup sederhana dan bersih

ALTER TABLE if exists <tablename> add if not exists <columnname> <columntype>
Leon
sumber
1

fungsi di bawah ini akan memeriksa kolom jika ada mengembalikan pesan yang sesuai lain itu akan menambahkan kolom ke tabel.

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
sumber
Memukul saya sebagai jawaban yang sangat masuk akal, terutama karena DO adalah tambahan baru-baru ini untuk postgres
John Powell
1

Ini pada dasarnya adalah solusi dari sola, tetapi baru dibersihkan sedikit. Cukup berbeda sehingga saya tidak hanya ingin "meningkatkan" solusinya (ditambah, saya kira itu tidak sopan).

Perbedaan utama adalah bahwa ia menggunakan format EXECUTE. Yang menurut saya sedikit lebih bersih, tapi saya percaya berarti Anda harus menggunakan PostgresSQL 9.1 atau yang lebih baru.

Ini telah diuji pada 9.1 dan berfungsi. Catatan: Ini akan memunculkan kesalahan jika skema / table_name / atau data_type tidak valid. Itu bisa "diperbaiki", tetapi mungkin perilaku yang benar dalam banyak kasus.

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

pemakaian:

select add_column('public', 'foo', 'bar', 'varchar(30)');
David S
sumber
0

Dapat ditambahkan ke fungsi memanggil skrip migrasi dan drop ketika selesai.

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();
pengguna645527
sumber
0

Dalam kasus saya, untuk alasan pembuatannya, agak sulit bagi skrip migrasi kami untuk memotong skema yang berbeda.

Untuk mengatasi ini, kami menggunakan pengecualian yang hanya menangkap dan mengabaikan kesalahan. Ini juga memiliki efek samping yang bagus karena jauh lebih mudah dilihat.

Namun, berhati-hatilah karena solusi lain memiliki kelebihan sendiri yang mungkin lebih penting daripada solusi ini:

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
sumber
-1

Anda bisa melakukannya dengan cara berikut.

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

Jadi itu akan menjatuhkan kolom jika sudah ada. Dan kemudian tambahkan kolom ke tabel tertentu.

parthivrshah
sumber
17
bagaimana dengan kehilangan data?
Aliaksei Ramanau
48
Anda dapat selalu meminta maaf kepada klien Anda
konzo
Saya baru saja menambahkan kolom, jadi ini sangat nyaman bagi saya.
Noumenon
-4

Cukup periksa apakah kueri mengembalikan nama_kolom.

Jika tidak, jalankan sesuatu seperti ini:

ALTER TABLE x ADD COLUMN y int;

Di mana Anda meletakkan sesuatu yang berguna untuk 'x' dan 'y' dan tentu saja tipe data yang cocok di mana saya menggunakan int.

Erwin Moller
sumber
Anda berada di lingkungan apa? Apakah Anda memiliki bahasa scripting di proposal Anda? Atau apakah Anda menggunakan PL / pgSQL? Apakah Anda mengeksekusi dari beberapa bahasa seperti PHP / Java / etc?
Erwin Moller
Tidak ada bahasa scripting. Saya perlu melakukan ini hanya dalam SQL . Saya punya aplikasi Java yang pada input mendapatkan skrip SQL dan menjalankan skrip itu pada db yang dipilih.
marioosh
2
Kemudian saya menyarankan Anda untuk melihat ke dalam pl / pgsql: postgresql.org/docs/9.1/static/plpgsql.html Buat fungsi yang menggunakan nama-nama kolom dan nama-nama tabel sebagai argumen.
Erwin Moller