Make a relationship between two tables though another - python

class Condominium(db.Model):
__tablename__ = 'condominiums'
id = db.Column(db.Integer, primary_key=True)
properties = db.relationship('Property', backref='condominiums')
class Property(db.Model):
__tablename__ = 'properties'
id = db.Column(db.Integer, primary_key=True)
condominium_id = db.Column(db.Integer, db.ForeignKey('condominiums.id'))
listings = db.relationship('Listing', backref='property')
class Listing(db.Model):
__tablename__ = 'listings'
id = db.Column(db.Integer, primary_key=True)
property_id = db.Column(db.Integer, db.ForeignKey('properties.id'))
I want to list all listings for a given condominium, like this:
SELECT listings.* FROM condominiums
INNER JOIN properties ON properties.condominium_id = condominiums.id
INNER JOIN listings ON listings.property_id = properties.id
WHERE condominiums.id = 1;
I want to be able to get a listing collection like this:
condominium = Condominium.query.get(1)
listings = condominium.listings
How can I achieve that using SQLAlchemy? Is it possible?

Assuming Condominium is 1:M to Property which is 1:M to Listing and that all foreign keys are well-defined, you can get all Listings for Condominium with id 123 thus:
session.query(Listing).join(Property).join(Condominium).filter(Condominium.id=123)

Related

SQLAlchemy - querying multiple tables and returning nested objects

Suppose we have a simple one-to-many relationship between Company and Employee, is there a way to query all companies and have a list of employees in the attribute of each company?
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
company_id = Column(Integer, ForeignKey(Company.id))
I'm looking for something like this:
>>> result = db.session.query(Company).join(Employee).all()
>>> result[0].Employee
[<Employee object at 0x...>, <Employee object at 0x...>]
The size of result should be same as the number of rows in company table.
I tried the following and it gives tuple of objects (which makes sense) instead of nice parent / child structure:
>>> db.session.query(Company, Employee).filter(Company.id = Employee.company_id).all()
It's not hard to convert this into my desired object structure but just wanted to see if there's any shortcut.
You have to configure the relationship in the parent class:
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
employees = relationship('Employee', lazy='joined') # <<< Add this line
Then you can query it without a join:
companies = session.query(Company).all()
print(companies[0].employees)
Documentation:
https://docs.sqlalchemy.org/en/13/orm/loading_relationships.html
You could do something like this:
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
employees = db.session.query(Company, Employee).filter(Company.id = self.id).all()
self.employee_list = ['{0} {1}'.format(c.first_name, c.last_name) for c in employees]
Then you could access a employee name with Company.employee_list[0]

SQLAlchemy inserting data into two tables

I am just working on my first App based on SQLAlchemy and after couple hours of work with the documentation and some videos, I still can't fix the issue.
My app is a simple CRUD grocery list. I want to keep the category of the product in separate table so here comes relationship module of the SQLAlchemy. Error msg gives me no hint at all tbh.
engine = create_engine(my_database, echo = True)
connection = engine.connect()
Base = declarative_base()
session = sessionmaker(bind=engine)
class MyEnum(enum.Enum):
one = "pieces"
two = "kg"
class ProductTable(Base):
__tablename__ = 'product'
product_id = Column(Integer, primary_key=True)
product_name = Column(String(30), nullable=False)
product_quantity = Column(Integer, nullable=False)
product_type = Column(Enum(MyEnum), nullable=False)
category_id = Column(Integer, ForeignKey('category.id'), nullable=False)
category = relationship("category", back_populates="product")
product_description = Column(String(255))
class CategoryTable(Base):
__tablename__ = 'category'
id = Column(Integer, primary_key=True)
category_name = Column(String(25), nullable=False)
Base.metadata.create_all(engine)
session = session()
cc_product = ProductTable(product_id=1,
product_name="cucumber",
product_quantity="1",
product_type="kg",
product_description="For the salad")
cc_category= CategoryTable(category_name="vegetables")
session.add(cc_product, cc_category)
session.commit()
I. Creation of the tables finished smoothly with no errors, however, is the creation itself designed properly? Each product has single category but one category should be assigned to one or more product. I made that based on one to one relationship.
II. Inserting data to both tables. I want to insert data like:
Product_id = 1
Product_name = Cucumber
Product_quantity = 1
Product_type = "kg" or "pieces"
Category = Vegetables ( from the category table)
Description = "blah blah blah"
I think there is something wrong not only with the data inserting process but also with the tables creation.
Here is the error, which tbh, doesn't tell me anything:
sqlalchemy.exc.ArgumentError: relationship 'category' expects a class or a mapper argument (received: <class 'sqlalchemy.sql.schema.Table'>)
You have two mistakes:
you wrote "category" as the Mapper class instead of "CategoryTable"
forgot to create products on 'CategoryTable'
class ProductTable(Base):
__tablename__ = 'product'
product_id = Column(Integer, primary_key=True)
product_name = Column(String(30), nullable=False)
product_quantity = Column(Integer, nullable=False)
product_type = Column(Enum(MyEnum), nullable=False)
category_id = Column(Integer, ForeignKey('category.id'), nullable=False)
categories = relationship("CategoryTable", back_populates="products")
product_description = Column(String(255))
class CategoryTable(Base):
__tablename__ = 'category'
id = Column(Integer, primary_key=True)
category_name = Column(String(25), nullable=False)
products = relationship('ProductTable', back_populates='categories')
Some more changes are still needed:
change CategoryTable to Category (also for ProductTable, better names)
you'll have constraints failing after you'll get things running...

Two levels deep one-to-many relationship with contains()

I have two tables like so
class Department(Model):
id = Column(Integer, primary_key=True)
employees = relationship('Employee', backref='department')
class Employee(Model):
id = Column(Integer, primary_key=True)
name = Column(String)
department_id = Column(Integer, ForeignKey('department.id'))
email_addresses = relationship('EmailAddress', backref='employee')
class EmailAddress(Model):
id = Column(Integer, primary_key=True)
value = Column(String)
employee_id = Column(Integer, ForeignKey('employee.id'))
And now I need to find all departments that have an employee with an email address of X.
Back when each employee was allowed to have only one email address (so they had email_address = Column(String) I used this code, but I'm not sure how to extend it to work with multiple emails:
session.query(Department).filter(
Employee.email_address==request.args['email']
).outerjoin((Employee, Department.employees))
You can use the any() method of relationship. This code should do it:
session.query(Department).filter(
Employee.email_address.any(value=request.args['email'])
).outerjoin((Employee, Department.employees))
You could use any() like this :
department1 = Department()
department2 = Department()
DBSession.add(department1)
DBSession.add(department2)
employee = Employee()
employee.email_addresses.append(EmailAddress(employee.id, 'e#mail.com'))
DBSession.add(employee)
department1.employees.append(employee)
DBSession.flush()
print(DBSession.query(Department).filter(Department.employees.any(Employee.email_addresses.any(EmailAddress.value == 'e#mail.com'))).all())
see this gist for a full example.

How do I correctly create a flask sqlalchemy many to many relationship with multiple foreign keys

In the Flask sqlalchemy documentation an example of using a simple many to many relationship is given:
tags = db.Table('tags',
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')),
db.Column('page_id', db.Integer, db.ForeignKey('page.id'))
)
class Page(db.Model):
id = db.Column(db.Integer, primary_key=True)
tags = db.relationship('Tag', secondary=tags,
backref=db.backref('pages', lazy='dynamic'))
class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)
Where one can use the following syntax to reach out to the related objects:
Page.tags
What I am trying to accomplish is basically to add the relationship below to the one above:
tag_children = db.Table('tag_children,',
db.Column('parent_id', db.Integer, db.ForeignKey('tags.tag_id')),
db.Column('child_id', db.Integer, db.ForeignKey('tags.tag_id'))
)
So that each page has tags attached to it, but each tag can have multiple children in the scope of that page. I've made a show case for a Page called cars below where I have it's tags and their respective children tags.
(Page) Cars:
Mercedes
A-series
B-series
C-series
Tesla
Tesla Roadster
Model X
Model S
Model 3
All the list items above are tag objects
I want to be able to use the following syntax bellow to get the related objects:
Page.tag.children
For instance (obvously dummy code below, but I want to be clear about what the intended purpose of the relationship is):
Cars.tesla.children
I think, you don't need another table for tag_children. Try to use SQLAlchemy Adjacency Lists:
class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('tag.id'))
children = relationship("Tag",
backref=backref('parent', remote_side=[id])
)
With this schema you may use syntax like:
for tag in page.tags: # where page is a Page instance received from db
print tag.children
It is a common syntax for working with SQLAlchemy models. Try to use it instead of the proposed Cars.tesla.children.
Something like Cars['tesla'].children may be implemented via getitem method, but i think, it's very unclear way.
Full code snippet:
class Page(Base):
__tablename__ = 'page'
id = Column(Integer, primary_key=True)
name = Column(String(256))
tags = relationship('Tag', secondary='tags',
backref=backref('pages', lazy='dynamic'))
def __str__(self):
return self.name
class Tag(Base):
__tablename__ = 'tag'
id = Column(Integer, primary_key=True)
name = Column(String(256))
parent_id = Column(Integer, ForeignKey('tag.id'))
children = relationship(
"Tag",
backref=backref('parent', remote_side=[id])
)
def __str__(self):
return self.name
class Tags(Base):
__tablename__ = 'tags'
tag_id = Column(Integer, ForeignKey('tag.id'), primary_key=True)
page_id = Column(Integer, ForeignKey('page.id'), primary_key=True)
And test case:
# Create page and tags
session.add(Page(id=1, name="cars"))
session.add(Tag(id=1, name="Mercedes"))
session.add(Tag(id=2, name="A-series", parent_id=1))
session.add(Tag(id=3, name="B-series", parent_id=1))
session.add(Tag(id=4, name="C-series", parent_id=1))
session.add(Tag(id=5, name="Tesla"))
session.add(Tag(id=6, name="Tesla Roadster", parent_id=5))
session.add(Tag(id=7, name="Model X", parent_id=5))
session.add(Tag(id=8, name="Model S", parent_id=5))
session.add(Tag(id=9, name="Model 3", parent_id=5))
# Fill relation
session.add(Tags(tag_id=1, page_id=1))
session.add(Tags(tag_id=5, page_id=1))
session.commit()
print session.query(Page).get(1) # >>> cars
print session.query(Page).get(1).tags # >>> Mercedes, Tesla
print session.query(Page).get(1).tags[1].children # >>> Tesla models

Generated queries contain redundant products?

I have rather simple models like these:
TableA2TableB = Table('TableA2TableB', Base.metadata,
Column('tablea_id', BigInteger, ForeignKey('TableA.id')),
Column('tableb_id', Integer, ForeignKey('TableB.id')))
class TableA(Base):
__tablename__ = 'TableA'
id = Column(BigInteger, primary_key=True)
infohash = Column(String, unique=True)
url = Column(String)
tablebs = relationship('TableB', secondary=TableA2TableB, backref='tableas')
class TableB(Base):
__tablename__ = 'TableB'
id = Column(Integer, primary_key=True)
url = Column(String, unique=True)
However, sqla generates queries like
SELECT "TableB".id, "TableB".url AS "TableB_url" FROM "TableB", "TableA2TableB"
WHERE "TableA2TableB".tableb_id = "TableB".id AND "TableA2TableB".tablea_id = 408997;
But why is there a cartesian product in the query when the attributes selected are those in TableB? TableA2TableB shouldn't be needed.
Thanks
As it is right now, there is a backref relationship in TableB (tableas) and it's loaded because the default loading mode is set to select.
You may want to change the TableA.tablebs to
tablebs = relationship('TableB', secondary=TableA2TableB, backref='tableas', lazy="dynamic")

Categories

Resources