Mô phỏng TẠO CƠ SỞ DỮ LIỆU NẾU KHÔNG TỒN TẠI cho PostgreSQL?


115

Tôi muốn tạo một cơ sở dữ liệu không tồn tại thông qua JDBC. Không giống như MySQL, PostgreSQL không hỗ trợ create if not existscú pháp. cách tốt nhất để thực hiện điều này là gì?

Ứng dụng không biết liệu cơ sở dữ liệu có tồn tại hay không. Nó sẽ kiểm tra và nếu cơ sở dữ liệu tồn tại, nó nên được sử dụng. Vì vậy, nó là hợp lý để kết nối với cơ sở dữ liệu mong muốn và nếu kết nối không thành công do không tồn tại cơ sở dữ liệu, nó sẽ tạo cơ sở dữ liệu mới (bằng cách kết nối với postgrescơ sở dữ liệu mặc định ). Tôi đã kiểm tra mã lỗi do Postgres trả về nhưng tôi không thể tìm thấy bất kỳ mã liên quan nào giống nhau.

Một phương pháp khác để đạt được điều này là kết nối với postgrescơ sở dữ liệu và kiểm tra xem cơ sở dữ liệu mong muốn có tồn tại hay không và thực hiện hành động tương ứng. Điều thứ hai là một chút tẻ nhạt để làm việc.

Có cách nào để đạt được chức năng này trong Postgres không?

Câu trả lời:


111

Những hạn chế

Bạn có thể hỏi danh mục hệ thống pg_database- có thể truy cập từ bất kỳ cơ sở dữ liệu nào trong cùng một cụm cơ sở dữ liệu. Phần khó khăn là nó CREATE DATABASEchỉ có thể được thực thi như một câu lệnh duy nhất. Hướng dẫn sử dụng:

CREATE DATABASE không thể được thực hiện bên trong một khối giao dịch.

Vì vậy, nó không thể được chạy trực tiếp bên trong một hàm hoặc DOcâu lệnh, nơi nó sẽ ẩn bên trong một khối giao dịch.

(Các thủ tục SQL, được giới thiệu với Postgres 11, cũng không thể giúp được việc này .)

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

Bạn có thể khắc phục nó từ bên trong psql bằng cách thực hiện câu lệnh DDL có điều kiện:

SELECT 'CREATE DATABASE mydb'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'mydb')\gexec

Hướng dẫn sử dụng:

\gexec

Gửi bộ đệm truy vấn hiện tại đến máy chủ, sau đó xử lý mỗi cột của mỗi hàng trong đầu ra của truy vấn (nếu có) như một câu lệnh SQL được thực thi.

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

Với \gexecbạn chỉ cần gọi psql một lần :

echo "SELECT 'CREATE DATABASE mydb' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'mydb')\gexec" | psql

Bạn có thể cần nhiều tùy chọn psql hơn cho kết nối của mình; role, port, password, ... Xem:

Điều tương tự không thể được gọi với psql -c "SELECT ...\gexec"\gexeclà một lệnh meta ‑ psql và -ctùy chọn yêu cầu một lệnh duy nhất mà hướng dẫn sử dụng cho biết:

commandphải là một chuỗi lệnh mà máy chủ hoàn toàn có thể phân tích cú pháp (tức là nó không chứa các tính năng dành riêng cho psql) hoặc một lệnh dấu gạch chéo ngược. Vì vậy, bạn không thể kết hợp các lệnh meta SQL và psql trong một -ctùy chọn.

Cách giải quyết từ bên trong giao dịch Postgres

Bạn có thể sử dụng dblinkkết nối quay lại cơ sở dữ liệu hiện tại, cơ sở dữ liệu này chạy bên ngoài khối giao dịch. Do đó, hiệu ứng cũng có thể không được quay trở lại.

Cài đặt dblink mô-đun bổ sung cho điều này (một lần cho mỗi cơ sở dữ liệu):

Sau đó:

DO
$do$
BEGIN
   IF EXISTS (SELECT FROM pg_database WHERE datname = 'mydb') THEN
      RAISE NOTICE 'Database already exists';  -- optional
   ELSE
      PERFORM dblink_exec('dbname=' || current_database()  -- current db
                        , 'CREATE DATABASE mydb');
   END IF;
END
$do$;

Một lần nữa, bạn có thể cần nhiều tùy chọn psql hơn cho kết nối. Xem câu trả lời được thêm vào của Ortwin:

Giải thích chi tiết cho dblink:

Bạn có thể đặt chức năng này để sử dụng nhiều lần.


Tôi gặp sự cố với điều này khi tạo cơ sở dữ liệu trên AWS RDS Postgres từ xa. Người dùng chính RDS không phải là người dùng cấp cao và do đó không được phép sử dụng dblink_connect.
Ondrej Burkert

Nếu bạn không có đặc quyền của người dùng siêu cấp, bạn có thể sử dụng mật khẩu cho kết nối. Chi tiết: dba.stackexchange.com/a/105186/3684
Erwin Brandstetter

Hoạt động như một sự quyến rũ, được sử dụng trong tập lệnh init.sql bên trong vùng chứa Docker. Cảm ơn!
Micheal J. Roberts

Tôi đã phải bỏ \gexeckhi tôi chạy truy vấn đầu tiên từ trình bao, nhưng nó hoạt động.
FilBot3

117

một giải pháp thay thế khác, chỉ trong trường hợp bạn muốn có một tập lệnh shell tạo cơ sở dữ liệu nếu nó không tồn tại và nếu không thì chỉ cần giữ nguyên như vậy:

psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = 'my_db'" | grep -q 1 || psql -U postgres -c "CREATE DATABASE my_db"

Tôi thấy điều này hữu ích trong việc devops các tập lệnh cấp phép, mà bạn có thể muốn chạy nhiều lần trong cùng một phiên bản.


Nó không hiệu quả với tôi. c:\Program Files\PostgreSQL\9.6\bin $ psql.exe -U admin -tc "SELECT 1 FROM pg_database WHERE datname = 'my_db'" | grep -q 1 || psql -U admin -c "CREATE DATABASE my_db" 'grep' is not recognized as an internal or external command, operable program or batch file.Tôi đã làm gì sai ?
Anton Anikeev

2
Bạn không có greptrong con đường của bạn. Trên Windows, grepkhông được cài đặt theo mặc định. Bạn có thể tìm kiếm gnu grep windowsđể tìm một phiên bản có thể hoạt động trên Windows.
Rod

Thx @Rod. Sau khi tôi cài đặt grep, tập lệnh này đã hoạt động với tôi.
Anton Anikeev

@AntonAnikeev: Có thể thực hiện chỉ với một lệnh gọi psql mà không cần grep. Tôi đã thêm các giải pháp cho câu trả lời của mình.
Erwin Brandstetter

1
Tôi thấy hữu ích khi chúng tôi pg_isready đầu tiên kiểm tra xem kết nối có khả thi không; nếu kết nối không khả dụng (sai tên máy chủ, lỗi mạng, v.v.), tập lệnh sẽ cố gắng tạo cơ sở dữ liệu và sẽ không thành công với thông báo lỗi có thể khó hiểu
Oliver

8

Tôi đã phải sử dụng phiên bản mở rộng một chút @Erwin Brandstetter được sử dụng:

DO
$do$
DECLARE
  _db TEXT := 'some_db';
  _user TEXT := 'postgres_user';
  _password TEXT := 'password';
BEGIN
  CREATE EXTENSION IF NOT EXISTS dblink; -- enable extension 
  IF EXISTS (SELECT 1 FROM pg_database WHERE datname = _db) THEN
    RAISE NOTICE 'Database already exists';
  ELSE
    PERFORM dblink_connect('host=localhost user=' || _user || ' password=' || _password || ' dbname=' || current_database());
    PERFORM dblink_exec('CREATE DATABASE ' || _db);
  END IF;
END
$do$

Tôi đã phải kích hoạt dblinktiện ích mở rộng, ngoài ra tôi phải cung cấp thông tin đăng nhập cho dblink. Làm việc với Postgres 9.4.


7

Nếu bạn không quan tâm đến dữ liệu, bạn có thể xóa cơ sở dữ liệu trước và sau đó tạo lại nó:

DROP DATABASE IF EXISTS dbname;
CREATE DATABASE dbname;

Giải pháp rất thanh lịch. Chỉ cần đừng quên để sao lưu cơ sở dữ liệu đầu tiên nếu bạn làm việc chăm sóc về các dữ liệu. Đối với các tình huống thử nghiệm mặc dù đây là giải pháp ưa thích của tôi.
Laryx Decidua

6

PostgreSQL không hỗ trợ IF NOT EXISTScho CREATE DATABASEcâu lệnh. Nó chỉ được hỗ trợ trong CREATE SCHEMA. Hơn nữa, CREATE DATABASEkhông thể được phát hành trong giao dịch do đó nó không thể nằm trong DOkhối với việc bắt ngoại lệ.

Khi nào CREATE SCHEMA IF NOT EXISTSđược phát hành và lược đồ đã tồn tại thì thông báo (không phải lỗi) với thông tin đối tượng trùng lặp được đưa ra.

Để giải quyết những vấn đề này, bạn cần sử dụng dblinktiện ích mở rộng mở một kết nối mới đến máy chủ cơ sở dữ liệu và thực hiện truy vấn mà không cần tham gia giao dịch. Bạn có thể sử dụng lại các tham số kết nối với việc cung cấp chuỗi trống.

Dưới đây là PL/pgSQLmã mô phỏng đầy đủ CREATE DATABASE IF NOT EXISTSvới hành vi tương tự như trong CREATE SCHEMA IF NOT EXISTS. Nó gọi CREATE DATABASEthông qua dblink, bắt duplicate_databasengoại lệ (được phát hành khi cơ sở dữ liệu đã tồn tại) và chuyển nó thành thông báo với việc truyền bá errcode. Thông báo chuỗi đã được nối , skippingtheo cách tương tự như cách thực hiện CREATE SCHEMA IF NOT EXISTS.

CREATE EXTENSION IF NOT EXISTS dblink;

DO $$
BEGIN
PERFORM dblink_exec('', 'CREATE DATABASE testdb');
EXCEPTION WHEN duplicate_database THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
END
$$;

Giải pháp này không có bất kỳ điều kiện chạy đua nào như trong các câu trả lời khác, trong đó cơ sở dữ liệu có thể được tạo bằng quy trình bên ngoài (hoặc trường hợp khác của cùng một tập lệnh) giữa việc kiểm tra xem cơ sở dữ liệu có tồn tại và việc tạo riêng của nó hay không.

Hơn nữa, khi CREATE DATABASEkhông thành công với lỗi khác với cơ sở dữ liệu đã tồn tại thì lỗi này được coi là lỗi và không bị loại bỏ một cách âm thầm. Chỉ có bắt duplicate_databaselỗi. Vì vậy, nó thực sự hoạt động như IF NOT EXISTSnên.

Bạn có thể đặt mã này vào chức năng riêng, gọi trực tiếp hoặc từ giao dịch. Chỉ khôi phục lại (khôi phục cơ sở dữ liệu bị bỏ) sẽ không hoạt động.

Kiểm tra đầu ra (được gọi hai lần thông 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=# CREATE EXTENSION IF NOT EXISTS dblink;
CREATE EXTENSION
postgres=# DO $$
postgres$# BEGIN
postgres$# PERFORM dblink_exec('', 'CREATE DATABASE testdb');
postgres$# EXCEPTION WHEN duplicate_database THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
postgres$# END
postgres$# $$;
DO
postgres=# 
postgres=# CREATE EXTENSION IF NOT EXISTS dblink;
NOTICE:  42710: extension "dblink" already exists, skipping
LOCATION:  CreateExtension, extension.c:1539
CREATE EXTENSION
postgres=# DO $$
postgres$# BEGIN
postgres$# PERFORM dblink_exec('', 'CREATE DATABASE testdb');
postgres$# EXCEPTION WHEN duplicate_database THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
postgres$# END
postgres$# $$;
NOTICE:  42P04: database "testdb" already exists, skipping
LOCATION:  exec_stmt_raise, pl_exec.c:3165
DO
postgres=# 
postgres=# CREATE DATABASE testdb;
ERROR:  42P04: database "testdb" already exists
LOCATION:  createdb, dbcommands.c:467

1
Đâ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 70 điểm.
vog

2
Chà, các câu trả lời khác không chính xác như vậy để xử lý tất cả các trường hợp góc có thể xảy ra. Bạn cũng có thể gọi mã PL / pgSQL của tôi song song nhiều lần hơn và nó không bị lỗi.
Pali

1

Nếu bạn có thể sử dụng shell, hãy thử

psql -U postgres -c 'select 1' -d $DB &>dev/null || psql -U postgres -tc 'create database $DB'

Tôi nghĩ psql -U postgres -c "select 1" -d $DBlà dễ hơn SELECT 1 FROM pg_database WHERE datname = 'my_db', và chỉ cần một loại trích dẫn, dễ kết hợp hơn sh -c.

Tôi sử dụng cái này trong nhiệm vụ có thể kiểm soát được của mình

- name: create service database
  shell: docker exec postgres sh -c '{ psql -U postgres -tc "SELECT 1" -d {{service_name}} &> /dev/null && echo -n 1; } || { psql -U postgres -c "CREATE DATABASE {{service_name}}"}'
  register: shell_result
  changed_when: "shell_result.stdout != '1'"

0

Chỉ cần tạo cơ sở dữ liệu bằng createdbcông cụ CLI:

PGHOST="my.database.domain.com"
PGUSER="postgres"
PGDB="mydb"
createdb -h $PGHOST -p $PGPORT -U $PGUSER $PGDB

Nếu cơ sở dữ liệu tồn tại, nó sẽ trả về lỗi:

createdb: database creation failed: ERROR:  database "mydb" already exists

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.