Làm cách nào để thêm cột nếu không tồn tại trên PostgreSQL?


145

Câu hỏi rất đơn giản. Làm cách nào để thêm cột xvào bảng y, nhưng chỉ khi xcột không tồn tại? Tôi chỉ tìm thấy giải pháp ở đây làm thế nào để kiểm tra nếu cột tồn tại.

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

Câu trả lời:


133

Đây là phiên bản ngắn và ngọt bằng cách sử dụng câu lệnh "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;
$$

Bạn không thể truyền các tham số này dưới dạng tham số, bạn sẽ cần thay thế chuỗi trong phía máy khách, nhưng đây là truy vấn độc lập chỉ phát ra một thông báo nếu cột đã tồn tại, thêm nếu nó không và sẽ tiếp tục thất bại với các lỗi khác (như kiểu dữ liệu không hợp lệ).

Tôi không khuyên bạn nên thực hiện BẤT K of các phương pháp này nếu đây là các chuỗi ngẫu nhiên đến từ các nguồn bên ngoài. Bất kể bạn sử dụng phương pháp nào (chuỗi động phía cleint hoặc phía máy chủ được thực thi dưới dạng truy vấn), đó sẽ là một công thức cho thảm họa khi nó mở ra cho bạn các cuộc tấn công SQL 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;$$;cùng một cách tiếp cận trong CREATE INDEX;) Cảm ơn câu trả lời của bạn,
marioosh

Tôi không chắc chắn tại sao chỉ bắt đầu khối mã ẩn danh với DO $$thất bại. Tôi cũng đã thử DO $$;thất bại, cho đến khi tôi mới bắt đầu khối DO $$DECLARE r record;được đưa ra trong một ví dụ về tài liệu dev postgres .
nemeisfixx

9
Đóng với END; $$là một lỗi cú pháp (Postgres 9.3), END $$;thay vào đó tôi phải sử dụng
LightSystem

5
Cách tiếp cận tốt, nhưng tại sao các khối BEGIN / END lồng nhau? Nó hoạt động tốt với một lớp duy nhất cho tôi. Ngoài ra việc thêm một dấu chấm phẩy ở cuối ($$;) làm cho câu lệnh không rõ ràng cho psql.
Shane

1
Cách tiếp cận này ( EXCEPTION) tổng quát hơn một chút và có thể được sử dụng cho các tác vụ không có IF NOT EXISTScú pháp - ví dụ ALTER TABLE ... ADD CONSTRAINT.
Tomasz Gandor

389

Với Postgres 9.6, điều này có thể được thực hiện bằng cách sử dụng tùy chọnif not exists

ALTER TABLE table_name ADD COLUMN IF NOT EXISTS column_name INTEGER;

4
Ngọt. Thật không may, không có ADD CONSTRAINT IF NOT EXISTS.
Tomasz Gandor

4
Tại sao câu trả lời này ở dưới cùng của trang, nó tốt hơn nhiều so với các tùy chọn khác.
Ecksters

Vì tò mò: điều này có gây ra khóa truy cập trên bàn không (và do đó cần có cửa sổ bảo trì khi chạy trên các bảng lớn trong cơ sở dữ liệu sản xuất)?
Hassan Baig

4
Tràn ngăn xếp thực sự nên hỗ trợ thay đổi câu trả lời được chấp nhận.
Henrik Sommerland

@HenrikSommerland: điều đó được cho phép - nhưng chỉ bởi người hỏi câu hỏi.
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;

Gọi:

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

Trả TRUEvề thành công, khác FALSE(cột đã tồn tại).
Tăng một ngoại lệ cho tên bảng hoặc loại không hợp lệ.

Tại sao lại là phiên bản khác?

  • Điều này có thể được thực hiện với một DOtuyên bố, nhưng các DOtuyên bố không thể trả lại bất cứ điều gì. Và nếu nó được sử dụng nhiều lần, tôi sẽ tạo ra một chức năng.

  • Tôi sử dụng loại nhận dạng đối tượng regclassregtypecho _tbl_typeđó a) ngăn chặn SQL injection và b) kiểm tra tính hợp lệ của cả hai ngay lập tức (cách giá rẻ nhất có thể). Tên cột _colvẫn phải được làm vệ sinh cho EXECUTEvới quote_ident(). Giải thích thêm trong câu trả lời liên quan này:

  • format()yêu cầu Postgres 9.1+. Đối với các phiên bản cũ hơn, ghép thủ công:

    EXECUTE 'ALTER TABLE ' || _tbl || ' ADD COLUMN ' || quote_ident(_col) || ' ' || _type;
  • Bạn có thể đủ điều kiện lược đồ tên bảng của bạn, nhưng bạn không phải.
    Bạn có thể trích dẫn hai số định danh trong lệnh gọi hàm để bảo vệ trường hợp lạc đà và các từ dành riêng (nhưng dù sao bạn cũng không nên sử dụng bất kỳ từ nào trong số này).

  • Tôi truy vấn pg_catalogthay vì information_schema. Giải thích chi tiết:

  • Các khối chứa một EXCEPTIONmệnh đề như câu trả lời hiện được chấp nhận chậm hơn đáng kể. Điều này thường đơn giản và nhanh hơn. Các tài liệu:

Mẹo: Một khối chứa một EXCEPTIONmệnh đề đắt hơn đáng kể để nhập và thoát so với một khối không có một mệnh đề. Do đó, không sử dụng EXCEPTIONmà không cần.


Tôi thích giải pháp của bạn hơn của tôi! Nó tốt hơn, an toàn hơn và nhanh hơn.
David S

Phiên bản Postgres tôi phải làm việc không có DOtuyên bố, một sửa đổi nhỏ để chấp nhận DEFAULTvà điều này hoạt động hoàn hảo!
renab

18

Sau khi chọn truy vấn sẽ trở lại true/false, sử dụng EXISTS()chức năng.

EXISTS () :
Đối số của EXISTS là một câu lệnh CHỌN hoặc truy vấn con tùy ý. Truy vấn con được đánh giá để xác định xem nó có trả về bất kỳ hàng nào không. Nếu nó trả về ít nhất một hàng, kết quả của EXISTS là "true"; nếu truy vấn con trả về không có hàng, kết quả của EXISTS là "false"

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

và sử dụng câu lệnh SQL động sau đây để thay đổi bảng của bạn

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
Tên bảng trùng lặp và tên cột có thể tồn tại trong nhiều lược đồ.
Mike Sherrill 'Nhớ lại mèo'

1
Chà, bạn có thể muốn viết lại mã của mình vào tài khoản cho các lược đồ.
Mike Sherrill 'Nhớ lại mèo'

2

Đối với những người sử dụng Postgre 9.5+ (tôi tin rằng hầu hết các bạn đều làm như vậy), có một giải pháp khá đơn giản và sạch sẽ

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

1

hàm dưới đây sẽ kiểm tra cột nếu tồn tại trả về thông báo thích hợp khác, nó sẽ thêm cột vào bảng.

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

Đây là một câu trả lời rất hợp lý, đặc biệt là DO là một bổ sung gần đây cho các postgres
John Powell

1

Đây về cơ bản là giải pháp từ sola, nhưng chỉ cần làm sạch một chút. Nó đủ khác biệt đến nỗi tôi không chỉ muốn "cải thiện" giải pháp của anh ấy (cộng với, tôi nghĩ rằng điều đó thật thô lỗ).

Sự khác biệt chính là nó sử dụng định dạng EXECUTE. Mà tôi nghĩ là sạch hơn một chút, nhưng tôi tin rằng bạn phải có trên PostgresQuery 9.1 hoặc mới hơn.

Điều này đã được thử nghiệm trên 9.1 và hoạt động. Lưu ý: Nó sẽ phát sinh lỗi nếu lược đồ / tên_bảng / hoặc data_type không hợp lệ. Điều đó có thể "cố định", nhưng có thể là hành vi đúng trong nhiều trường hợp.

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

sử dụng:

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

0

Có thể được thêm vào các kịch bản di chuyển gọi hàm và thả khi hoàn tất.

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

Trong trường hợp của tôi, vì lý do nó được tạo ra như thế nào, có một chút khó khăn cho các tập lệnh di chuyển của chúng tôi để cắt qua các lược đồ khác nhau.

Để khắc phục điều này, chúng tôi đã sử dụng một ngoại lệ vừa bắt và bỏ qua lỗi. Điều này cũng có tác dụng phụ tốt đẹp là dễ nhìn hơn rất nhiều.

Tuy nhiên, hãy cảnh giác rằng các giải pháp khác có những ưu điểm riêng có thể vượt trội hơn giải pháp này:

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

Bạn có thể làm điều đó bằng cách làm theo.

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

Vì vậy, nó sẽ thả cột nếu nó đã tồn tại. Và sau đó thêm cột vào bảng cụ thể.


17
Mất dữ liệu thì sao?
Aliaksei Ramanau

48
Bạn luôn có thể xin lỗi khách hàng của mình
konzo

Tôi vừa mới thêm cột, vì vậy điều này rất thuận tiện cho tôi.
Noumenon

-4

Chỉ cần kiểm tra nếu truy vấn trả về một cột_name.

Nếu không, thực hiện một cái gì đó như thế này:

ALTER TABLE x ADD COLUMN y int;

Nơi bạn đặt một cái gì đó hữu ích cho 'x' và 'y' và tất nhiên là một kiểu dữ liệu phù hợp nơi tôi đã sử dụng int.


Bạn ở môi trường nào Bạn có một ngôn ngữ kịch bản theo đề nghị của bạn? Hay bạn đang sử dụng PL / pgSQL? Bạn đang thực hiện từ một số ngôn ngữ như PHP / Java / etc?
Erwin Moller

Không có ngôn ngữ kịch bản. Tôi cần phải làm điều này chỉ trong SQL . Tôi có ứng dụng Java mà trên đầu vào có tập lệnh SQL và chạy tập lệnh đó trên db đã chọn.
marioosh

2
Sau đó, tôi khuyên bạn nên xem xét pl / pssql: postgresql.org/docs/9.1/static/plpgsql.html Tạo một hàm lấy cột_name và tên_bảng làm đối số.
Erwin Moller
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.