composite two foreign key in sqlalchemy? - python

I am using SQLALchemy in flask. And I am confused how to define composite key with two foreign keys.
One foreign key from one table and second from another table.
class Store_category_mapping(db.Model):
__tablename__ = 'store_category_mapping'
category_id = db.Column(db.Integer, nullable=False)
store_id = db.Column(db.Integer, nullable=False)
store_name = db.Column(db.String(50), nullable=False)
__table_args__ = (
db.ForeignKeyConstraint(
[category_id, store_id],
[Category_master.category_id, Store_master.store_id]
),
)
It gives this error:
sqlalchemy.exc.ArgumentError: ForeignKeyConstraint on
store_category_mapping(category_id, store_id) refers to multiple
remote tables: category_master and store_master

You can't have a composite foreign key that references more than one remote table. Composite foreign keys are a way to ensure integrity when one remote table has a composite primary key.
If you want to make sure that you only ever have one store_category_mapping entry with a given combination of category_id and store_id you could go for a UniqueConstraint instead.
I'm not familiar with flask, but I assume it would look somewhat like this:
class Store_category_mapping(db.Model):
__tablename__ = 'store_category_mapping'
category_id = db.Column(db.Integer, db.ForeignKey(Category_master.category_id), nullable=False)
store_id = db.Column(db.Integer, db.ForeignKey(Store_master.store_id), nullable=False)
store_name = db.Column(db.String(50), nullable=False)
__table_args__ = (
db.UniqueConstraint('category_id','store_id'),
)

Related

Disable Foreign Key Constraints Mysqlalchemy ORM (Python)

I have two models with a relationship between them something like this:
Base = declarative_base()
class Model1(Base):
__tablename__ = 'm1'
id = Column(Integer, primary_key=True, index=True, unique=True)
Name = Column(String(255))
info = Column(String(255))
m2s = relationship("Model2", back_populates="m1")
class Model2(Base):
__tablename__ = 'm2'
id = Column(Integer, primary_key=True, index=True, unique=True)
Name = Column(String(255))
info = Column(String(255))
m1 = Column(Integer, ForeignKey('m1.id'))
Now the issue arises when I try to insert a record into Model2 which doesn't have a corresponding Foreign Key in Model1. The error I get is something like this:
(pymysql.err.IntegrityError) (1452, 'Cannot add or update a child row: a foreign key constraint fails'
Tried looking for many solutions, but none helped. I'm sure there must be a way to insert data in Model2 without having to bother about its Foreign Key constraint.

Linking two tables where the same value exists, without Primary key - SQLAlchemy

I have the following tables defined (very simplified version):
class Orders(db.Model):
id = db.Column(db.Integer, primary_key=True)
order_id = db.Column(db.Integer,nullable=False)
date_created = db.Column(db.DateTime, nullable=False)
class ProductOrders(db.Model):
id = db.Column(db.Integer, primary_key=True)
order_id = db.Column(db.Integer, nullable=False)
product_id = db.Column(db.Integer, nullable=False)
base_price = db.Column(db.Float, nullable=False)
I am using BigCommerce API and have multiple order_ids in both tables. The order_id is not unique globally but is unique per store. I am trying to work out how to link the two tables. I do have a Store table (shown below) that holds the store.id for each store, but I just cannot work out how to join the Orders and ProductOrders tables together so I can access both tables data where the store.id is the same. I just want to query, for example, a set of Orders.order_id or Orders.date_created and get ProductOrders.base_price as well.
class Store(db.Model):
id = db.Column(db.Integer, primary_key=True)
Any ideas?
Assuming id in both queries is the store_id and order_id is unique per store, you will have to apply join with AND statement.
For example: (in SQL)
Orders join ProductOrders on Orders.id = ProductOrders.id and Orders.order_id = ProductOrders.order_id
Answer is based on what I have understood from your question, sorry if that's not your required answer.
Edit:
In sqlalchemy it would be something like below:
from sqlalchemy import and_
session.query(Orders, ProductOrders).filter(and_(Orders.id == ProductOrders.id, Orders.order_id == ProductOrders.order_id)).all()
References:
https://www.tutorialspoint.com/sqlalchemy/sqlalchemy_orm_working_with_joins.htm
Using OR in SQLAlchemy

Many-to-Many with three tables relating with each other (SqlAlchemy)

I've three tables User, Device and Role. I have created a many-to-many relation b/w User and Device like this;
#Many-to-Many relation between User and Devices
userDevices = db.Table("user_devices",
db.Column("id", db.Integer, primary_key=True),
db.Column("user_id", db.Integer, db.ForeignKey("user.id")),
db.Column("device_id", db.Integer, db.ForeignKey("device.id"))))
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(60), index=True, unique=True)
devices = db.relationship("Device", secondary=userDevices, backref=db.backref('users'), lazy="dynamic")
class Device(db.Model):
__tablename__ = 'device'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60), unique=True)
This works quiet well. I can assign a device d1 to user u1 > d1.users.append(u1), and user to device > u1.devices.append(d1) and db.session.commit().
What I want more is to extend the table user_devices with one more column as role_id which will be ForeignKey for Role table. So that this table user_devices will clearly describe a Role for specific User on specific Device. after adding a column role_id in table user_devices I described Role table as;
class Role(db.Model):
__tablename__ = 'role'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60), unique=True)
device = db.relationship("Device", secondary=userDevices, backref=db.backref('roles'), lazy="dynamic")
In this way, how can I assign a Role r1 to User u1 on Device d1 ?
here is what I tried:
# First get the device, user and role
deviceRow = db.session.query(Device).filter(Device.name=="d1").first()
userRow = db.session.query(User).filter(User.username=="u1").first()
roleRow = db.session.query(Role).filter(Role.name == "r1").first()
# Then add the user on that device
deviceRow.users.append(userRow)
deviceRow.roles.append(roleRow)
This creates two rows in the table user_devices
Is there any way that we could add two attributes into the table like this ?;
deviceRow.users.append(userRow).roles.append(roleRow)
so that it creates only one row after commit() ?
An association of 3 entities is no more a simple many to many relationship. What you need is the association object pattern. In order to make handling the association a bit easier map it as a model class instead of a simple Table:
class UserDevice(db.Model):
__tablename__ = "user_devices"
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
device_id = db.Column(db.Integer, db.ForeignKey("device.id"), nullable=False)
role_id = db.Column(db.Integer, db.ForeignKey("role.id"), nullable=False)
__table_args__ = (db.UniqueConstraint(user_id, device_id, role_id),)
user = db.relationship("User", back_populates="user_devices")
device = db.relationship("Device")
role = db.relationship("Role", back_populates="user_devices")
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(60), index=True, unique=True)
user_devices = db.relationship("UserDevice", back_populates="user")
class Role(db.Model):
__tablename__ = "role"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60), unique=True)
user_devices = db.relationship("UserDevice", back_populates="role")
To associate a user with a device and a role create a new UserDevice object:
device = db.session.query(Device).filter(Device.name == "d1").first()
user = db.session.query(User).filter(User.username == "u1").first()
role = db.session.query(Role).filter(Role.name == "r1").first()
assoc = UserDevice(user=user, device=device, role=role)
db.session.add(assoc)
db.session.commit()
Note that the ORM relationships are no longer simple collections of Device etc., but UserDevice objects. This is a good thing: when you iterate over user.user_devices for example, you get information on both the device and the role the user has on it. If you do wish to provide the simpler collections as well for situations where you for example don't need the role information, you can use an associationproxy.
There is a way to have 3-way many-to-many that is not a composition of two many-to-many relationships. You need an association object because the syntax for using just a Table doesn't allow 3-way many-to-many (because secondary explicitly refers to a 2-way many-to-many).
Here is a minimum example of how to do that in general:
from sqlalchemy import ForeignKey, Column, Integer, String
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base, declared_attr
Base = declarative_base()
# Helper classes to simplify the other classes:
# 1. Adds ch column
# 2. Defines how to print it
class Ch:
ch = Column(String, nullable=False)
def __str__(self):
return self.ch
# 3. Automatically determines table name (for foreign key)
class AutoNamed:
#declared_attr
def __tablename__(cls):
return cls.__name__
class ABC(AutoNamed, Base):
a_id = Column(Integer, ForeignKey('A.a_id'), primary_key=True)
b_id = Column(Integer, ForeignKey('B.b_id'), primary_key=True)
c_id = Column(Integer, ForeignKey('C.c_id'), primary_key=True)
a = relationship('A', back_populates='abcs')
b = relationship('B', back_populates='abcs')
c = relationship('C', back_populates='abcs')
def __repr__(self):
return f'{self.a} {self.b} {self.c}'
class A(Ch, AutoNamed, Base):
a_id = Column(Integer, primary_key=True)
abcs = relationship('ABC', back_populates='a')
class B(Ch, AutoNamed, Base):
b_id = Column(Integer, primary_key=True)
abcs = relationship('ABC', back_populates='b')
class C(Ch, AutoNamed, Base):
c_id = Column(Integer, primary_key=True)
abcs = relationship('ABC', back_populates='c')
Ok, now a little explanation:
ABC is an association table that needs a single instance of each of the tables in the 3-way many-to-many.
Each of A, B, C will have references to all ABC objects that involve them added automatically when you instantiate an ABC instance.
There is a gotchya: when you use relationship.secondary, the property on the object is a list of the other type (in their case, parent.children is a list of Children objects). However, in the docs for "association objects", when translating this to Association objects, although they still name the property on the parent object children, it is actually a list of Association objects. Here, I make this explicit by calling the property abcs.
You can instantiate these like normal:
anA = A(ch='x')
anB = B(ch='y')
anC = C(ch='z')
anABC = ABC(a=anA, b=anB, c=anC)
sess.add(anABC)
As a sanity check, here's the SQL that gets generated from this for SQLite. Exactly what we expect.
CREATE TABLE "A" (
ch VARCHAR NOT NULL,
a_id INTEGER NOT NULL,
PRIMARY KEY (a_id)
);
CREATE TABLE "B" (
ch VARCHAR NOT NULL,
b_id INTEGER NOT NULL,
PRIMARY KEY (b_id)
);
CREATE TABLE "C" (
ch VARCHAR NOT NULL,
c_id INTEGER NOT NULL,
PRIMARY KEY (c_id)
);
CREATE TABLE "ABC" (
a_id INTEGER NOT NULL,
b_id INTEGER NOT NULL,
c_id INTEGER NOT NULL,
PRIMARY KEY (a_id, b_id, c_id),
FOREIGN KEY(a_id) REFERENCES "A" (a_id),
FOREIGN KEY(b_id) REFERENCES "B" (b_id),
FOREIGN KEY(c_id) REFERENCES "C" (c_id)
);

Reorder composite primary key in sqlalchemy

I want to use inheritance when defining my sqlalchemy tables.
class DatetimeBase(object):
inserted = Column(DateTime, primary_key=True)
class OtherTable(DeclarativeBase, DatetimeBase):
__tablename__ = 'other_table'
blah = Column(Integer, primary_key=True)
other_column = Column(Text)
Now I have a composite primary key on other_table, namely (inserted, blah). However, for efficiency of queries (assuming I'm looking for the latest blah, etc.), it would be better if the timestamp came after the integer, (blah, inserted). Is there some fancy way to reorder the primary key?
Yes, using PrimaryKeyConstraint:
class OtherTable(DeclarativeBase, DatetimeBase):
__tablename__ = 'other_table'
blah = Column(Integer, primary_key=True)
other_column = Column(Text)
__table_args__ = (PrimaryKeyConstraint("blah", "inserted"),)

SqlAlchemy can't determine join condition

I have 2 tables defined:
class TCableSet(Base):
__tablename__ = 'tCableSet'
ixCableSet = Column(Integer, primary_key=True)
decCableSetOne = Column(Numeric(8, 2))
decCableSetTwo = Column(Numeric(8, 2))
decCableSetThree = Column(Numeric(8, 2))
class TStepVoltage(Base):
__tablename__ = 'tStepVoltage'
ixStepVoltage = Column(Integer, primary_key=True)
ixSubReport = Column(Integer, ForeignKey('tSubReport.ixSubReport'), nullable=False)
iVoltage = Column(Integer)
ixPhaseA = Column(Integer, ForeignKey('tCableSet.ixCableSet'), nullable=False)
ixPhaseB = Column(Integer, ForeignKey('tCableSet.ixCableSet'), nullable=False)
ixPhaseC = Column(Integer, ForeignKey('tCableSet.ixCableSet'), nullable=False)
sub_report = relationship('TSubReport',
backref=backref('step_voltage'))
I understand why I am getting this error but can't figure out a proper way (yet).
When the table gets saved, I store the values in the tCableSet table and then use the id as a foreign key in my tStepVoltage table. The problem I have is when I go to retrieve the data, I want to be able to get the values(tCableSet row) along with the rest of my tStepVoltage table via a relationship, however I'm not sure how to go about this since I don't have a field in my tCableSet that can directly be linked via relationship to my tStepVoltage. I basically just needed the tCableSet for normalization
Since you have more than one foreign key that points to the same table, you have to tell SQLAlchemy which foreign key to use.
For example:
sub_report_a = relationship('TSubReport',
backref=backref('step_voltage'),
foreign_keys=[ixPhaseA])

Categories