จำลองสร้างฐานข้อมูลหากไม่มีอยู่สำหรับ PostgreSQL?


115

ฉันต้องการสร้างฐานข้อมูลที่ไม่มีผ่าน JDBC PostgreSQL ไม่รองรับcreate if not existsไวยากรณ์ซึ่งแตกต่างจาก MySQL วิธีที่ดีที่สุดในการทำสิ่งนี้คืออะไร?

แอปพลิเคชันไม่ทราบว่ามีฐานข้อมูลอยู่หรือไม่ ควรตรวจสอบและว่ามีฐานข้อมูลอยู่หรือไม่ควรใช้ ดังนั้นจึงเหมาะสมที่จะเชื่อมต่อกับฐานข้อมูลที่ต้องการและหากการเชื่อมต่อล้มเหลวเนื่องจากไม่มีฐานข้อมูลก็ควรสร้างฐานข้อมูลใหม่ (โดยเชื่อมต่อกับpostgresฐานข้อมูลเริ่มต้น) ฉันตรวจสอบรหัสข้อผิดพลาดที่ Postgres ส่งคืนแล้ว แต่ไม่พบรหัสที่เกี่ยวข้องที่มีสายพันธุ์เดียวกัน

อีกวิธีหนึ่งในการบรรลุเป้าหมายนี้คือการเชื่อมต่อกับpostgresฐานข้อมูลและตรวจสอบว่ามีฐานข้อมูลที่ต้องการอยู่หรือไม่ ข้อที่สองค่อนข้างน่าเบื่อในการออกกำลังกาย

มีวิธีใดบ้างที่จะบรรลุฟังก์ชันนี้ใน Postgres?

คำตอบ:


111

ข้อ จำกัด

คุณสามารถขอแค็ตตาล็อกระบบpg_database- เข้าถึงได้จากฐานข้อมูลใดก็ได้ในคลัสเตอร์ฐานข้อมูลเดียวกัน ส่วนที่ยุ่งยากคือCREATE DATABASEสามารถดำเนินการเป็นคำสั่งเดียวเท่านั้น คู่มือ:

CREATE DATABASE ไม่สามารถดำเนินการภายในบล็อกธุรกรรม

ดังนั้นจึงไม่สามารถเรียกใช้โดยตรงภายในฟังก์ชันหรือDOคำสั่งโดยที่มันจะอยู่ในบล็อกธุรกรรมโดยปริยาย

(โพรซีเดอร์ SQL ที่นำมาใช้กับ Postgres 11 ไม่สามารถช่วยได้เช่นกัน)

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

คุณสามารถแก้ไขได้จากภายใน psql โดยดำเนินการคำสั่ง DDL ตามเงื่อนไข:

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

คู่มือ:

\gexec

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

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

ด้วย\gexecคุณต้อง psql โทรเพียงครั้งเดียว :

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

คุณอาจต้องการตัวเลือก psql เพิ่มเติมสำหรับการเชื่อมต่อของคุณ บทบาทพอร์ตรหัสผ่าน ... ดู:

ไม่สามารถเรียกสิ่งเดียวกันนี้ได้ด้วยpsql -c "SELECT ...\gexec"เพราะ\gexecเป็นคำสั่ง psql meta ‑ และ-cอ็อพชันคาดว่าจะมีคำสั่งเดียวที่แมนนวลระบุ:

commandต้องเป็นสตริงคำสั่งที่เซิร์ฟเวอร์สามารถแยกวิเคราะห์ได้อย่างสมบูรณ์ (กล่าวคือไม่มีคุณลักษณะเฉพาะ psql) หรือคำสั่งแบ็กสแลชเดียว ดังนั้นคุณจึงไม่สามารถผสมเมตา - คำสั่ง SQL และ psql ภายใน-cตัวเลือกได้

วิธีแก้ปัญหาจากภายในธุรกรรม Postgres

คุณสามารถใช้การdblinkเชื่อมต่อกลับไปยังฐานข้อมูลปัจจุบันซึ่งทำงานนอกบล็อกธุรกรรม ผลกระทบจึงไม่สามารถย้อนกลับได้

ติดตั้งโมดูลเพิ่มเติม dblink สำหรับสิ่งนี้ (หนึ่งครั้งต่อฐานข้อมูล):

แล้ว:

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

อีกครั้งคุณอาจต้องการตัวเลือก psql เพิ่มเติมสำหรับการเชื่อมต่อ ดูคำตอบเพิ่มเติมของ Ortwin:

คำอธิบายโดยละเอียดสำหรับ dblink:

คุณสามารถทำให้ฟังก์ชันนี้ใช้งานซ้ำได้


ฉันพบปัญหานี้เมื่อสร้างฐานข้อมูลบน AWS RDS Postgres จากระยะไกล ผู้ใช้ต้นแบบ RDS ไม่ได้เป็นผู้ใช้ super dblink_connectและด้วยเหตุนี้ไม่ได้รับอนุญาตให้ใช้งาน
Ondrej Burkert

หากคุณไม่มีสิทธิ์ superuser คุณสามารถใช้รหัสผ่านสำหรับการเชื่อมต่อ รายละเอียด: dba.stackexchange.com/a/105186/3684
Erwin Brandstetter

ทำงานได้อย่างมีเสน่ห์โดยใช้ภายในสคริปต์ init.sql ภายในคอนเทนเนอร์ Docker ขอบคุณ!
Micheal J. Roberts

ฉันต้องวาง\gexecเมื่อฉันเรียกใช้แบบสอบถามแรกจากเชลล์ แต่มันใช้งานได้
FilBot3

117

อีกทางเลือกหนึ่งในกรณีที่คุณต้องการมีเชลล์สคริปต์ซึ่งสร้างฐานข้อมูลหากไม่มีอยู่หรือไม่ก็เก็บไว้ตามที่เป็นอยู่:

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

ฉันพบว่าสิ่งนี้มีประโยชน์ในการพัฒนาสคริปต์การจัดเตรียมซึ่งคุณอาจต้องการเรียกใช้หลายครั้งในอินสแตนซ์เดียวกัน


มันไม่ได้ผลสำหรับฉัน 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.ฉันทำอะไรผิด ?
Anton Anikeev

2
คุณไม่มีgrepในเส้นทางของคุณ บน Windows grepไม่ได้ติดตั้งตามค่าเริ่มต้น คุณสามารถค้นหาgnu grep windowsเพื่อค้นหาเวอร์ชันที่สามารถทำงานบน Windows ได้
Rod

ขอบคุณ @Rod. หลังจากที่ฉันติดตั้ง grep สคริปต์นี้ใช้งานได้สำหรับฉัน
Anton Anikeev

@AntonAnikeev: สามารถทำได้ด้วยการเรียก psql เพียงครั้งเดียวโดยไม่ต้อง grep ฉันเพิ่มคำตอบให้กับคำตอบของฉัน
Erwin Brandstetter

1
ฉันคิดว่ามันมีประโยชน์สำหรับเราก่อนอื่น pg_isready เพื่อตรวจสอบว่าการเชื่อมต่อเป็นไปได้ หากการเชื่อมต่อไม่พร้อมใช้งาน (ชื่อโฮสต์ผิดเครือข่ายล่ม ฯลฯ ) สคริปต์จะพยายามสร้างฐานข้อมูลและจะล้มเหลวด้วยข้อความแสดงข้อผิดพลาดที่อาจทำให้สับสน
Oliver

8

ฉันต้องใช้เวอร์ชันเพิ่มเติมเล็กน้อยที่ @Erwin Brandstetter ใช้:

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$

ฉันต้องเปิดใช้งานdblinkส่วนขยายและฉันต้องให้ข้อมูลรับรองสำหรับ dblink ทำงานร่วมกับ Postgres 9.4


7

หากคุณไม่สนใจข้อมูลคุณสามารถวางฐานข้อมูลก่อนแล้วจึงสร้างใหม่:

DROP DATABASE IF EXISTS dbname;
CREATE DATABASE dbname;

โซลูชันที่หรูหรามาก ก็อย่าลืมที่จะสำรองฐานข้อมูลครั้งแรกถ้าคุณทำดูแลเกี่ยวกับข้อมูล สำหรับการทดสอบสถานการณ์แม้ว่านี่เป็นทางออกที่ฉันต้องการ
Laryx Decidua

6

PostgreSQL ไม่สนับสนุนIF NOT EXISTSสำหรับCREATE DATABASEคำสั่ง รองรับเฉพาะในCREATE SCHEMA. ยิ่งไปกว่าCREATE DATABASEนั้นไม่สามารถออกธุรกรรมได้ดังนั้นจึงไม่สามารถDOปิดกั้นได้ยกเว้นการจับ

เมื่อCREATE SCHEMA IF NOT EXISTSมีการออกและมี schema อยู่แล้วให้แจ้ง (ไม่ใช่ข้อผิดพลาด) พร้อมกับข้อมูลวัตถุที่ซ้ำกัน

ในการแก้ปัญหาเหล่านี้คุณต้องใช้dblinkส่วนขยายซึ่งจะเปิดการเชื่อมต่อใหม่กับเซิร์ฟเวอร์ฐานข้อมูลและดำเนินการสืบค้นโดยไม่ต้องทำธุรกรรม คุณสามารถใช้พารามิเตอร์การเชื่อมต่อซ้ำได้ด้วยการจัดหาสตริงว่าง

ด้านล่างเป็นPL/pgSQLรหัสซึ่งอย่างเต็มที่จำลองที่มีพฤติกรรมเช่นเดียวกับในCREATE DATABASE IF NOT EXISTS CREATE SCHEMA IF NOT EXISTSมันเรียกร้องCREATE DATABASEผ่านทางdblinkจับduplicate_databaseข้อยกเว้น (ซึ่งออกเมื่อฐานข้อมูลอยู่แล้ว) errcodeและแปลงเป็นแจ้งให้ทราบล่วงหน้ากับขยายพันธุ์ ข้อความ String ได้ผนวกในทางเดียวกันว่ามันไม่, skippingCREATE 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
$$;

โซลูชันนี้ไม่มีเงื่อนไขการแย่งชิงเหมือนในคำตอบอื่น ๆ ซึ่งฐานข้อมูลสามารถสร้างขึ้นโดยกระบวนการภายนอก (หรืออินสแตนซ์อื่น ๆ ของสคริปต์เดียวกัน) ระหว่างการตรวจสอบว่ามีฐานข้อมูลอยู่หรือไม่และการสร้างของตนเอง

ยิ่งไปกว่านั้นเมื่อCREATE DATABASEล้มเหลวด้วยข้อผิดพลาดอื่นนอกเหนือจากฐานข้อมูลที่มีอยู่แล้วข้อผิดพลาดนี้จะถูกเผยแพร่เป็นข้อผิดพลาดและไม่ถูกละทิ้งโดยไม่โต้ตอบ มีเพียงการจับสำหรับduplicate_databaseข้อผิดพลาด ดังนั้นจึงมีพฤติกรรมตามที่IF NOT EXISTSควร

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

ผลลัพธ์การทดสอบ (เรียกว่าสองครั้งผ่าน 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=# 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
ขณะนี้เป็นคำตอบเดียวที่ถูกต้องที่นี่ซึ่งไม่ได้รับผลกระทบจากสภาพการแข่งขันและใช้การจัดการข้อผิดพลาดที่เลือกที่จำเป็น เป็นเรื่องน่าเสียดายอย่างยิ่งที่คำตอบนี้ปรากฏขึ้นหลังจากคำตอบด้านบน (ไม่ถูกต้องครบถ้วน) รวบรวมได้มากกว่า 70 คะแนน
vog

2
คำตอบอื่น ๆ นั้นไม่แม่นยำในการจัดการกับกรณีมุมที่อาจเกิดขึ้นได้ทั้งหมด คุณสามารถเรียกรหัส PL / pgSQL ของฉันได้หลายครั้งแบบขนานและไม่ล้มเหลว
บาลี

1

ถ้าคุณสามารถใช้เชลล์ลอง

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

ผมคิดว่าpsql -U postgres -c "select 1" -d $DBเป็นเรื่องง่ายกว่าและต้องการเพียงประเภทหนึ่งของคำพูดที่ง่ายต่อการใช้ร่วมกับSELECT 1 FROM pg_database WHERE datname = 'my_db'sh -c

ฉันใช้สิ่งนี้ในงานที่ตอบได้

- 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

เพียงสร้างฐานข้อมูลโดยใช้createdbเครื่องมือ CLI:

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

หากมีฐานข้อมูลอยู่จะส่งคืนข้อผิดพลาด:

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.