สร้าง PostgreSQL ROLE (ผู้ใช้) หากไม่มีอยู่


123

ฉันจะเขียนสคริปต์ SQL เพื่อสร้าง ROLE ใน PostgreSQL 9.1 ได้อย่างไร แต่จะไม่เกิดข้อผิดพลาดหากมีอยู่แล้ว

สคริปต์ปัจจุบันมี:

CREATE ROLE my_user LOGIN PASSWORD 'my_password';

สิ่งนี้จะล้มเหลวหากมีผู้ใช้อยู่แล้ว ฉันต้องการสิ่งที่ต้องการ:

IF NOT EXISTS (SELECT * FROM pg_user WHERE username = 'my_user')
BEGIN
    CREATE ROLE my_user LOGIN PASSWORD 'my_password';
END;

... แต่ไม่ได้ผล - IFดูเหมือนจะไม่รองรับใน SQL ธรรมดา

ฉันมีไฟล์แบตช์ที่สร้างฐานข้อมูล PostgreSQL 9.1 บทบาทและสิ่งอื่น ๆ อีกสองสามอย่าง เรียกว่า psql.exe ส่งผ่านในชื่อของสคริปต์ SQL เพื่อเรียกใช้ จนถึงตอนนี้สคริปต์ทั้งหมดเหล่านี้เป็น SQL ธรรมดาและฉันต้องการหลีกเลี่ยง PL / pgSQL และถ้าเป็นไปได้

คำตอบ:


157

ลดความซับซ้อนในรูปแบบที่คล้ายกับสิ่งที่คุณคิดไว้:

DO
$do$
BEGIN
   IF NOT EXISTS (
      SELECT FROM pg_catalog.pg_roles  -- SELECT list can be empty for this
      WHERE  rolname = 'my_user') THEN

      CREATE ROLE my_user LOGIN PASSWORD 'my_password';
   END IF;
END
$do$;

(สร้างจากคำตอบของ@a_horse_with_no_nameและปรับปรุงด้วยความคิดเห็นของ @ Gregory )

ซึ่งแตกต่างจากตัวอย่างเช่นCREATE TABLEไม่มีIF NOT EXISTSประโยคสำหรับCREATE ROLE(อย่างน้อยหน้า 12) และคุณไม่สามารถเรียกใช้คำสั่ง DDL แบบไดนามิกใน SQL ธรรมดาได้

คำขอ "หลีกเลี่ยง PL / pgSQL" ของคุณเป็นไปไม่ได้ยกเว้นโดยใช้ PL อื่น DOคำสั่งการใช้งานเป็นภาษา plpgsql ขั้นตอนเริ่มต้น ไวยากรณ์อนุญาตให้ละเว้นการประกาศที่ชัดเจน:

DO [ LANGUAGE lang_name ] code
... ชื่อของภาษาขั้นตอนรหัสถูกเขียนใน. หากละเว้นเริ่มต้นคือ
lang_name
plpgsql


1
@Alberto: pg_userและpg_rolesถูกต้องทั้งคู่ ยังคงเป็นกรณีในเวอร์ชัน 9.3 ปัจจุบันและจะไม่มีการเปลี่ยนแปลงในเร็ว ๆ นี้
Erwin Brandstetter

2
@ เคน: หาก$มีความหมายพิเศษในลูกค้าของคุณคุณต้องหลีกเลี่ยงมันตามกฎไวยากรณ์ของลูกค้าของคุณ ลองหนี$ด้วย\$ใน Linux shell หรือเริ่มคำถามใหม่ - ความคิดเห็นไม่ใช่สถานที่ คุณสามารถเชื่อมโยงไปยังบริบทนี้ได้ตลอดเวลา
Erwin Brandstetter

1
ฉันใช้ 9.6 และถ้าผู้ใช้ถูกสร้างขึ้นด้วย NOLOGIN พวกเขาจะไม่ปรากฏในตาราง pg_user แต่จะปรากฏในตาราง pg_roles pg_roles จะเป็นทางออกที่ดีกว่าที่นี่หรือไม่
Jess

2
@ErwinBrandstetter สิ่งนี้ใช้ไม่ได้กับบทบาทที่มี NOLOGIN แสดงใน pg_roles แต่ไม่ปรากฏใน pg_user
Gregory Arenius

2
โซลูชันนี้ทนทุกข์ทรมานจากสภาวะการแข่งขัน คำตอบนี้มีรูปแบบที่ปลอดภัยกว่าระบุไว้
blubb

61

ทนทุกข์ทรมานจากคำตอบที่ได้รับการยอมรับสภาพการแข่งขันถ้าสองสคริปต์ดังกล่าวจะดำเนินการควบคู่กันไปใน Postgres คลัสเตอร์เดียวกัน (เซิร์ฟเวอร์ DB) ราวกับเป็นเรื่องธรรมดาในสภาพแวดล้อมอย่างต่อเนื่องบูรณาการ

โดยทั่วไปจะปลอดภัยกว่าถ้าพยายามสร้างบทบาทและจัดการกับปัญหาอย่างสง่างามเมื่อสร้างขึ้น:

DO $$
BEGIN
  CREATE ROLE my_role WITH NOLOGIN;
  EXCEPTION WHEN DUPLICATE_OBJECT THEN
  RAISE NOTICE 'not creating role my_role -- it already exists';
END
$$;

2
ฉันชอบวิธีนี้เพราะมันเป็นหนังสือรับรองที่มีอยู่
Matias Barone

2
DUPLICATE_OBJECTOTHERSเป็นเงื่อนไขที่แม่นยำในกรณีนี้ถ้าคุณไม่ต้องการที่จะจับเพียงเกี่ยวกับเงื่อนไขทั้งหมดที่มี
Danek Duvall

43

หรือถ้าบทบาทไม่ใช่เจ้าของวัตถุ db ใด ๆ ที่สามารถใช้ได้:

DROP ROLE IF EXISTS my_user;
CREATE ROLE my_user LOGIN PASSWORD 'my_password';

แต่ถ้าทิ้งผู้ใช้นี้จะไม่ทำอันตรายใด ๆ


10

ทางเลือกBash (สำหรับBash scripting ):

psql -h localhost -U postgres -tc \
"SELECT 1 FROM pg_user WHERE usename = 'my_user'" \
| grep -q 1 \
|| psql -h localhost -U postgres \
-c "CREATE ROLE my_user LOGIN PASSWORD 'my_password';"

(ไม่ใช่คำตอบสำหรับคำถามนี้สำหรับผู้ที่อาจมีประโยชน์เท่านั้น)


3
ควรอ่านFROM pg_roles WHERE rolnameแทนFROM pg_user WHERE usename
Barth

8

นี่คือวิธีแก้ปัญหาทั่วไปโดยใช้ plpgsql:

CREATE OR REPLACE FUNCTION create_role_if_not_exists(rolename NAME) RETURNS TEXT AS
$$
BEGIN
    IF NOT EXISTS (SELECT * FROM pg_roles WHERE rolname = rolename) THEN
        EXECUTE format('CREATE ROLE %I', rolename);
        RETURN 'CREATE ROLE';
    ELSE
        RETURN format('ROLE ''%I'' ALREADY EXISTS', rolename);
    END IF;
END;
$$
LANGUAGE plpgsql;

การใช้งาน:

posgres=# SELECT create_role_if_not_exists('ri');
 create_role_if_not_exists 
---------------------------
 CREATE ROLE
(1 row)
posgres=# SELECT create_role_if_not_exists('ri');
 create_role_if_not_exists 
---------------------------
 ROLE 'ri' ALREADY EXISTS
(1 row)

8

คำตอบบางคำแนะนำให้ใช้รูปแบบ: ตรวจสอบว่าไม่มีบทบาทอยู่หรือไม่และหากไม่มีให้ออกCREATE ROLEคำสั่ง สิ่งนี้มีข้อเสียอย่างหนึ่งคือสภาพการแข่งขัน หากมีผู้อื่นสร้างบทบาทใหม่ระหว่างการตรวจสอบและการออกCREATE ROLEคำสั่งจะCREATE ROLEเห็นได้ชัดว่าล้มเหลวด้วยข้อผิดพลาดร้ายแรง

เพื่อแก้ปัญหาข้างต้นคำตอบอื่น ๆ ที่กล่าวถึงแล้วการใช้งานการPL/pgSQLออกCREATE ROLEโดยไม่มีเงื่อนไขแล้วจับข้อยกเว้นจากการโทรนั้น มีเพียงปัญหาเดียวในการแก้ปัญหาเหล่านี้ พวกเขาทิ้งข้อผิดพลาดใด ๆ อย่างเงียบ ๆ รวมถึงข้อผิดพลาดที่ไม่ได้สร้างขึ้นจากข้อเท็จจริงที่มีบทบาทอยู่แล้ว CREATE ROLEสามารถโยนข้อผิดพลาดอื่น ๆ ได้และการจำลองIF NOT EXISTSควรปิดเสียงข้อผิดพลาดเมื่อมีบทบาทอยู่แล้วเท่านั้น

CREATE ROLEเกิดduplicate_objectข้อผิดพลาดเมื่อมีบทบาทอยู่แล้ว และตัวจัดการข้อยกเว้นควรตรวจจับข้อผิดพลาดนี้เท่านั้น ดังคำตอบอื่น ๆ ที่กล่าวถึงเป็นความคิดที่ดีที่จะแปลงข้อผิดพลาดร้ายแรงเป็นการแจ้งให้ทราบล่วงหน้า IF NOT EXISTSคำสั่งPostgreSQL อื่น ๆจะเพิ่ม, skippingเข้าไปในข้อความของพวกเขาดังนั้นเพื่อความสอดคล้องฉันจะเพิ่มไว้ที่นี่ด้วย

นี่คือรหัส SQL แบบเต็มสำหรับการจำลองCREATE ROLE IF NOT EXISTSด้วยข้อยกเว้นที่ถูกต้องและการแพร่กระจายของ sqlstate:

DO $$
BEGIN
CREATE ROLE test;
EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
END
$$;

ผลลัพธ์การทดสอบ (เรียกสองครั้งผ่าน DO จากนั้นโดยตรง):

$ sudo -u postgres psql
psql (9.6.12)
Type "help" for help.

postgres=# \set ON_ERROR_STOP on
postgres=# \set VERBOSITY verbose
postgres=# 
postgres=# DO $$
postgres$# BEGIN
postgres$# CREATE ROLE test;
postgres$# EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
postgres$# END
postgres$# $$;
DO
postgres=# 
postgres=# DO $$
postgres$# BEGIN
postgres$# CREATE ROLE test;
postgres$# EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
postgres$# END
postgres$# $$;
NOTICE:  42710: role "test" already exists, skipping
LOCATION:  exec_stmt_raise, pl_exec.c:3165
DO
postgres=# 
postgres=# CREATE ROLE test;
ERROR:  42710: role "test" already exists
LOCATION:  CreateRole, user.c:337

2
ขอบคุณ. ไม่มีเงื่อนไขการแข่งขันการจับข้อยกเว้นที่เข้มงวดการตัดข้อความของ Postgres เองแทนที่จะเขียนใหม่ของคุณเอง
Stefano Taschini

1
แน่นอน! ขณะนี้เป็นคำตอบเดียวที่ถูกต้องที่นี่ซึ่งไม่ได้รับผลกระทบจากสภาพการแข่งขันและใช้การจัดการข้อผิดพลาดที่เลือกที่จำเป็น เป็นเรื่องที่น่าเสียดายอย่างยิ่งที่คำตอบนี้ปรากฏขึ้นหลังจากคำตอบยอดนิยม (ไม่ถูกต้องครบถ้วน) รวบรวมได้มากกว่า 100 คะแนน
vog

1
ยินดี! โซลูชันของฉันยังเผยแพร่ SQLSTATE ดังนั้นหากคุณเรียกใช้คำสั่งจากสคริปต์ PL / SQL อื่นหรือภาษาอื่นที่มีตัวเชื่อมต่อ SQL คุณจะได้รับ SQLSTATE ที่ถูกต้อง
บาลี

6

ในขณะที่คุณอยู่ใน 9.x คุณสามารถรวมสิ่งนั้นไว้ในคำสั่ง DO:

do 
$body$
declare 
  num_users integer;
begin
   SELECT count(*) 
     into num_users
   FROM pg_user
   WHERE usename = 'my_user';

   IF num_users = 0 THEN
      CREATE ROLE my_user LOGIN PASSWORD 'my_password';
   END IF;
end
$body$
;

เลือกควรเป็น `` SELECT count (*) ใน num_users FROM pg_roles WHERE rolname = 'data_rw'; `` มิฉะนั้นจะไม่ทำงาน
Miro

6

ทีมของฉันประสบกับสถานการณ์ที่มีฐานข้อมูลหลายฐานข้อมูลบนเซิร์ฟเวอร์เดียวขึ้นอยู่กับฐานข้อมูลที่คุณเชื่อมต่ออยู่ ROLE ที่เป็นปัญหาไม่ได้ถูกส่งคืนโดยSELECT * FROM pg_catalog.pg_userตามที่ @ erwin-brandstetter และ @a_horse_with_no_name เสนอ role "my_user" already existsบล็อกเงื่อนไขดำเนินการและเราตี

น่าเสียดายที่เราไม่แน่ใจในเงื่อนไขที่แน่นอน แต่วิธีนี้แก้ปัญหาได้:

        DO  
        $body$
        BEGIN
            CREATE ROLE my_user LOGIN PASSWORD 'my_password';
        EXCEPTION WHEN others THEN
            RAISE NOTICE 'my_user role exists, not re-creating';
        END
        $body$

อาจทำให้เฉพาะเจาะจงมากขึ้นเพื่อแยกแยะข้อยกเว้นอื่น ๆ


3
ตาราง pg_user ดูเหมือนจะรวมเฉพาะบทบาทที่มี LOGIN หากบทบาทมี NOLOGIN จะไม่ปรากฏใน pg_user อย่างน้อยที่สุดใน PostgreSQL 10
Gregory Arenius

2

คุณสามารถทำได้ในไฟล์แบตช์ของคุณโดยแยกวิเคราะห์ผลลัพธ์ของ:

SELECT * FROM pg_user WHERE usename = 'my_user'

แล้วรันpsql.exeอีกครั้งหากไม่มีบทบาท


2
ไม่มีคอลัมน์ "ชื่อผู้ใช้" ควรเป็น "usename"
Mouhammed Soueidane

3
"usename" คือชื่อที่ไม่มีอยู่จริง :)
Garen

1
โปรดดูที่pg_user view doc ไม่มีคอลัมน์ "ชื่อผู้ใช้" ในเวอร์ชัน 7.4-9.6 "ชื่อผู้ใช้" เป็นคอลัมน์ที่ถูกต้อง
Sheva

1

โซลูชันเดียวกับการจำลองสร้างฐานข้อมูลหากไม่มีอยู่สำหรับ PostgreSQL? ควรจะทำงาน - ส่งไปยังCREATE USER …\gexec

วิธีแก้ปัญหาจากภายใน psql

SELECT 'CREATE USER my_user'
WHERE NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'my_user')\gexec

วิธีแก้ปัญหาจากเชลล์

echo "SELECT 'CREATE USER my_user' WHERE NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'my_user')\gexec" | psql

ดูคำตอบที่ยอมรับสำหรับรายละเอียดเพิ่มเติม


โซลูชันของคุณยังคงมีสภาพการแข่งขันซึ่งฉันได้อธิบายไว้ในคำตอบของฉันstackoverflow.com/a/55954480/7878845 หากคุณเรียกใช้เชลล์สคริปต์ของคุณแบบขนานหลายครั้งคุณจะได้รับข้อผิดพลาด: มีบทบาท "my_user" อยู่แล้ว
ภาษาบาลี
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.