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! :)
Related
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
)
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)
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')
I have two models
class Song(db.Model):
__tablename__ = 'song'
id = db.Column(db.Integer, primary_key=True)
collections = db.relationship('SongCollect', backref='song', lazy='dynamic')
class SongCollect(db.Model):
__tablename__ = 'songcollect'
id = db.Column(db.Integer, primary_key=True)
song_id = db.Column(db.Integer, db.ForeignKey('song.id'))
collect_status = db.Column(db.Boolean, default=True)
How can I get the Song query result order by count(SongCollect.collect_status == True)
I use flask-sqlalchemy.
Or how can I translate following sql to sqlalchemy syntax
select s.id, s.songid, s.songname,c.collect_status, sum(c.collect_status) as collect_count from song as s left join songcollect as c on s.id = c.song_id group by s.id order by collect_count desc
# aliases: not really required
s = db.aliased(Song, name="s")
c = db.aliased(SongCollect, name="c")
# expression for the count: simple `sum` might not work on `boolean`
# types for all databases, the used option below is more robust
# collect_count = db.func.sum(c.collect_status)
collect_count = db.func.sum(db.case([(c.collect_status, 1)], else_=0))
# actual query
q = (db.session
.query(s, collect_count.label("collect_count"))
.outerjoin(c)
.group_by(s)
.order_by(collect_count.desc())
)
for song, song_collect_count in q:
print(song, song_collect_count)
print("-"*80)
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)