Reputation: 5721
Let's say that I have a class that represents locations. Locations "belong" to customers. Locations are identified by a unicode 10 character code. The "location code" should be unique among the locations for a specific customer.
The two below fields in combination should be unique
customer_id = Column(Integer,ForeignKey('customers.customer_id')
location_code = Column(Unicode(10))
So if i have two customers, customer "123" and customer "456". They both can have a location called "main" but neither could have two locations called main.
I can handle this in the business logic but I want to make sure there is no way to easily add the requirement in sqlalchemy. The unique=True option seems to only work when applied to a specific field and it would cause the entire table to only have a unique code for all locations.
Upvotes: 276
Views: 181203
Reputation: 4364
This Python3 answer using Flask and sqlalchemy is completely derivative, it just puts everything from above into a small self-contained working example for MySQL. I needed a uniqueness constraint on the table that implements a many-to-many relationship. Maybe you can run this to debug local environment problems, in my case they were purely between the keyboard and the chair :)
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, ForeignKey, Integer, String, UniqueConstraint
from sqlalchemy.orm import relationship
import logging
logging.basicConfig()
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqlconnector://user:pass@localhost/mydb'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
user_role = db.Table(
'user_role',
Column('uid', String(6), ForeignKey('user.uid')),
Column('role', String(32), ForeignKey('role.name')),
UniqueConstraint('uid', 'role', name='idx_uid_role'))
class UserModel(db.Model):
__tablename__ = 'user'
uid = Column(String(6), primary_key=True)
create_time = Column(Integer, nullable=False)
login_time = Column(Integer, nullable=True)
roles = relationship('RoleModel', secondary='user_role',
backref='user', lazy='joined')
class RoleModel(db.Model):
__tablename__ = 'role'
name = Column(String(32), primary_key=True)
description = Column(String(256), nullable=False)
db.create_all()
After you run this, check the indexes defined for the table like this:
mysql> show index from user_role;
And you should see:
+-----------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-----------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| user_role | 0 | idx_uid_role | 1 | uid | A | 0 | NULL | NULL | YES | BTREE | | | YES | NULL |
| user_role | 0 | idx_uid_role | 2 | role | A | 0 | NULL | NULL | YES | BTREE | | | YES | NULL |
| user_role | 1 | role | 1 | role | A | 0 | NULL | NULL | YES | BTREE | | | YES | NULL |
+-----------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
3 rows in set (0.00 sec)
Create test data:
mysql> insert into user values ('abc123', 1, 2);
mysql> insert into role values ('role1', 'Description');
mysql> insert into user_role (uid, role) values ('abc123', 'role1');
Finally, test the uniqueness constraint by running that last insert a second time, you should see:
mysql> insert into user_role (uid, role) values ('abc123', 'role1');
ERROR 1062 (23000): Duplicate entry 'abc123-role1' for key 'user_role.idx_uid_role'
Upvotes: 4
Reputation: 21
__table_args__ = (
Index("idx_room_user", room_id, uid, unique=True),
UniqueConstraint(room_id, uid, name='u_room_user'),
)
It worked under sqlalchemy 2.0
Upvotes: 2
Reputation: 2321
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Location(Base):
__table_args__ = (
# this can be db.PrimaryKeyConstraint if you want it to be a primary key
db.UniqueConstraint('customer_id', 'location_code'),
)
customer_id = Column(Integer,ForeignKey('customers.customer_id')
location_code = Column(Unicode(10))
Upvotes: 50
Reputation: 77082
Extract from the documentation of the Column
:
unique – When True, indicates that this column contains a unique constraint, or if index is True as well, indicates that the Index should be created with the unique flag. To specify multiple columns in the constraint/index or to specify an explicit name, use the UniqueConstraint or Index constructs explicitly.
As these belong to a Table and not to a mapped Class, one declares those in the table definition, or if using declarative as in the __table_args__
:
# version1: table definition
mytable = Table('mytable', meta,
# ...
Column('customer_id', Integer, ForeignKey('customers.customer_id')),
Column('location_code', Unicode(10)),
UniqueConstraint('customer_id', 'location_code', name='uix_1')
)
# or the index, which will ensure uniqueness as well
Index('myindex', mytable.c.customer_id, mytable.c.location_code, unique=True)
# version2: declarative
class Location(Base):
__tablename__ = 'locations'
id = Column(Integer, primary_key = True)
customer_id = Column(Integer, ForeignKey('customers.customer_id'), nullable=False)
location_code = Column(Unicode(10), nullable=False)
__table_args__ = (UniqueConstraint('customer_id', 'location_code', name='_customer_location_uc'),
)
Upvotes: 464