Unique constraints and indexes (PostgreSQL)
Overview
This page explains how to configure a unique constraint / index in your PostgreSQL database. Constraints and indexes are very similar in PostgreSQL (learn more here): When adding a unique constraint to one or more columns, PostgreSQL will always create a corresponding unique index.
In this guide, you'll always configure unique constraints (which will automatically configure unique indexes as well).
You can configure unique constraints either on a single column or on multiple columns. These can be added when you create the table initially (using CREATE TABLE
) or to an already existing table (using ALTER TABLE
). This guide covers all four combinations.
At the end of the guide, you'll introspect your database to reflect the unique constraint in the Prisma schema, then you'll generate Prisma Client and write a simple Node.js script to validate the constraints.
Prerequisites
In order to follow this guide, you need:
- a PostgreSQL database server running
- the
createdb
command line utility - the
psql
command line client for PostgreSQL - Node.js installed on your machine
1. Create a new database and project directory
Start by creating a project directory where you can put the files you'll create throughout this guide:
$$
Next, make sure that your PostgreSQL database server is running. Then execute the following command in your terminal to create a new database called UniqueDemo
:
$
You can validate that the database was created by running the \dt
command which lists all tables (relations) in your database (right now there are none):
$
2. Create a table with a single-column unique constraint and index
In this section, you'll create a new table with a single-column unique constraint in the UniqueDemo
database. As mentioned above, this means that PostgreSQL automatically adds a unique index to the same column.
Create a new file named single-column-unique.sql
and add the following code to it:
single-column-unique.sql
1234
Now run the SQL statement against your database to create a new table called User
:
$
Congratulations, you just created a table called User
in the database. The table has one column called email
on which you defined a unique index. PostgreSQL also automatically added a corresponding unique index (do not run this code):
CREATE UNIQUE INDEX "User_email_key" ON "User"("email" text_ops);
In the code above, you created the unique constraint as a column constraint. Alternatively, you can define it as a table constraint. There's no practical difference between the two, the alternative is just added for completeness.
To add the unique constraint as a table constraint, you need to adjust your SQL statement to look as follows:
CREATE TABLE "public"."User" ("id" SERIAL PRIMARY KEY,email TEXT,UNIQUE ("email"));
3. Create a table with a multi-column unique constraint and index
Next, you'll create a table with a multi-column unique constraint. This also adds a unique index to the columns with the constraint.
Create a new file named multi-column-unique.sql
and add the following code to it:
multi-column-unique.sql
123456
Now run the SQL statement against your database to create a new table called AnotherUser
:
$
Congratulations, you just created a table called AnotherUser
in the database. The table has two column called firstName
and lastName
on which you defined a unique index. PostgreSQL also automatically added a corresponding unique index (do not run this code):
4. Adding a single-column unique constraint to an existing table
In this section, you'll add a single-column unique constraint to a table that already exists in your database. To do so, you first need to create a new table and then alter the table to add the constraint.
Create a new file named add-single-unique-constraint-later.sql
and add the following code:
add-single-unique-constraint-later.sql
123456
This code contains two SQL statements:
- Create a new table called
OneMoreUser
- Alter the table to add an unique constraint
Now run the SQL statements against your database to create a new table called OneMoreUser
:
$
Congratulations, you just created a table called OneMoreUser
in the database. The table has one column called email
on which you later added a unique constraint in the second SQL statement. PostgreSQL also automatically added a corresponding unique index (do not run this code):
5. Adding a multi-column unique constraint to an existing table
In this section, you'll add a multi-column unique constraint to a table that already exists in your database. To do so, you first need to create a new table and then alter the table to add the constraint.
Create a new file named add-multi-unique-constraint-later.sql
and add the following code:
add-multi-unique-constraint-later.sql
1234567
This code contains two SQL statements:
- Create a new table called
TheLastUser
- Alter the table to add an unique constraint
Now run the SQL statements against your database to create a new table called TheLastUser
:
$
Congratulations, you just created a table called TheLastUser
in the database. The table has two columns called firstName
and lastName
on which you later added a unique constraint in the second SQL statement. PostgreSQL also automatically added a corresponding unique index (do not run this code):
CREATE UNIQUE INDEX "TheLastUser_firstName_lastName_unique_constraint" ON "TheLastUser"(firstname text_ops,lastname text_ops);
6. Introspect your database with Prisma
In the previous sections, you created four tables with unique constraints:
- The table
User
has a singe-column unique constraint and index on theemail
column - The table
AnotherUser
has a multi-column unique constraint and index on thefirstName
andlastName
columns - The table
OneMoreUser
has a singe-column unique constraint and index on theemail
column - The table
TheLastUser
has a multi-column unique constraint and index on thefirstName
andlastName
columns
In this section you'll introspect your database to generate the Prisma models for these tables.
To start, set up a new Node.js project and add the Prisma CLI as a development dependency:
$$
Next, set up your Prisma project by creating your Prisma schema file with the following command:
$
This command created a new directory called prisma
with the following contents:
schema.prisma
: The Prisma schema with your database connection and the Prisma Client generator.env
: A dotenv file for defining environment variables (used for your database connection)
To connect your database, you need to set the url
field of the datasource
block in your Prisma schema to your database connection URL:
prisma/schema.prisma
1datasource db {2 provider = "postgresql"3 url = env("DATABASE_URL")4}
In this case, the url
is set via an environment variable which is defined in prisma/.env
:
prisma/.env
1DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
The format of the connection URL for your database depends on the database you use. For PostgreSQL, it looks as follows (the parts spelled all-uppercased are placeholders for your specific connection details):
postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA
Here's a short explanation of each component:
USER
: The name of your database userPASSWORD
: The password for your database userPORT
: The port where your database server is running (typically5432
for PostgreSQL)DATABASE
: The name of the databaseSCHEMA
: The name of the schema inside the database
With both the schema.prisma
and .env
files in place, you can run Prisma's introspection with the following command:
$
This command introspects your database and for each table adds a Prisma model to the Prisma schema:
prisma/schema.prisma
1datasource db {2 provider = "postgresql"3 url = env("DATABASE_URL")4}56model AnotherUser {7 id Int @id @default(autoincrement())8 firstName String?9 lastName String?1011 @@unique([firstName, lastName])12}1314model OneMoreUser {15 id Int @id @default(autoincrement())16 email String? @unique(map: "OneMoreUser_email_unique_constraint")17}1819model TheLastUser {20 id Int @id @default(autoincrement())21 firstName String?22 lastName String?2324 @@unique([firstName, lastName], map: "TheLastUser_firstName_lastName_unique_constraint")25}2627model User {28 id Int @id @default(autoincrement())29 email String? @unique30}
7. Generate Prisma Client
To validate whether the unique constraints work, you'll now generate Prisma Client and send a few sample queries to the database.
First, add a generator
block to your Prisma schema (typically added right below the datasource
block):
schema/schema.prisma
1generator client {2 provider = "prisma-client-js"3}
Run the following command to install and generate Prisma Client in your project:
$
Now you can use Prisma Client to send database queries in Node.js.
8. Validate the unique constraints in a Node.js script
Create a new file named index.js
and add the following code to it:
index.js
1234567891011121314151617181920
In this code, you're creating two users with the same email
, so you're violating the unique constraint that's configured on the User
table.
Run the code with this command:
$
After newUser1
gets printed to the console successfully, the script throws an error indicating that the unique constraint on email
is violated:
Invalid `const newUser1 = await prisma.user.create()` invocation in/Users/janedoe/unique-demo/index.js:6:3823 const prisma = new PrismaClient()45 async function main() {β 6 const newUser1 = await prisma.user.create(Unique constraint failed on the fields: (`email`)
To validate the multi-column unique constraint, replace the code in index.js
with the following:
index.js
12345678910111213141516171819202122
Run the script again with this command:
$
This time, you'll see a similar error message indicating the unique constraint on firstName
and lastName
was violated:
Invalid `newUser2 = await prisma.anotherUser.create()` invocation in/Users/janedoe/unique-demo/index.js:13:459 lastname: "Smith"10 }11 })12 console.log(newUser1)β 13 const newUser2 = await prisma.anotherUser.create(Unique constraint failed on the fields: (`firstname`,`lastname`)
Note that you can add NULL
values for these columns without violating the constraints. For example, the following code snippet will not fail:
index.js
123456789101112
It will create two new records where the email
is set to NULL
in the database.