Sqlalchemy get records with less than N parents - sqlalchemy

How to get records with less than 3 parents or without parents?
Hello everyone! I have 3 tables: Product, Order and User. Product and Order, Product and User are linked many to many.
How to get products with less than 3 orders and users?
My query is:
stmt = select(User).where(Product.user_id == User.id).subquery()
stmt1 = select(Order).where(Order.user_id == User.id).subquery()
select(Product)
.outerjoin(Product.orders)
.outerjoin(Product.users)
filter(
and_(
select(func.count("*")).select_from(stmt).as_scalar() < 3,
select(func.count("*")).select_from(stmt1).as_scalar() <3,
)
)
If I remove the second condition from _and, I get 36 products, although I only have 20.
If I apply scalars().unique().all(), I get 20 products, although I should get less, because some of them already have 3 users or 3 orders.
With second _and condition I get 0 products
Thanks you in advance!

Since you have many-to-many relationships, I'll assume you must have at least association tables (if not association objects).
I have managed to extend from your query, using the association tables and it returns the expected results.
stmt = (
select(Product)
.outerjoin(Product.orders)
.outerjoin(Product.users)
.group_by(Product.id)
.having(
and_(
select(func.count())
.select_from(product_order_AT)
.where(product_order_AT.c.product_id == Product.id)
.as_scalar()
< 3,
select(func.count())
.select_from(product_user_AT)
.where(product_user_AT.c.product_id == Product.id)
.as_scalar()
< 3,
)
)
)
Now, it's not very pretty but it is readable, and produces the output you seem to want.
Full demo.
from sqlalchemy import (
Column,
ForeignKey,
Integer,
Table,
and_,
create_engine,
func,
select,
)
from sqlalchemy.orm import Session, declarative_base, relationship
Base = declarative_base()
product_order_AT = Table(
"product_order_AT",
Base.metadata,
Column("order_id", ForeignKey("order.id"), primary_key=True),
Column("product_id", ForeignKey("product.id"), primary_key=True),
)
product_user_AT = Table(
"product_user_AT",
Base.metadata,
Column("user_id", ForeignKey("user.id"), primary_key=True),
Column("product_id", ForeignKey("product.id"), primary_key=True),
)
class Product(Base):
__tablename__ = "product"
id = Column(Integer, primary_key=True)
orders = relationship(
"Order", secondary=product_order_AT, back_populates="products"
)
users = relationship(
"User", secondary=product_user_AT, back_populates="products"
)
def __repr__(self):
return f"Product(orders={self.orders}, users={self.users})"
class Order(Base):
__tablename__ = "order"
id = Column(Integer, primary_key=True)
products = relationship(
"Product", secondary=product_order_AT, back_populates="orders"
)
def __repr__(self):
return f"Order(id={self.id})"
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
products = relationship(
"Product", secondary=product_user_AT, back_populates="users"
)
def __repr__(self):
return f"User(id={self.id})"
engine = create_engine("sqlite://", echo=True, future=True)
Base.metadata.create_all(engine)
with Session(engine) as session:
p1 = Product()
p2 = Product()
p3 = Product()
p4 = Product()
o1 = Order()
o2 = Order()
o3 = Order()
o4 = Order()
u1 = User()
u2 = User()
u3 = User()
u4 = User()
p1.orders.extend([o1, o2, o3, o4])
p1.users.extend([u1, u2, u3, u4])
p2.orders.extend([o1, o2, o3, o4])
p2.users.extend([u1, u2])
p3.orders.extend([o1, o2])
p3.users.extend([u1, u2, u3, u4])
p4.orders.extend([o1, o2])
p4.users.extend([u1, u2])
session.add_all([p1, p2, p3, p4])
session.commit()
with Session(engine) as session:
stmt = (
select(Product)
.outerjoin(Product.orders)
.outerjoin(Product.users)
.group_by(Product.id)
.having(
and_(
select(func.count())
.select_from(product_order_AT)
.where(product_order_AT.c.product_id == Product.id)
.as_scalar()
< 3,
select(func.count())
.select_from(product_user_AT)
.where(product_user_AT.c.product_id == Product.id)
.as_scalar()
< 3,
)
)
)
print(session.scalars(stmt).all())
prints
[Product(orders=[Order(id=2), Order(id=1)], users=[User(id=1), User(id=2)])]

Related

how to find available quantity of products Django ORM

Inaccurate results using Django ORM for calculating available quantity of products.
I'm facing a problem in finding the available quantity of products using the Django ORM. I've written a code that uses the ORM to annotate the queryset with the quantity produced, quantity sold and available quantity fields, but the results are inaccurate. However, when I use raw SQL to perform the same calculations, the results are accurate. I need help understanding why the ORM code is not working as expected and how to fix it.
Django ORM code for finding quantities of products:
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(Quantity_Produced=Sum(F('production__qunatity_produced')))
queryset = queryset.annotate(Quantity_Sold=Sum(F('sales__qunatity_delivered')))
queryset = queryset.annotate(Quantity_available=Sum(F('production__qunatity_produced'))
- Sum(F('sales__qunatity_delivered')))
return queryset
The Output (Inaccurate):
{
"product_id": 1,
"product_name": "Product 1",
"weight": 10.0,
"Quantity_Produced": 6300.0,
"Quantity_Sold": 2600.0,
"Quantity_available": 3700.0
}
ORM's Raw SQL method for finding the available quantity of products:
def get_queryset(self):
queryset= models.Products.objects.raw('''
SELECT
*,
(SELECT SUM(q.qunatity_produced) FROM production q WHERE q.product_id = p.product_id) AS Quantity_Produced ,
(SELECT SUM(s.qunatity_delivered) FROM Sales s WHERE s.product_id = p.product_id) AS Quantity_Sold,
sum((SELECT SUM(q.qunatity_produced) FROM production q WHERE q.product_id = p.product_id)
-(SELECT SUM(s.qunatity_delivered) FROM Sales s WHERE s.product_id = p.product_id))as Quantity_available
FROM
products p
group by Product_id
order by Product_id
''')
return queryset
The Output (Accurate):
{
"product_id": 1,
"product_name": "Product 1",
"weight": 10.0,
"Quantity_Produced": 700.0,
"Quantity_Sold": 260.0,
"Quantity_available": 440.0
}
Models:
class Products(models.Model):
product_id = models.AutoField(primary_key=True)
product_name = models.CharField(max_length=255)
weight = models.ForeignKey(Bags, models.DO_NOTHING)
class Production(models.Model):
production_id = models.AutoField(primary_key=True)
product = models.ForeignKey('Products', models.DO_NOTHING)
date_of_production = models.DateField()
qunatity_produced = models.FloatField()
unit_price = models.FloatField()
class Sales(models.Model):
sales_id = models.AutoField(primary_key=True)
date_of_sale = models.DateField()
customer = models.ForeignKey(Customers, models.DO_NOTHING)
product = models.ForeignKey(Products, models.DO_NOTHING)
qunatity_delivered = models.FloatField()
unit_price = models.FloatField()
total_amount = models.FloatField(default=0.0)
payment_type = models.ForeignKey(Paymenttype, models.DO_NOTHING)
description = models.TextField(blank=True, null=True)
address= models.CharField(max_length=255)

How to Query 4 tables with the key in the 4th table in Flask SQLAlchemy?

I have four tables with the structure as below:
class Vendors(db.Model):
id = db.Column(db.Integer, primary_key=True)
companyName = db.Column(db.String(100))
billingAddress = db.Column(db.String(100))
shippingAddress = db.Column(db.String(100))
contactPersonName = db.Column(db.String(100))
contactPersonNumber = db.Column(db.String(100))
gstNo = db.Column(db.String(100))
openingBalance = db.Column(db.BIGINT)
creditPeriod = db.Column(db.String(100))
vendorCode = db.Column(db.String(100))
vendorProducts = db.relationship('VendorsProduct', backref="vendProd")
class VendorsProduct(db.Model):
id = db.Column(db.Integer, primary_key=True)
bf = db.Column(db.Integer)
gsm = db.Column(db.Integer)
types = db.Column(db.String(100))
itemCode = db.Column(db.String(100))
vendorID = db.Column(db.String(100), db.ForeignKey('vendors.vendorCode'))
vendorInwards = db.relationship('VendorsInward', backref="vendInward")
class VendorsInward(db.Model):
id = db.Column(db.Integer, primary_key=True)
reel = db.Column(db.String(100))
deckle = db.Column(db.Integer)
weight = db.Column(db.Integer)
vendorInwardID = db.Column(
db.String(100), db.ForeignKey('vendors_product.itemCode'))
reelID = db.relationship('ReelStatusTable', backref="reelStatusTable")
class ReelStatusTable(db.Model):
id = db.Column(db.Integer, primary_key=True)
reelStatus = db.Column(db.String(100))
reelCode = db.Column(db.String(100), db.ForeignKey('vendors_inward.reel'))
In the ReelStatusTable under the reelStatus I have values named In Stock. I want to query in Flask-SQLAlchemy all the In Stock entries associated with a Vendors table.
I tried the following code and it simply displays all the values from the reelStatus and I cannot only query the In Stock ones :(
vendorsProductsList = db.session.query(
Vendors
).join(
VendorsProduct
).join(
VendorsInward
).join(
ReelStatusTable
).filter(
ReelStatusTable.reelStatus == "In Stock"
).all()
Please help me query the In Stock entries.
NOTE: I want to use
Vendors.name, VendorsProduct.bf, VendorsProduct.gsm, VendorsProduct.types, VendorsInward.reel, VendorsInward.deckle, VendorsInward.weight and ReelStatusTable.reelStatus fields in my flask application
Thank you in Advance.
It worked after I tried this query !!
vendorsProductsList = db.session.query(Vendors,
VendorsProduct,
VendorsInward,
ReelStatusTable
).filter(
ReelStatusTable.reelStatus == "In Stock"
).filter(
ReelStatusTable.reelCode == VendorsInward.reel
).filter(
VendorsInward.vendorInwardID == VendorsProduct.itemCode
).filter(
VendorsProduct.vendorID == Vendors.vendorCode
).all()

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! :)

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 query result order by relationship filter count

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)