SQLAlchemy One-to-Many relationship in single table- declarative - sqlalchemy

I have category and sub-category, I want to populate parent with child categories. How should I do?
class Category(Base):
__tablename__ = 'category'
id = Column(UUIDType, primary_key=True)
organisation_id = Column(UUIDType)
category_name = Column(String(50))
parent_category_id = Column(UUIDType, ForeignKey("category.id"))
parent_category = relationship("Category", backref='category',
remote_side=parent_category_id)

Related

Querying multiple related tables with join to generate single table using sqlalchemy

I am working on a web app which manages the inventory system and selling books and items. I want to generate the monthly based report on how many books where sold and ordered to Inventory in given period of time. In order to do this I have to join several tables. Here are my tables:
- Book Table
class Book(ResourceMixin, db.Model):
__tablename__ = 'book'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(85))
stock_amount = db.Column(db.Integer, nullable=False, server_default='0')
#Foreign Key
category_id = db.Column(db.Integer, db.ForeignKey('category.id',
onupdate='CASCADE',
ondelete='CASCADE'),
index=True, nullable=False)
Category Table
class Category(ResourceMixin, db.Model):
__tablename__ = 'category'
id = db.Column(db.Integer, primary_key=True)
category_name = db.Column(db.String(85))
isbn1 = db.Column(db.String(13))
isbn2 = db.Column(db.String(13))
total_stock_amount = db.Column(db.Integer)
unit_price = db.Column(db.Float)
selling_price = db.Column(db.Float)
bank_transfer_price = db.Column(db.Float)
unit_cost = db.Column(db.Float)
author = db.Column(db.String(100))
ordered = db.Column('is_ordered', db.Boolean(), nullable=False, server_default='0')
#Association Proxies
orders = association_proxy('book_orders', 'order')
stores = association_proxy('book_store', 'store')
supplier_id = db.Column(db.Integer, db.ForeignKey('suppliers.id',
onupdate='CASCADE',
ondelete='CASCADE'),
index=True, nullable=False)
#Relationship with Books
books = db.relationship(Book, backref=db.backref('book_category'), innerjoin=True)
BookStore table which inherits from Category table
class BookStore(ResourceMixin, db.Model):
__tablename__ = 'book_store'
id = db.Column(db.Integer, primary_key=True)
category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
store_id = db.Column(db.Integer, db.ForeignKey('store.id'))
isbn1 = db.Column(db.String)
isbn2 = db.Column(db.String)
book_amount = db.Column(db.Integer)
#Bidirectional attribute/collection of 'category'/'book_store'
category = db.relationship('Category', backref = db.backref('book_store', cascade='all, delete-orphan'),
lazy='joined', innerjoin=True,
order_by='Category.category_name')
bookstore_cart = db.relationship('StoreCart', backref='book_store_cart', passive_deletes=True)
book_purchases = association_proxy('book_orders', 'customer_purchases')
#Reference to the 'Store' object
store = db.relationship('Store')
CustomerPurchase table which is inherits from BookStore
class CustomerPurchase(ResourceMixin, db.Model):
__tablename__ = 'customer_purchase'
id = db.Column(db.Integer, primary_key=True)
book_store_category_id = db.Column(db.Integer, db.ForeignKey('book_store.id'))
customer_order_id = db.Column(db.Integer, db.ForeignKey('customer_orders.id'))
book_title = db.Column(db.Text)
unit_price = db.Column(db.Float)
quantity = db.Column(db.Float)
total_price = db.Column(db.Float)
store_id = db.Column(db.Integer, db.ForeignKey('store.id',
onupdate='CASCADE',
ondelete='CASCADE'),
index=True)
#Bidirectional attribute/collection of 'bookstore'/'customer purchase'
book_purchase = db.relationship('BookStore', backref = db.backref('customer_purchases', cascade='all, delete-orphan'),
lazy='joined', innerjoin=True,
order_by='BookStore.isbn1')
#Bidirectional attribute/collection of 'store'/'purchase'
customer_order = db.relationship('CustomerOrders')
Let me explain you the working principle. So in order to sell books user has to transfer Category to the BookStore and then s/he can sell it. Sales are stored in CustomerPurchase table. If books in inventory are finished, then the User has to order books from supplier and put it in inventory, the date of the input to the inventory is captured to generate report in future.
My desired report table should include these columns
book_title | unit_cost | amount_in_stock | revenue | amount_ordered | total_price_of_ordered_books | amount_sold | revenue | amount_left | total_price_of_amount_left |
My query is:
bp = db.session.query(CustomerPurchase.book_store_category_id, BookStore.category, func.sum(CustomerPurchase.quantity).label('quantity'))\
.filter(CustomerPurchase.created_on >= start_date)\
.filter(CustomerPurchase.created_on <= end_date)\
.group_by(CustomerPurchase.book_store_category_id, BookStore.id, Category.id)\
.subquery()
cp = db.session.query(BookStore, bp.c.quantity)\
.join(bp, BookStore.category_id == bp.c.book_store_category_id)\
.distinct(bp.c.book_store_category_id)\
.order_by(bp.c.book_store_category_id)\
.all()
It outputs the CustomerPurchase and BookStore table but doesn't go beyond it for example I cannot go down CustomerPurchase.book_purchase.category.books.stock_amount or BookStore.category.books.stock_amount
When I want to access CustomerPurchase.book_purchase it throws me an error
UndefinedError: 'sqlalchemy.util._collections.result object' has no attribute 'book_purchase'
Any help is greatly appreciated! Thanks in advance!
After 2 weeks of research and collaboration I managed to solve the problem:
Now it outputs all the desired columns and returns even though a column is NULL:
my_query = db.session.query(CustomerPurchase.book_title, Category.unit_cost, Category.total_stock_amount, BookStore.book_amount,
func.sum(CustomerPurchase.quantity).label('quantity'),
func.sum(Book.stock_amount).label('book_stock_amount'))\
.join(BookStore)\
.join(Category)\
.outerjoin(Book)\
.filter(CustomerPurchase.created_on >= report.start_date.date())\
.filter(CustomerPurchase.created_on <= report.end_date.date())\
.group_by(CustomerPurchase.book_title,
Category.unit_cost, Category.total_stock_amount,
BookStore.book_amount).all()
As simple as that! Basically I have to cherry pick every column that I want to display. Also I was concerned about NULL return value of Book.stock_amount but after I used outerjoin on Book table I managed to pull out all the data I needed for my report.
I hope it will help for some people out there! :)

How to make this query in sql alchemy

SELECT * FROM city WHERE id in
(SELECT distinct(id) FROM city c WHERE id in
(SELECT city_id from address WHERE id in (SELECT address_id FROM maintener)));
I'm trying to get only the cities that has reference in my maintener address model. That is, if i have 200 cities in my city table but only 40 cities are referenced on my table of maintener, i need only the 40 cities to show in my cities filter.
I have this models in my persist sqlalchemy
class Maintener(persist.Base):
__tablename__ = 'maintener'
id = Column(Integer, primary_key=True)
name = Column(String(255))
address_id = Column(ForeignKey(u'address.id'), index=True)
address = relationship(u'Address', lazy='noload', \
primaryjoin='Maintener.address_id == Address.id')
class Address(persist.Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
state_id = Column(ForeignKey(u'state.id'), index=True)
city_id = Column(ForeignKey(u'city.id'), index=True)
state = relationship(u'State', primaryjoin='Address.state_id == State.id', lazy='noload')
city = relationship(u'City', primaryjoin='Address.city_id == City.id', lazy='noload')
class City(persist.Base):
__tablename__ = 'city'
id = Column(Integer, primary_key=True, server_default=FetchedValue(), autoincrement=False)
name = Column(String(200))
state_id = Column(ForeignKey(u'state.id'), index=True)
state = relationship(u'State',
primaryjoin='City.state_id == State.id', \
backref=backref(u'cities', lazy='noload'), lazy='noload')
If anyone could help me i'll be grateful
Bit simpler version:
SELECT *
FROM city
WHERE id in (
SELECT a.city_id
FROM address a
JOIN maintener m on m.address_id=a.id
)

How do you do many to many with more than 2 tables?

I have a table for Logs that contains various information about employees
ex:
class Log(Model):
division_id = Column(Integer, ForeignKey('division.id'), nullable=False)
division = relationship("Division")
employee_id = Column(Integer, ForeignKey("employee.id"), nullable=False)
employee = relationship("Employee")
skill_id = Column(Integer, ForeignKey("skill.id"), nullable=False)
skill = relationship("Skill")
message = Column(String, default='OK', nullable=False)
date = Column(DateTime, default=NowTime(), nullable=True)
Employee and Skill Tables look like this:
class Employee(Model):
id = Column(Integer, primary_key=True)
name = Column(String, unique=True, nullable=False)
division_id = Column(Integer, ForeignKey('division.id'), nullable=False)
division = relationship("Division")
class Skill(Model):
id = Column(Integer, primary_key=True)
name = Column(String, unique=True, nullable=False)
I am currently using Flask-Appbuilder and I have a Skill View that displays all the logs for the current selected skill.
class LogView(ModelView):
datamodel = SQLAInterface(Log)
list_columns = ['division', 'employee', 'skill', 'message', 'date']
show_template = 'appbuilder/general/model/show_cascade.html'
class SkillLogView(ModelView):
datamodel = SQLAInterface(Skill)
list_columns = ['name']
related_views = [LogView]
show_template = 'appbuilder/general/model/show_cascade.html'
In the SkillLogView, I also want to display a list of employee names that have this skill.
How do I also get the employees from the logs that pertain to the current skill ?
I am not sure how to do it, but I thought it might be a case for many to many. The problem is that there are 3 tables, not 2.
Is there a way to do many to many with more than 2 tables ?
Or is there another way to accomplish what I want to do ?
Any help appreciated.
Your Employee has not established any relationship with Log, so querying Employee using join with Log for a predicate is difficult.
However, you can simply query the Log for employee_id with the skill_id as a subquery, and fetch Employee with the given result.
# Subquery returns `employee_id` from `logs` with the given `skill_id`
sq = session.query(Log.employee_id).\
filter(Log.skill_id == skill_id).\
subquery()
# Fetch `Employee` that matches `employee_id` in `sq`
q = session.query(Employee).\
filter(Employee.employee_id.in_(sq))
employees = q.all() # or paginate, e.g. q.limit(..).all()
There is an example online that does this very thing!
It has a person table, car table, car ownership table.
All I had to do was substitute employee for person, skill for car, and log for car ownership.
Reference URL:
Many-to-Many with association object and all relationships defined crashes on delete
Updated Code:
class Log(Model):
division_id = Column(Integer, ForeignKey('division.id'), nullable=False)
division = relationship("Division")
employee_id = Column(Integer, ForeignKey("employee.id"), nullable=False)
employee = relationship("Employee", backref=backref('log', passive_deletes='all'))
skill_id = Column(Integer, ForeignKey("skill.id"), nullable=False)
skill = relationship("Skill", backref=backref('log', passive_deletes='all'))
message = Column(String, default='OK', nullable=False)
date = Column(DateTime, default=NowTime(), nullable=True)
class Skill(Model):
id = Column(Integer, primary_key=True)
name = Column(String, unique=True, nullable=False)
employees = relationship('Employee', secondary='log', backref='skill')
class Employee(Model):
id = Column(Integer, primary_key=True)
name = Column(String, unique=True, nullable=False)
division_id = Column(Integer, ForeignKey('division.id'), nullable=False)
division = relationship("Division")
skills = relationship('Skill', secondary='log', backref='employee')

Check join condition of a relationship in SqlAlchemy

Say I have a grouped parent / child relationship, with composite foreign keys, like this:
class Group(Base):
__tablename__ = 'group'
id_ = Column('id', GUID, primary_key=True)
class Parent(Base):
__tablename__ = 'parent'
id_ = Column('id', GUID, primary_key=True)
group_id = Column(GUID, ForeignKey('group.id'), primary_key=True)
group = relationship(Group)
class Child(Base):
__tablename__ = 'child'
id_ = Column('id', GUID, primary_key=True)
group_id = Column(GUID, ForeignKey('group.id'), primary_key=True)
parent_id = Column(GUID)
__table_args__ = (
ForeignKeyConstraint(
['parent_id', 'group_id'],
['parent.id', 'parent.group_id']
),
)
group = relationship(Group)
parent = relationship(Parent, foreign_keys=[parent_id], backref='children')
My actual question is: how can I find out what the join condition would be? I'm hoping for something like Child.parent.join_condition
Bonus points: will Child.parent produce a join condition like this: ON (child.group_id = parent.group_id AND child.parent_id = parent.id) Or do I need to be explicit and use a primaryjoin argument? Edit I just re-read this section of the docs, and it looks like it won't.
You do not need to use primaryjoin in this case. It is enough to define foreign_keys by including group_id as well:
class Child(Base):
...
parent = relationship(
Parent,
foreign_keys=[parent_id, group_id],
backref='children',
)
In this case including join like:
q = session.query(Child).join(Parent)
print(q)
will produce:
SELECT child.id AS child_id,
child.group_id AS child_group_id,
child.parent_id AS child_parent_id
FROM child
JOIN parent
ON parent.group_id = child.group_id
AND parent.id = child.parent_id
Note: one does not need parenthesis around the ON condition.

SQLAlchemy distinct related objects

I have a simple messaging system with Conversation, User and Message objects defined like so:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
class Conversation(db.Model):
id = db.Column(db.Integer, primary_key=True)
class Message(db.Model):
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.String)
author = db.relationship('User')
conversation = db.relationship('Conversation', backref=db.backref('messages', lazy='dynamic'))
I would like to get all the Users that participate in a conversations.
It's easy for me in SQL:
select distinct users.*
from users inner join messages on messages.author_id = users.id
where messages.conversation_id = 1;
But I can't figure out how do that with SQLAlchemy's syntax so I get back objects and not just fields. Ideally I would like to implement it as a get_authors method on Conversation.
session.query(User).distinct().\
join(Message, Message.author_id == User.id).\
filter(Message.conversation_id == 1)