What I need is a migration to apply unique constraint to a combination of columns. i.e. for a people
table, a combination of first_name
, last_Name
and Dob
should be unique.

- 17,418
- 8
- 58
- 76

- 5,448
- 8
- 46
- 66
-
3Just an FYI for anyone reading this question: Don't make a constraint based on first name, last name, and date of birth, because more than one person can match on all three of those. (This is mentioned in an answer, but it isn't really an answer.) – cesoid Nov 25 '20 at 13:49
7 Answers
add_index :people, [:firstname, :lastname, :dob], unique: true

- 6,024
- 2
- 37
- 34

- 15,382
- 6
- 40
- 45
-
15I think that's adding a unique index, *not* a constraint. Or does the index add the constraint as well? – Paul Cantrell Aug 24 '11 at 16:24
-
20Nope, it's all good. My bad! The unique constraint comes with the unique index. – Paul Cantrell Aug 24 '11 at 16:34
-
8I agree with @paul-cantrell : ain't there any way to only add a constraint, not an index (which has db storage influences) – Augustin Riedinger Jul 04 '13 at 15:24
-
1@AugustinRiedinger, little reason to put constraint in DB then. Instead, use a validation on the model: http://stackoverflow.com/questions/4870961/rails-validate-uniqueness-of-multiple-columns – Ari Apr 25 '14 at 17:04
-
22The problem with the model level validation is that it doesn't scale. Two servers could run the same data through at the same time (like a double tap on an api heavy app) I have two identical records in my DB right now and the model has the validation.. – baash05 Mar 17 '15 at 01:26
-
7
-
2@baash05 I'm here because of this exactly problem, two servers running the same data at the same time, requested by my API. The model validation is useless in this case – Jun 26 '15 at 12:17
-
For lots of discussion about why you need the database to enforce this, not just the model, see http://www.bailis.org/papers/feral-sigmod2015.pdf – Nathan Long Dec 28 '16 at 22:00
-
1@PaulCantrell I got confused by your comments; the first comment is about whether an index creates a constraint as well, whereas the second comment asserts that a constraint creates an index as well ... while the latter statement is true (for postgres), the converse isn't and requires a different approach, like [this answer](https://stackoverflow.com/a/28704445/1338292) – Ja͢ck Jan 12 '18 at 01:10
-
No, both comments were about whether a unique index automatically creates a unique constraint. I was indeed only considering Postgres. – Paul Cantrell Jan 12 '18 at 01:25
-
Would be helpful to instruct whether or not this operation is compatible with migration "change". – karns Sep 03 '19 at 15:07
You may want to add a constraint without an index. This will depend on what database you're using. Below is sample migration code for Postgres. (tracking_number, carrier)
is a list of the columns you want to use for the constraint.
class AddUniqeConstraintToShipments < ActiveRecord::Migration
def up
execute <<-SQL
alter table shipments
add constraint shipment_tracking_number unique (tracking_number, carrier);
SQL
end
def down
execute <<-SQL
alter table shipments
drop constraint if exists shipment_tracking_number;
SQL
end
end
There are different constraints you can add. Read the docs

- 8,329
- 4
- 36
- 33
-
14[Docs for PostgreSQL 9.4](http://www.postgresql.org/docs/9.4/static/ddl-constraints.html#DDL-CONSTRAINTS-UNIQUE-CONSTRAINTS) say: _Adding a unique constraint will automatically create a unique btree index on the column or group of columns used in the constraint. A uniqueness constraint on only some rows can be enforced by creating a partial index._ So IMHO there's no need to drop to raw SQL when the outcome will be basically the same as using the `add_index` method. ;) – Rafał Cieślak Mar 23 '15 at 10:32
-
8Actually there is one reason: It is an implementation detail and discouraged by the [docs](http://www.postgresql.org/docs/9.3/static/indexes-unique.html). Also note that you cannot refer to the constraint by name, since it is not added to the `pg_constraint` table. – kaikuchn May 11 '15 at 10:58
According to howmanyofme.com, "There are 46,427 people named John Smith" in the United States alone. That's about 127 years of days. As this is well over the average lifespan of a human being, this means that a DOB clash is mathematically certain.
All I'm saying is that that particular combination of unique fields could lead to extreme user/customer frustration in future.
Consider something that's actually unique, like a national identification number, if appropriate.
(I realise I'm very late to the party with this one, but it could help future readers.)

- 3,516
- 1
- 22
- 28
-
3hrm... you are certainly right. but probably it was just an example of what Ian wanted to do just to make the question clear. – eritiro May 12 '16 at 22:45
-
2Maybe. The answer wasn't intended for Ian though. Or indeed Rangalo. – A Fader Darkly May 13 '16 at 13:10
-
For completeness sake, and to avoid confusion here are 3 ways of doing the same thing:
Adding a named unique constraint to a combination of columns in Rails 5.2+
Let's say we have Locations table that belongs to an advertiser and has column reference_code and you only want 1 reference code per advertiser. so you want to add a unique constraint to a combination of columns and name it.
Do:
rails g migration AddUniquenessConstraintToLocations
And make your migration look either something like this one liner:
class AddUniquenessConstraintToLocations < ActiveRecord::Migration[5.2]
def change
add_index :locations, [:reference_code, :advertiser_id], unique: true, name: 'uniq_reference_code_per_advertiser'
end
end
OR this block version.
class AddUniquenessConstraintToLocations < ActiveRecord::Migration[5.2]
def change
change_table :locations do |t|
t.index ['reference_code', 'advertiser_id'], name: 'uniq_reference_code_per_advertiser', unique: true
end
end
end
OR this raw SQL version
class AddUniquenessConstraintToLocations < ActiveRecord::Migration[5.2]
def change
execute <<-SQL
ALTER TABLE locations
ADD CONSTRAINT uniq_reference_code_per_advertiser UNIQUE (reference_code, advertiser_id);
SQL
end
end
Any of these will have the same result, check your schema.rb

- 6,557
- 2
- 19
- 26
Hi You may add unique index in your migration to the columns for example
add_index(:accounts, [:branch_id, :party_id], :unique => true)
or separate unique indexes for each column

- 8,298
- 6
- 41
- 51
-
Sorry, it worked, first I tried by editing and existing migration which didn't work, then added a new one and it worked, thanks. – rangalo Jul 30 '10 at 11:10
In the typical example of a join table between users and posts:
create_table :users
create_table :posts
create_table :ownerships do |t|
t.belongs_to :user, foreign_key: true, null: false
t.belongs_to :post, foreign_key: true, null: false
end
add_index :ownerships, [:user_id, :post_id], unique: true
Trying to create two similar records will throw a database error (Postgres in my case):
ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_ownerships_on_user_id_and_post_id"
DETAIL: Key (user_id, post_id)=(1, 1) already exists.
: INSERT INTO "ownerships" ("user_id", "post_id") VALUES ($1, $2) RETURNING "id"
e.g. doing that:
Ownership.create!(user_id: user_id, post_id: post_id)
Ownership.create!(user_id: user_id, post_id: post_id)
Fully runnable example: https://gist.github.com/Dorian/9d641ca78dad8eb64736173614d97ced
db/schema.rb
generated: https://gist.github.com/Dorian/a8449287fa62b88463f48da986c1744a

- 22,759
- 8
- 120
- 116
If you are creating a new table just add unique: true
class CreatePosts < ActiveRecord::Migration[6.0]
def change
create_table :posts do |t|
t.string :title, unique: true
t.text :body
t.references :user, foreign_key: true
t.timestamps
end
add_index :posts, :user_id, unique: true
end
end

- 111
- 8