SQLAlchemy - Complex Sub-querying with join - python

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)))\

Related

Correlated subquery in SQLAlchemy

I have the following query:
SELECT ID, (SELECT SUM(AMOUNT) FROM PURCHASE_PAYMENTS WHERE PURCHASE_PAYMENTS.PURCHASE_ID
= PURCHASES.ID) AS PAID
-> FROM PURCHASES;
which works as I expect. I have problems translating this to SQLAlchemy. My mapped classes are ( I only show the fields of interest):
class Purchase(Base):
__tablename__ = 'purchases'
id = Column(Integer, primary_key=True)
class PurchasePayment(Base):
__tablename__ = 'purchase_payments'
id = Column(Integer, primary_key=True)
purchase_id = Column(Integer, ForeignKey('purchases.id'))
amount = Column(Numeric(19, 4), nullable=False)
Also, I need the results to be like,
(models.db.Purchase object at 0x0000021DD3683D..., Decimal('1234'))
I tried unsuccessfully to follow this post Can we make correlated queries with SQLAlchemy.
Any help?

Subquery with count in SQLAlchemy

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()

sqlalchemy foreign keys / query joins

Hi im having some trouble with foreign key in sqlalchemy not auto incrementing on a primary key ID
Im using: python 2.7, pyramid 1.3 and sqlalchemy 0.7
Here is my models
class Page(Base):
__tablename__ = 'page'
id = Column(Integer, ForeignKey('mapper.object_id'), autoincrement=True, primary_key=True)
title = Column(String(30), unique=True)
title_slug = Column(String(75), unique=True)
text = Column(Text)
date_added = Column(DateTime)
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(100), unique=True)
email = Column(String(100), unique=True)
password = Column(String(100))
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
name = Column(String(100), unique=True)
class Member(Base):
__tablename__ = 'members'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
group_id = Column(Integer, ForeignKey('groups.id'), primary_key=True)
class Resource(Base):
__tablename__ = 'resource'
id = Column(Integer, primary_key=True)
tablename = Column(Text)
action = Column(Text)
class Mapper(Base):
__tablename__ = 'mapper'
resource_id = Column(Integer, ForeignKey('resource.id'), primary_key=True)
group_id = Column(Integer, ForeignKey('groups.id'), primary_key=True)
object_id = Column(Integer, primary_key=True)
and here is my RAW SQL query which i've written in SQLAlchemys ORM
'''
SELECT g.name, r.action
FROM groups AS g
INNER JOIN resource AS r
ON m.resource_id = r.id
INNER JOIN page AS p
ON p.id = m.object_id
INNER JOIN mapper AS m
ON m.group_id = g.id
WHERE p.id = ? AND
r.tablename = ?;
'''
obj = Page
query = DBSession().query(Group.name, Resource.action)\
.join(Mapper)\
.join(obj)\
.join(Resource)\
.filter(obj.id == obj_id, Resource.tablename == obj.__tablename__).all()
the raw SQL Query works fine without any relations between Page and Mapper, but SQLAlchemys ORM seem to require a ForeignKey link to be able to join them. So i decided to put the ForeignKey at Page.id since Mapper.object_id will link to several different tables.
This makes the SQL ORM query with the joins work as expected but adding new data to the Page table results in a exception.
FlushError: Instance <Page at 0x3377c90> has a NULL identity key.
If this is an auto- generated value, check that the database
table allows generation of new primary key values, and that the mapped
Column object is configured to expect these generated values.
Ensure also that this flush() is not occurring at an inappropriate time,
such as within a load() event.
here is my view code:
try:
session = DBSession()
with transaction.manager:
page = Page(title, text)
session.add(page)
return HTTPFound(location=request.route_url('home'))
except Exception as e:
print e
pass
finally:
session.close()
I really don't know why, but i'd rather have the solution in SQLalchemy than doing the RAW SQL since im making this project for learning purposes :)
I do not think autoincrement=True and ForeignKey(...) play together well.
In any case, for join to work without any ForeignKey, you can just specify the join condition in the second parameter of the join(...):
obj = Page
query = DBSession().query(Group.name, Resource.action)\
.join(Mapper)\
.join(Resource)\
.join(obj, Resource.tablename == obj.__tablename__)\
.filter(obj.id == obj_id)\
.all()

Having issues creating a query with an inner join and an outer join

I have the following model (simplified):
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
class Thing(Base):
__tablename__ = 'thing'
id = Column(Integer, primary_key=True)
class Relationship(Base):
__tablename__ = 'relationship'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('thing.id'))
parent = relationship('Thing', backref='parentrelationships', primaryjoin = "Relationship.parent_id == Thing.id")
child_id = Column(Integer, ForeignKey('thing.id'))
child = relationship('Thing', backref='childrelationships', primaryjoin = "Relationship.child_id == Thing.id")
class Vote(Base)
__tablename__ = 'vote'
id = Column(Integer, primary_key=True)
rel_id = Column(Integer, ForeignKey('relationship.id'))
rel = relationship('Relationship', backref='votes')
voter_id = Column(Integer, ForeignKey('user.id'))
voter = relationship('User', backref='votes')
I wanted to query all Relationships with a certain parent, and I also want to query votes made by a certain user on those Relationships. What I've tried:
def get_relationships(thisthing, thisuser):
return DBSession.query(Relationship, Vote).\
filter(Relationship.parent_id == thisthing.id).\
outerjoin(Vote, Relationship.id == Vote.rel_id).\
filter(Vote.voter_id == thisuser.id).\
filter(Vote.rel_id == Relationship.id).\
all()
as well as:
def get_relationships(thisthing, thisuser):
session = DBSession()
rels = session.query(Relationship).\
filter(Relationship.parent_id == thisthing.id).\
subquery()
return session.query(rels, Vote).\
outerjoin(Vote, rels.c.id == Vote.rel_id).\
filter(Vote.voter_id == thisuser.id).\
all()
I get nulls when I do either of these queries. What am I doing wrong?
Just turn on SQL logging (echo=True) and you will see that the resulting SQL query for the first option is something like:
SELECT relationship.id AS relationship_id, relationship.parent_id AS relationship_parent_id, relationship.child_id AS relationship_child_id, vote.id AS vote_id, vote.rel_id AS vote_rel_id, vote.voter_id AS vote_voter_id
FROM relationship LEFT OUTER JOIN vote ON relationship.id = vote.rel_id
WHERE relationship.parent_id = ? AND vote.voter_id = ? AND vote.rel_id = relationship.id
If you examine it, you will notice that the clause vote.rel_id = relationship.id is part of both the JOIN clause and the WHERE clause, which makes the query to filter out those Relationship rows which do not have any votes by requested user.
Solution:
Remove redundant filter(Vote.rel_id == Relationship.id). part from the query.
Edit-1: Also move (remove) the filter for the user filter(Vote.voter_id == thisuser.id) out of WHERE and into the LEFT JOIN clause: outerjoin(Vote, and_(Relationship.id == Vote.rel_id, Vote.voter_id == thisuser.id)).

FULL JOIN in SQLAlchemy?

I would like to display a list of "last entries" in a budget app. The entries (like expenses, income, account transfers, loans) have different columns defined apart from a user_id.
In SQL I would go for a FULL JOIN, but I am using SQLAlchemy (declarative). What is the correct approach here? Some meta table?
Thanks a lot.
Example tables:
class Expense(Base):
__tablename__ = 'expenses'
id = Column(Integer, primary_key=True)
user = Column('user_id', Integer, ForeignKey('users.id'))
date = Column(Integer)
category = Column('category_id', Integer, ForeignKey('expense_categories.id'))
description = Column(String(50))
deduct_from = Column('account_id', Integer, ForeignKey('accounts.id'))
amount = Column(Float(precision=2))
class Loan(Base):
__tablename__ = 'loans'
id = Column(Integer, primary_key=True)
from_user = Column('from_user_id', Integer, ForeignKey('users.id'))
to_user = Column('to_user_id', Integer, ForeignKey('users.id'))
date = Column(Integer)
account = Column('account_id', Integer, ForeignKey('accounts.id'))
description = Column(String(50))
amount = Column(Float(precision=2)
You'll have to use raw SQL if your database supports it or an union otherewise. from http://groups.google.com/group/sqlalchemy/msg/80ea8e712380bff4

Categories

Resources