Tạo VAI TRÒ PostgreSQL (người dùng) nếu nó không tồn tại


122

Làm cách nào để viết tập lệnh SQL để tạo VAI TRÒ trong PostgreSQL 9.1, nhưng không phát sinh lỗi nếu nó đã tồn tại?

Tập lệnh hiện tại chỉ có:

CREATE ROLE my_user LOGIN PASSWORD 'my_password';

Điều này không thành công nếu người dùng đã tồn tại. Tôi muốn một cái gì đó như:

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

... nhưng điều đó không hoạt động - IFdường như không được hỗ trợ trong SQL thuần túy.

Tôi có một tệp lô tạo cơ sở dữ liệu PostgreSQL 9.1, vai trò và một số thứ khác. Nó gọi psql.exe, truyền vào tên của một tập lệnh SQL để chạy. Cho đến nay tất cả các tập lệnh này đều là SQL thuần túy và tôi muốn tránh PL / pgSQL và những thứ tương tự, nếu có thể.

Câu trả lời:


156

Đơn giản hóa theo cách tương tự như những gì bạn đã nghĩ:

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

(Dựa trên câu trả lời của @a_horse_with_no_name và được cải thiện với nhận xét của @ Gregory .)

Không giống như, chẳng hạn, với CREATE TABLEkhông có IF NOT EXISTSmệnh đề cho CREATE ROLE(tối đa ít nhất là pg 12). Và bạn không thể thực thi các câu lệnh DDL động trong SQL thuần túy.

Yêu cầu của bạn để "tránh PL / pgSQL" là không thể ngoại trừ bằng cách sử dụng PL khác. Câu DOlệnh sử dụng plpgsql làm ngôn ngữ thủ tục mặc định. Cú pháp cho phép bỏ qua khai báo rõ ràng:

DO [ LANGUAGE lang_name ] code
... Tên của ngôn ngữ thủ tục mà mã được viết. Nếu bỏ qua, giá trị mặc định là .
lang_name
plpgsql


1
@Alberto: pg_userpg_roles đều đúng. Vẫn là trường hợp trong phiên bản 9.3 hiện tại và nó sẽ không sớm thay đổi.
Erwin Brandstetter

2
@Ken: Nếu $có ý nghĩa đặc biệt trong ứng dụng của bạn, bạn cần phải thoát khỏi nó theo các quy tắc cú pháp của ứng dụng của bạn. Hãy thử thoát $với \$trong Linux shell. Hoặc bắt đầu một câu hỏi mới - nhận xét không phải là nơi. Bạn luôn có thể liên kết đến cái này để biết ngữ cảnh.
Erwin Brandstetter

1
Tôi đang sử dụng 9.6 và nếu người dùng được tạo bằng NOLOGIN, họ sẽ không hiển thị trong bảng pg_user, nhưng hiển thị trong bảng pg_roles. Pg_roles có phải là giải pháp tốt hơn ở đây không?
Jess

2
@ErwinBrandstetter Điều này không hoạt động cho các vai trò có NOLOGIN. Chúng hiển thị trong pg_roles nhưng không hiển thị trong pg_user.
Gregory Arenius

2
Giải pháp này gặp phải tình trạng chủng tộc. Một biến thể an toàn hơn được ghi lại trong câu trả lời này .
blubb

60

Câu trả lời được chấp nhận gặp phải điều kiện chạy đua nếu hai tập lệnh như vậy được thực thi đồng thời trên cùng một cụm Postgres (máy chủ DB), điều này thường xảy ra trong môi trường tích hợp liên tục .

Nói chung sẽ an toàn hơn nếu cố gắng tạo vai trò và giải quyết các vấn đề một cách khéo léo khi tạo vai trò đó:

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
Tôi thích cách này vì nó tồn tại.
Matias Barone

2
DUPLICATE_OBJECTlà điều kiện chính xác trong trường hợp này, nếu bạn không muốn chỉ nắm bắt tất cả các điều kiện với OTHERS.
Danek Duvall

43

Hoặc nếu vai trò không phải là chủ sở hữu của bất kỳ đối tượng db nào thì người ta có thể sử dụng:

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

Nhưng chỉ khi thả người dùng này sẽ không gây hại gì.


10

Bash thay thế (cho 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';"

(không phải là câu trả lời cho câu hỏi! nó chỉ dành cho những người có thể hữu ích)


3
Nó nên đọc FROM pg_roles WHERE rolnamethay vìFROM pg_user WHERE usename
Barth

8

Đây là một giải pháp chung sử dụng 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;

Sử dụng:

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

Một số câu trả lời được đề xuất để sử dụng mẫu: kiểm tra nếu vai trò không tồn tại và nếu không thì hãy ra CREATE ROLElệnh. Điều này có một bất lợi: điều kiện chủng tộc. Nếu ai đó khác tạo một vai trò mới giữa kiểm tra và phát hành CREATE ROLElệnh thì CREATE ROLErõ ràng là không thành công với lỗi nghiêm trọng.

Để giải quyết vấn đề trên, nhiều câu trả lời khác đã đề cập đến việc sử dụng PL/pgSQL, phát hành CREATE ROLEvô điều kiện và sau đó bắt các ngoại lệ từ cuộc gọi đó. Chỉ có một vấn đề với các giải pháp này. Họ âm thầm loại bỏ bất kỳ lỗi nào, bao gồm cả những lỗi không được tạo ra bởi thực tế rằng vai trò đã tồn tại. CREATE ROLEcũng có thể tạo ra các lỗi khác và mô phỏng IF NOT EXISTSchỉ nên tắt lỗi khi vai trò đã tồn tại.

CREATE ROLEném duplicate_objectlỗi khi vai trò đã tồn tại. Và trình xử lý ngoại lệ chỉ nên bắt một lỗi này. Như các câu trả lời khác đã đề cập, bạn nên chuyển lỗi nghiêm trọng thành thông báo đơn giản. Các IF NOT EXISTSlệnh PostgreSQL khác thêm , skippingvào thông điệp của chúng, vì vậy để nhất quán, tôi cũng sẽ thêm nó vào đây.

Đây là mã SQL đầy đủ để mô phỏng CREATE ROLE IF NOT EXISTSvới sự lan truyền ngoại lệ và sqlstate chính xác:

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

Kiểm tra đầu ra (được gọi hai lần qua DO và sau đó trực tiếp):

$ 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
Cảm ơn bạn. Không có điều kiện chủng tộc, bắt ngoại lệ chặt chẽ, gói thông điệp của riêng Postgres thay vì viết lại của riêng bạn.
Stefano Taschini

1
Thật! Đây hiện là câu trả lời đúng duy nhất ở đây, không bị ảnh hưởng bởi các điều kiện chủng tộc và sử dụng xử lý lỗi chọn lọc cần thiết. Thật đáng tiếc là câu trả lời này xuất hiện sau khi câu trả lời hàng đầu (không hoàn toàn chính xác) thu về hơn 100 điểm.
vog

1
Không có gì! Giải pháp của tôi cũng truyền SQLSTATE vì vậy nếu bạn đang gọi câu lệnh từ tập lệnh PL / SQL khác hoặc ngôn ngữ khác với trình kết nối SQL, bạn sẽ nhận được SQLSTATE chính xác.
Pali

6

Khi bạn đang sử dụng 9.x, bạn có thể gói nó thành một câu lệnh 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$
;

Chọn nên được `select count (*) vào NUM_USERS TỪ pg_roles ĐÂU rolname = 'data_rw';` Nếu không nó sẽ không làm việc
Miro

6

Nhóm của tôi đã gặp phải tình huống có nhiều cơ sở dữ liệu trên một máy chủ, tùy thuộc vào cơ sở dữ liệu bạn đã kết nối, VAI TRÒ được đề cập không được trả lại SELECT * FROM pg_catalog.pg_userbởi @ erwin-brandstetter và @a_horse_with_no_name. Khối điều kiện được thực thi và chúng tôi đã nhấn role "my_user" already exists.

Rất tiếc, chúng tôi không chắc chắn về các điều kiện chính xác, nhưng giải pháp này khắc phục được sự cố:

        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$

Nó có thể được thực hiện cụ thể hơn để loại trừ các trường hợp ngoại lệ khác.


3
Bảng pg_user dường như chỉ bao gồm các vai trò có ĐĂNG NHẬP. Nếu một vai trò có NOLOGIN nó không hiển thị trong pg_user, ít nhất là trong PostgreSQL 10
Gregory Arenius

2

Bạn có thể làm điều đó trong tệp lô của mình bằng cách phân tích cú pháp đầu ra của:

SELECT * FROM pg_user WHERE usename = 'my_user'

và sau đó chạy psql.exelại một lần nữa nếu vai trò không tồn tại.


2
cột "tên người dùng" không tồn tại. Nó phải là "tên sử dụng".
Mouhammed Soueidane,

3
"usename" là tên không tồn tại. :)
Garen

1
Vui lòng tham khảo tài liệu chế độ xem pg_user . Không có cột "tên người dùng" trong các phiên bản 7.4-9.6, "tên sử dụng" là cột chính xác.
Sheva

1

Giải pháp tương tự như đối với Mô phỏng TẠO CƠ SỞ DỮ LIỆU NẾU KHÔNG TỒN TẠI cho PostgreSQL?nên hoạt động - gửi một CREATE USER …đến \gexec.

Cách giải quyết từ bên trong psql

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

Cách giải quyết từ shell

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

Xem câu trả lời được chấp nhận ở đó để biết thêm chi tiết.


Giải pháp của bạn vẫn có điều kiện chủng tộc mà tôi đã mô tả trong câu trả lời của tôi stackoverflow.com/a/55954480/7878845 Nếu bạn chạy tập lệnh shell của mình song song nhiều lần hơn, bạn nhận được LỖI: vai trò "my_user" đã tồn tại
Pali
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.