จะเพิ่มคอลัมน์ได้อย่างไรหากไม่มีอยู่ใน 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;
$$

คุณไม่สามารถผ่านสิ่งเหล่านี้เป็นพารามิเตอร์ได้คุณจะต้องทำการทดแทนตัวแปรในสตริงที่ฝั่งไคลเอ็นต์ แต่นี่เป็นแบบสอบถามแบบใช้ตัวเองที่จะส่งข้อความเฉพาะเมื่อคอลัมน์มีอยู่แล้วเพิ่มถ้ามันไม่ได้และ จะยังคงล้มเหลวในข้อผิดพลาดอื่น ๆ (เช่นประเภทข้อมูลที่ไม่ถูกต้อง)

ฉันไม่แนะนำให้ทำวิธีใด ๆ เหล่านี้หากสิ่งเหล่านี้เป็นสตริงแบบสุ่มที่มาจากแหล่งภายนอก ไม่ว่าคุณจะใช้วิธีใด (สตริงแบบไดนามิกด้านฝั่งหรือฝั่งเซิร์ฟเวอร์ที่ดำเนินการเป็นแบบสอบถาม) มันจะเป็นสูตรสำหรับภัยพิบัติเมื่อมันเปิดให้คุณโจมตี SQL injection


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ตัวอย่างเช่น
Tomasz Gandor

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เลย
Tomasz Gandor

4
ทำไมคำตอบนี้ที่ด้านล่างของหน้าจึงดีกว่าตัวเลือกอื่น ๆ
Ecksters

เพิ่งออกมาจากความอยากรู้: สิ่งนี้จะทำให้ล็อคการเข้าถึงบนโต๊ะ (และต้องมีหน้าต่างการบำรุงรักษาเมื่อทำงานบนตารางขนาดใหญ่ในฐานข้อมูลการผลิต)?
Hassan Baig

4
กองซ้อนมากเกินไปควรสนับสนุนการเปลี่ยนคำตอบที่ยอมรับ
Henrik Sommerland

@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 และ b) การตรวจสอบความถูกต้องของทั้งสองทันที (วิธีที่ถูกที่สุด) ชื่อคอลัมน์_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
ชื่อตารางและชื่อคอลัมน์ที่ซ้ำกันสามารถมีอยู่ได้ในหลายสกีมา
Mike Sherrill 'Cat Recall'

1
คุณอาจต้องการเขียนรหัสของคุณอีกครั้งเพื่อพิจารณาสคีมา
ไมค์ Sherrill 'Cat Recall'

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 ซึ่งเป็นส่วนเสริมล่าสุดของ Postgres
John Powell

1

นี่เป็นวิธีแก้ปัญหาจากโซล่า แต่เพิ่งทำความสะอาดนิดหน่อย มันแตกต่างกันมากพอที่ฉันไม่เพียงแค่ต้องการ "ปรับปรุง" วิธีแก้ปัญหาของเขา (บวกฉันคิดว่ามันหยาบคาย)

ความแตกต่างที่สำคัญคือมันใช้รูปแบบ EXECUTE ซึ่งฉันคิดว่ามันค่อนข้างสะอาดกว่า แต่ฉันเชื่อว่าหมายความว่าคุณต้องอยู่ใน PostgresSQL 9.1 หรือใหม่กว่า

สิ่งนี้ได้รับการทดสอบบน 9.1 และใช้งานได้ หมายเหตุ: มันจะเพิ่มข้อผิดพลาดหาก schema / 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
สิ่งที่เกี่ยวกับการสูญเสียข้อมูล?
Aliaksei Ramanau

48
คุณอาจขอโทษลูกค้าของคุณเสมอ
konzo

ฉันเพิ่งเพิ่มคอลัมน์ดังนั้นมันจึงสะดวกมากสำหรับฉัน
Noumenon

-4

เพียงตรวจสอบว่าแบบสอบถามส่งคืน column_name หรือไม่

ถ้าไม่ทำสิ่งนี้:

ALTER TABLE x ADD COLUMN y int;

ที่ที่คุณใส่สิ่งที่มีประโยชน์สำหรับ 'x' และ 'y' และแน่นอนประเภทข้อมูลที่เหมาะสมที่ฉันใช้ int


คุณอยู่ในสภาพแวดล้อมใด คุณมีภาษาสคริปต์ตามข้อเสนอของคุณหรือไม่? หรือคุณใช้ PL / pgSQL คุณกำลังเรียกใช้งานจากบางภาษาเช่น PHP / Java / etc หรือไม่?
Erwin Moller

ไม่มีภาษาสคริปต์ ฉันต้องทำสิ่งนี้ภายใน SQLเท่านั้น ฉันมีแอปพลิเคชัน Java ที่อินพุตรับสคริปต์ SQL และรันสคริปต์นั้นบน db ที่เลือก
marioosh

2
ถ้าอย่างนั้นฉันแนะนำให้คุณดูใน pl / pgsql: postgresql.org/docs/9.1/static/plpgsql.htmlสร้างฟังก์ชั่นที่ใช้ column_name และ table_name เป็นอาร์กิวเมนต์
Erwin Moller
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.