Subquery with count in SQLAlchemy - python

Given these SQLAlchemy model definitions:
class Store(db.Model):
__tablename__ = 'store'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
class CustomerAccount(db.Model, AccountMixin):
__tablename__ = 'customer_account'
id = Column(Integer, primary_key=True)
plan_id = Column(Integer, ForeignKey('plan.id'), index=True, nullable=False)
store = relationship('Store', backref='account', uselist=False)
plan = relationship('Plan', backref='accounts', uselist=False)
class Plan(db.Model):
__tablename__ = 'plan'
id = Column(Integer, primary_key=True)
store_id = Column(Integer, ForeignKey('store.id'), index=True)
name = Column(String, nullable=False)
subscription_amount = Column(Numeric, nullable=False)
num_of_payments = Column(Integer, nullable=False)
store = relationship('Store', backref='plans')
How do I write a query to get a breakdown of subscription revenues by plan?
I'd like to get back a list of the plans for a given Store, and for each plan the total revenues for that plan, calculated by multiplying Plan.subscription_amount * Plan.num_of_payments * num of customers subscribed to that plan
At the moment I'm trying with this query and subquery:
store = db.session.query(Store).get(1)
subscriber_counts = db.session.query(func.count(CustomerAccount.id)).as_scalar()
q = db.session.query(CustomerAccount.plan_id, func.sum(subscriber_counts * Plan.subscription_amount * Plan.num_of_payments))\
.outerjoin(Plan)\
.group_by(CustomerAccount.plan_id)
The problem is the subquery is not filtering on the current plan id.
I also tried with this other approach (no subquery):
q = db.session.query(CustomerAccount.plan_id, func.count(CustomerAccount.plan_id) * Plan.subscription_amount * Plan.num_of_payments)\
.outerjoin(Plan)\
.group_by(CustomerAccount.plan_id, Plan.subscription_amount, Plan.num_of_payments)
And while the results seem fine, I don't know how to get back the plan name or other plan columns, as I'd need to add them to the group by (and that changes the results).
Ideally if a plan doesn't have any subscribers, I'd like it to be returned with a total amount of zero.
Thanks!

Thanks to Alex Grönholm on #sqlalchemy I ended up with this working solution:
from sqlalchemy.sql.expression import label
from sqlalchemy.sql.functions import coalesce
from instalment.models import db
from sqlalchemy import func, desc
def projected_total_money_volume_breakdown(store):
subscriber_counts = db.session.query(
CustomerAccount.plan_id,
func.count(CustomerAccount.id).label('count')
).group_by(CustomerAccount.plan_id) \
.subquery()
total_amount_exp = coalesce(
subscriber_counts.c.count, 0
) * Plan.subscription_amount * Plan.num_of_payments
return db.session.query(
Plan,
label('total_amount', total_amount_exp)
) \
.outerjoin(subscriber_counts, subscriber_counts.c.plan_id == Plan.id) \
.filter(Plan.store == store) \
.order_by(desc('total_amount')) \
.all()

Related

How to access column values in SQLAlchemy result list after a join a query

I need to access colums of result query. I have these models
class Order(Base):
__tablename__ = "orders"
internal_id = Column(Integer, primary_key=True)
total_cost = Column(Float, nullable=False)
created_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=text("now()"))
customer_id = Column(Integer, ForeignKey("customers.id", ondelete="CASCADE"), nullable=False)
customer = relationship("Customer")
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, nullable=False)
internal_id = Column(Integer, nullable=False)
price = Column(Float, nullable=False)
description = Column(String, nullable=False)
order_id = Column(Integer, ForeignKey("orders.internal_id", ondelete="CASCADE"), nullable=False)
order = relationship("Order")
Now I run this left join query that gives me all the columns from both tables
result = db.query(Order, Item).join(Item, Item.order_id == Order.internal_id, isouter=True).filter(Item.order_id == order_id).all()
I get back a list of tuples. How do I access a particular column of the result list? Doing something like this
for i in result:
print(i.???) # NOW WHAT?
Getting AttributeError: Could not locate column in row for column anytime i try to fetch it by the name I declared.
this is the full function where I need to use it
#router.get("/{order_id}")
def get_orders(order_id: int, db: Session = Depends(get_db)):
""" Get one order by id. """
# select * from orders left join items on orders.internal_id = items.order_id where orders.internal_id = {order_id};
result = db.query(Order, Item).join(Item, Item.order_id == Order.internal_id, isouter=True).filter(Item.order_id == order_id).all()
for i in result:
print(i.description) # whatever value i put here it errors out
This is the traceback
...
print(i.description) # whatever value i put here it errors out
AttributeError: Could not locate column in row for column 'description'
At least if I could somehow get the column names.. But i just cant get them. Trying keys(), _metadata.keys .. etc. Nothing works so far.
If additional implicite queries are not an issue for you, you can do something like this:
class Order(Base):
__tablename__ = "orders"
internal_id = Column(Integer, primary_key=True)
total_cost = Column(Float, nullable=False)
created_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=text("now()"))
customer_id = Column(Integer, ForeignKey("customers.id", ondelete="CASCADE"), nullable=False)
customer = relationship("Customer")
items = relationship("Item", lazy="dynamic")
order = session.query(Order).join(Item, Order.internal_id == Item.order_id, isoutrr=True).filter(Order.internal_id == order_id).first()
if order:
for i in order.items:
print(i.description)
print(order.total_cost)
However to avoid additional query when accessing items you can exploit contains_eager option:
from sqlalchemy.orm import contains_eager
order = session.query(Order).join(Item, Order.internal_id == Item.order_id, isoutrr=True).options(contains_eager("items").filter(Order.internal_id == order_id).all()
Here you have some examples: https://jorzel.hashnode.dev/an-orm-can-bite-you
Ok, so acctualy the answer is quite simple. One just simply needs to use dot notation like i.Order.total_cost or whichever other field from the Order model
result = db.query(Order, Item).join(Item, Item.order_id == Order.internal_id, isouter=True).filter(Item.order_id == order_id).all()
for i in result:
print(i.Order.total_cost)
print(i.Item.description)

fastAPI sqlalchemy - inner JOIN on 2 tables

I have a restapi up and running using the fastAPI framework, which is starting to work well.
Now: I already have my MySQL code on how to inner join on 2 tables, and I want to be able to do the same, just using sqlalchemy.
Firstly, here is my SQL code which works perfectly:
select `the_user`.`email_adress`, `the_exercise`.`exercise_name`, `run_the_workout`.`repetitions`,`run_the_workout`.`sets`,`run_the_workout`.`pause_time`,`run_the_workout`.`day_to_perform_the_task`
from `workout_plan_task` `run_the_workout`
inner join `user_profiles` `the_user` on `run_the_workout`.the_user=`the_user`.user_id
inner join `exercises` `the_exercise` on `the_exercise`.`exercise_ID` = `run_the_workout`.`the_exercise`
WHERE `run_the_workout`.the_user=1
Now, here are my table models in SQL alchemy, representing the "user_profiles", "exercises" and the "workout_plan_task" table:
#models.py:
class UserProfiles(Base):
__tablename__ = "user_profiles"
user_ID = Column(Integer, primary_key=True, index=True)
email_adress = Column(String, unique=True)
age = Column(Integer)
sex = Column(Integer)
height = Column(Integer)
weight = Column(Integer)
main_goal = Column(Integer)
level_experience = Column(Integer)
profile_created_at = Column(Date)
class Exercises(Base):
__tablename__ = "exercises"
exercise_ID = Column(Integer, primary_key=True, index=True)
exercise_name = Column(String)
exercise_type = Column(String, nullable=True)
muscle_groups_worked_out = Column(String)
equipment_ID = Column(Integer, nullable=True)
class WorkOutPlanTask(Base):
__tablename__ = "workout_plan_task"
task_ID = Column(Integer, primary_key=True, index=True)
user_ID = Column(Integer, ForeignKey("user_profiles.user_ID"))
workout_plan_ID = Column(Integer, ForeignKey("workout_plan.workout_plan_ID"))
exercise_ID = Column(Integer, ForeignKey("exercises.exercise_ID"))
repetitions = Column(Integer)
sets = Column(Integer)
pause_time = Column(Integer)
day_to_perform_the_task = Column(String)
inside my "crud.py" file, im trying to run the query using inner joins:
#crud.py
def get_workout_plan_for_user(db: Session, user_id:int):
return db.query(models.WorkOutPlanTask).join(models.UserProfiles, models.UserProfiles.user_ID == models.WorkOutPlanTask.user_ID).join(models.Exercises, models.Exercises.exercise_ID == models.WorkOutPlanTask.exercise_ID).filter(models.UserProfiles.user_ID == user_id)
and inside my #main.py i have this:
#app.get("/all_workout_plan_tasks_for_user/{user_id}")
def get_workout_plan_for_user_by_userID(user_id: int, db:Session = Depends(get_db)):
db_workout_plan = crud.get_workout_plan_for_user(db, user_id=user_id)
if db_workout_plan is None:
raise HTTPException(status_code=404, detail="sorry.. no workoutplans found ..")
return [schemas.a_workout_plan_task.from_orm(v) for v in db.query(...)]
This gives me the error:
sqlalchemy.exc.InvalidRequestError: SQL expression, column, or mapped entity expected - got 'Ellipsis'
Anyone here who could help me?
That's a funny mistake, take a look at the return statement:
return [schemas.a_workout_plan_task.from_orm(v) for v in db.query(...)]
You're passing Ellipsis to your db.query, and obviously, it's not the expected value.

SQLAlchemy - Complex Sub-querying with join

I need to be able to convert this to SQLALchemy, I'm a bit new but it is required. The SQL Query is this:
SELECT name,
(
SELECT value from account_settings
WHERE name = "max_allowed_records" and accounts.id = account_settings.account_id
) AS max_allowed_records,
(
SELECT count(*) from employees
join employeelists on employees.employeelist_id = employeelists.id
where employeelists.account_id = accounts.id
group by employeelists.account_id
) AS RECORD_COUNT FROM accounts
having coalesce(max_allowed_records,0) < coalesce(RECORD_COUNT,0);
In Joined form, I think they produce the same result (tried it like this to better visualize it in SQLAlchemy Query):
SELECT accounts.name, account_settings.value AS max_allowed_records, count(employees.id) as RECORD_COUNT
FROM accounts
left join account_settings on account_settings.account_id = accounts.id and account_settings.name = "max_allowed_records"
left join employeelists on employeelists.account_id = accounts.id
left join employees on employees.employeelist_id = employeelists.id
group by accounts.id
having coalesce(account_settings.value,0) < coalesce(RECORD_COUNT,0)
So what I want here is that I should get the value from account settings of the account where in the name is max_allowed_records then compare that to how many employees that account has. Unfortunately the only link between account and the employees is the employeelist. I was wondering if you guys could guide me on what I need to do here? SQLAlchemy joins? or sub queries?
Table Definitions:
class Account(Base):
__tablename__ = "accounts"
id = synonym("raw_id")
name = Column(String(255), nullable=False)
settings = relationship("AccountSetting")
class AccountSetting(Base):
__tablename__ = "account_settings"
id = Column(Integer, primary_key=True)
account_id = Column(Integer, ForeignKey("accounts.id"))
name = Column(String(30))
value = Column(String(1000))
class EmployeeList(Base):
__tablename__ = "employeelists"
id = Column(Integer, primary_key=True)
raw_id = Column("id", Integer, primary_key=True)
name = Column(String(255))
account_id = Column(Integer, ForeignKey("accounts.id"))
class Employee(Base):
__tablename__ = "employees"
id = synonym("raw_id")
raw_id = Column("id", Integer, primary_key=True)
employeelist_id = Column(Integer, ForeignKey("employeelists.id"), nullable=False)
After the whole weekend resting my mind, I was able to convert the SQL Query to SQLAlchemy Query:
query = query.outerjoin(max_records_settings,\
and_(max_records_settings.name == "max_allowed_records",\
Account.id == max_records_settings.account_id))\
.outerjoin(EmployeeList, Account.id == EmployeeList.account_id)\
.add_columns(max_records_settings.value)\
.join(Employee, EmployeeList.id == Employee.employeelist_id)\
.group_by(Account.id)\
.having(coalesce(func.count(Employee.id),0) > (coalesce(max_records_settings.value,0)))\

SQLAlchemy query filter by field value in related table

I try to get devices which type is not 'TYPE_BLADE_SERVER'. Please help write a query. My solution which i try:
result = Device.query.filter(DeviceGroup.type != ModelType.TYPE_BLADE_SERVER).all()
Which doesnt filter device_group table becouse device_group renamed to device_group_1. Sql query from sqlalchemy:
SELECT * FROM device_group, device
LEFT OUTER JOIN device_model AS device_model_1 ON device_model_1.id = device.model_id
LEFT OUTER JOIN device_group AS device_group_1 ON device_group_1.id = device_model_1.group_id
WHERE device_group.type != % (type_1)s ; {'type_1': 'TYPE_BLADE_SERVER'}
Working solution but like sql hardcode:
result = Device.query.filter(text("device_group_1.type <> 'TYPE_BLADE_SERVER'")).all()
My models:
class Device(db.Model):
__tablename__ = 'device'
id = db.Column(db.Integer, primary_key=True)
hostname = db.Column(db.String, index=True)
model_id = db.Column(db.ForeignKey('device_model.id'), nullable=True)
model = db.relationship("DeviceModel", backref='devices', lazy='joined')
class DeviceModel(db.Model):
__tablename__ = 'device_model'
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.ForeignKey('device_group.id', ondelete='SET NULL'), nullable=True)
class DeviceGroup(db.Model):
__tablename__ = 'device_group'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False, unique=True)
height = db.Column(db.Integer, nullable=False)
models = db.relationship("DeviceModel", backref=backref("group", lazy="joined"), lazy='joined')
type = db.Column(sa.Enum(ModelType), nullable=False)
class ModelType(enum.Enum):
TYPE_BLADE_SERVER = 'TYPE_BLADE_SERVER'
TYPE_ENGINEERING_DEVICES = 'TYPE_ENGINEERING_DEVICES'
TYPE_DATA_STORAGE = 'TYPE_DATA_STORAGE'
You need not_
from sqlalchemy import not_
result = Device.query.filter(not_(DeviceGroup.type == ModelType.TYPE_BLADE_SERVER)).all()
or
result = Device.query.filter(~DeviceGroup.type == ModelType.TYPE_BLADE_SERVER)
Copy answer link here:
https://groups.google.com/forum/#!topic/sqlalchemy/8L1HWG7H27U|
Docs:
http://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html#the-zen-of-joined-eager-loading

Order by issue when outer joining two tables in sqlalchemy

I am a noob trying to use flask with sqlalchemy and am having an issue sorting result from a base query.
I have a parent table and two joined many-to-many association tables:
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
(...)
qty_stock = db.Column(db.Integer)
requested_products = db.relationship('RequestedProducts')
ordered_products = db.relationship('OrderedProducts')
class OrderedProducts(db.Model):
__tablename__ = 'orderedproducts'
order_id = db.Column(db.Integer, db.ForeignKey('order.id'), primary_key=True)
product_id = db.Column(db.Integer, db.ForeignKey('product.id'), primary_key=True)
quantity = db.Column(db.Integer, default=1)
qty_delivered = db.Column(db.Integer, default=0)
product = db.relationship('Product', backref='order_assocs')
class RequestedProducts(db.Model):
__tablename__ = 'requestedproducts'
request_id = db.Column(db.Integer, db.ForeignKey('request.id'), primary_key=True)
product_id = db.Column(db.Integer, db.ForeignKey('product.id'), primary_key=True)
quantity = db.Column(db.Integer, default=1)
qty_supplied = db.Column(db.Integer, default=0)
product = db.relationship('Product', backref='request_assocs')
In my view class there are 4 table columns for each product showing stock quantity, number of requested products, number of ordered products and a net stock amount, which is basically (stock quantity - requested + ordered). This is the query for the net stock values I'm trying to get working:
products = Product.query.filter_by(active_flg=True)
.filter_by(category_id=int(g.category_id))
.outerjoin(Product.requested_products)
.outerjoin(Product.ordered_products)
.group_by(Product.id)
#Count requested amount for each product
reqs = func.coalesce((func.sum(RequestedProducts.quantity) - func.sum(RequestedProducts.qty_supplied)), 0)
#Count ordered amount for each product
ords = func.coalesce((func.sum(OrderedProducts.quantity) - func.sum(OrderedProducts.qty_delivered)), 0)
result = (Product.qty_stock - reqs + ords)
products = products.order_by(result.desc())
Now, the functions work as expected, the only problem is with the order_by function - the order is scrambled. I've found out that the cause is probably in the double outer join. Does anyone have an idea how to deal with that?
Also, I am really a beginner with sqlalchemy and flask so I'd be very grateful for any advice or a better solution (executable with my limited skills). Thank you!
If you already really use Hybrid Attributes for partial sums, then it should be pretty easy to combine them together.
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
qty_stock = db.Column(db.Integer)
requested_products = db.relationship('RequestedProducts')
ordered_products = db.relationship('OrderedProducts')
#hybrid_property
def diff_orders(self):
return sum(op.quantity - op.qty_delivered
for op in self.ordered_products)
#diff_orders.expression
def diff_orders(cls):
return (db.select([db.func.coalesce(
db.func.sum(
db.func.coalesce(
OrderedProducts.quantity - OrderedProducts.qty_delivered, 0)
), 0)])
.where(OrderedProducts.product_id == cls.id)
.label("diff_orders")
)
#hybrid_property
def diff_requests(self):
return sum(op.quantity - op.qty_supplied
for op in self.requested_products)
#diff_requests.expression
def diff_requests(cls):
return (db.select([db.func.coalesce(
db.func.sum(
db.func.coalesce(
RequestedProducts.quantity - RequestedProducts.qty_supplied, 0)
), 0)])
.where(RequestedProducts.product_id == cls.id)
.label("diff_requests")
)
In which case usage can be similar to:
products = db.session.query(
Product,
# Product.diff_orders,
# Product.diff_requests,
# Product.qty_stock + Product.diff_requests - Product.diff_orders,
).order_by((Product.qty_stock + Product.diff_requests - Product.diff_orders).desc())
for x in products:
print(x)

Categories

Resources