Simulate CREATE DATABASE IF NOT EXISTS for PostgreSQL?
Asked Answered
U

11

232

I want to create a database which does not exist through JDBC. Unlike MySQL, PostgreSQL does not support create if not exists syntax. What is the best way to accomplish this?

The application does not know if the database exists or not. It should check and if the database exists it should be used. So it makes sense to connect to the desired database and if connection fails due to non-existence of database it should create new database (by connecting to the default postgres database). I checked the error code returned by Postgres but I could not find any relevant code that species the same.

Another method to achieve this would be to connect to the postgres database and check if the desired database exists and take action accordingly. The second one is a bit tedious to work out.

Is there any way to achieve this functionality in Postgres?

Uganda answered 22/8, 2013 at 19:21 Comment(0)
W
238

Restrictions

You can ask the system catalog pg_database - accessible from any database in the same database cluster. The tricky part is that CREATE DATABASE can only be executed as a single statement. The manual:

CREATE DATABASE cannot be executed inside a transaction block.

So it cannot be run directly inside a function or DO statement, where it would be inside a transaction block implicitly. SQL procedures, introduced with Postgres 11, cannot help with this either.

Workaround from within psql

You can work around it from within psql by executing the DDL statement conditionally:

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

The manual:

\gexec

Sends the current query buffer to the server, then treats each column of each row of the query's output (if any) as a SQL statement to be executed.

Workaround from the shell

With \gexec you only need to call psql once:

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

You may need more psql options for your connection; role, port, password, ... See:

The same cannot be called with psql -c "SELECT ...\gexec" since \gexec is a psql meta‑command and the -c option expects a single command for which the manual states:

command must be either a command string that is completely parsable by the server (i.e., it contains no psql-specific features), or a single backslash command. Thus you cannot mix SQL and psql meta-commands within a -c option.

Workaround from within Postgres transaction

You could use a dblink connection back to the current database, which runs outside of the transaction block. Effects can therefore also not be rolled back.

Install the additional module dblink for this (once per database):

Then:

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

Again, you may need more psql options for the connection. See Ortwin's added answer:

Detailed explanation for dblink:

You can make this a function for repeated use.

Whitley answered 22/8, 2013 at 19:25 Comment(10)
I ran into problem with this when creating a database on AWS RDS Postgres from remote. RDS master user is not a super user and is hence not allowed to use dblink_connect.Thomism
If you don't have superuser privileges, you can use a password for the connection. Details: dba.stackexchange.com/a/105186/3684Whitley
Worked like a charm, used within an init.sql script inside Docker container. Thanks!Whoop
I had to drop the \gexec when I ran the first query from the shell, but it worked.Shive
ERROR: syntax error at or near "WHERE"Lurette
@Dieter: You need to be more specific than that. My money is on an unquoted, illegal DB name ...Whitley
This is a great answer and very thorough explanation as to why. Thank you.Horror
psycopg2.errors.UndefinedFunction: function dblink_exec(text, unknown) does not exist HINT: No function matches the given name and argument types. You might need to add explicit type casts.Shanell
@Shayan: Like instructed above, install the additional module "dblink" first.Whitley
I see! I added CREATE EXTENSION IF NOT EXISTS dblink; to my code but now I'm getting this error on my Python code (I'm using Peewee as the ORM). psycopg2.errors.SqlclientUnableToEstablishSqlconnection: could not establish connection DETAIL: connection to server at "localhost" (::1), port 5432 failed: fe_sendauth: no password suppliedShanell
S
156

another alternative, just in case you want to have a shell script which creates the database if it does not exist and otherwise just keeps it as it is:

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

I found this to be helpful in devops provisioning scripts, which you might want to run multiple times over the same instance.

For those of you who would like an explanation:

-c = run command in database session, command is given in string
-t = skip header and footer
-q = silent mode for grep 
|| = logical OR, if grep fails to find match run the subsequent command
Selfrestraint answered 13/4, 2016 at 7:47 Comment(6)
It doesn't works for me. 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. What did I do wrong ?Wescott
You don't have grep in your path. On Windows, grep is not installed by default. You can search for gnu grep windows to find a version that could work on Windows.Chopstick
Thx @Rod. After I installed grep this script worked for me.Wescott
@AntonAnikeev: Can be done with a single psql call without grep. I added solutions to my answer.Whitley
I find it useful to first us pg_isready to check that a connection is possible; if a connection is not available (wrong hostname, network down etc), the script will attempt to create the database and will fail with possibly confusing error messageCongius
Note. You might want to add -X and -A options to first psql to get more consistent results. You could also then just write the value to a variable and use more readable if statement dbExists=`psql -U postgres -X -A -t -c ...` and if [ -z "$dbExists" ]; then... else... fi.Headache
O
48

If you don't care about the data, you can drop database first and then recreate it:

DROP DATABASE IF EXISTS dbname;
CREATE DATABASE dbname;
Osteopath answered 11/3, 2019 at 15:36 Comment(2)
Very elegant solution. Just don't forget to back up the database first if you do care about the data. For testing situations though this is my preferred solution.Preconcert
Excellent solution. Works perfectly for integration tests.Godbey
P
26

PostgreSQL does not support IF NOT EXISTS for CREATE DATABASE statement. It is supported only in CREATE SCHEMA. Moreover CREATE DATABASE cannot be issued in transaction therefore it cannot be in DO block with exception catching.

When CREATE SCHEMA IF NOT EXISTS is issued and schema already exists then notice (not error) with duplicate object information is raised.

To solve these problems you need to use dblink extension which opens a new connection to database server and execute query without entering into transaction. You can reuse connection parameters with supplying empty string.

Below is PL/pgSQL code which fully simulates CREATE DATABASE IF NOT EXISTS with same behavior like in CREATE SCHEMA IF NOT EXISTS. It calls CREATE DATABASE via dblink, catch duplicate_database exception (which is issued when database already exists) and converts it into notice with propagating errcode. String message has appended , skipping in the same way how it does 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
$$;

This solution is without any race condition like in other answers, where database can be created by external process (or other instance of same script) between checking if database exists and its own creation.

Moreover when CREATE DATABASE fails with other error than database already exists then this error is propagated as error and not silently discarded. There is only catch for duplicate_database error. So it really behaves as IF NOT EXISTS should.

You can put this code into own function, call it directly or from transaction. Just rollback (restore dropped database) would not work.

Testing output (called two times via DO and then directly):

$ 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
Psychopathology answered 2/5, 2019 at 10:29 Comment(3)
This is currently the only correct answer here, which does not suffer from race conditions, and uses the necessary selective error handling. It is a realy pity that this answer appeared after the (not fully correct) top answer collected more that 70 points.Churchman
Well, other answers are not such precise to handle all possible corner cases which may happen. You can also call my PL/pgSQL code more times in parallel and it does not fail.Psychopathology
even this answer can suffer from race conditions as explained here. https://mcmap.net/q/119802/-quot-create-extention-if-not-exists-quot-doesn-39-t-really-check-if-extention-does-not-existAnthea
F
14

I had to use a slightly extended version @Erwin Brandstetter used:

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$

I had to enable the dblink extension, plus i had to provide the credentials for dblink. Works with Postgres 9.4.

Fury answered 25/3, 2016 at 11:12 Comment(0)
A
6

If you can use shell, try

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

I think psql -U postgres -c "select 1" -d $DB is easier than SELECT 1 FROM pg_database WHERE datname = 'my_db',and only need one type of quote, easier to combine with sh -c.

I use this in my ansible task

- 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'"
Ampoule answered 6/1, 2020 at 18:57 Comment(0)
V
6

The best way is just running the SQL.

CREATE DATABASE MY_DATABASE; 

if the database already exists, it throws "database already exists error" which you can do whatever you want to do, otherwise it creates the database. I do not think it will create a new database on top of yours. :D

Volume answered 28/6, 2021 at 22:50 Comment(0)
M
1

Another flavor if running with psql

psql --quiet -d postgres -c "CREATE DATABASE $DB_DATABASE;" || :

Note, this will still output ERROR: database "" already exists but can be ignored.

Migration answered 13/1, 2023 at 19:33 Comment(0)
F
0

After reading through all these in my opinion complicated solutions that are terrible work arounds for the lack of the IF NOT EXIST option for postgres user creation, I forgot almost there is a simply way to just handle it at the shell level. Even though it might not be what some want, I think a lot of folks want simplicity and not creating procedures and complicated constructs.

I'm using docker, here are the important snippets from my bash script that loads data in a devsetup:

execute_psql_command_pipe () {
         $DOCKER_COMMAND exec -it $POSTGRES_CONTAINER bash -c "echo \"$1\"| psql -h localhost -U postgres || echo psql command failed - object likely exists"
}

read -r -d '' CREATE_USER_COMMANDS << EOM
CREATE DATABASE MY_DATABASE; 
create user User1 WITH PASSWORD 'password';
create user User2 WITH PASSWORD 'password';
EOM

execute_psql_command_pipe "$CREATE_USER_COMMANDS"

There are a few things wrong with it, but it's the simplest way I could find to make it do what I want: create on first pass of script, continue on second pass when existing. By the way, the echo output does not show, but the commands continue because the echo command exits with 0.

The same can be done for any command (like db create). This obviously fails (or succeeds, depending on perspective) for any other error that may occur too, but you get the psql output printer so more handling can be added.

[updated with create database command as pointed out in comment]

Foldaway answered 3/6, 2021 at 14:12 Comment(2)
It seems you missed that the question is about create database, not create user?Unlookedfor
Good point, although it should not matter because it can be any SQL command, but I updated the answer. Thank you.Foldaway
D
0

One simple clean way to do it that I ended up using:

createdb $DATABASE 2> /dev/null || echo "database already exists"

If you expect other error than database "x" already exists that obviously won't work (e.g. permission denied). In any case, if that is a concern, one can always perform such checks prior to this point.

Don't forget to set the value for DATABASE, and to pass in the required switches for the createdb command. Preferably you can also do like:

export PGHOST=localhost
export PGUSER=user
export PGPASSWORD=p455w0rd
...
Draft answered 3/8, 2021 at 21:16 Comment(1)
This is what I'm ending up with. Note createdb -U <user> <dbname>Piemonte
D
-2

Just create the database using createdb CLI tool:

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

If the database exists, it will return an error:

createdb: database creation failed: ERROR:  database "mydb" already exists
Determinate answered 12/8, 2019 at 16:41 Comment(3)
OP doesn't want something that sends an error, because it might crash a script.Husbandman
script.sh &> /dev/null so it won't crashPersimmon
Database creation may crash on other conditions than already existence. The other causes will be silently hidden with your solution.Fistic

© 2022 - 2024 — McMap. All rights reserved.