I have 4 tables:
class Cebola(Base):
__tablename__ = 'cebolas'
id = Column(Integer, primary_key=True, autoincrement=True)
class Checklist(Base):
__tablename__ = 'checklists'
id = Column(Integer, primary_key=True, autoincrement=True)
checklist_type = Column(String)
cebola_id = Column(Integer, ForeignKey('cebolas.id'))
cebola = relationship('Cebola', backref=backref('checklists'))
__mapper_args__ = {'polymorphic_on': checklist_type,
'polymorphic_identity': 'generic'}
class ChecklistA(Checklist):
__tablename__ = 'checklist_a'
id = Column(Integer, ForeignKey('checklists.id', ondelete='CASCADE'), primary_key=True)
notes = Column(Unicode)
__mapper_args__ = {'polymorphic_identity': 'a'}
class ChecklistB(Checklist):
__tablename__ = 'checklist_b'
id = Column(Integer, ForeignKey('checklists.id', ondelete='CASCADE'), primary_key=True)
notes = Column(Unicode)
__mapper_args__ = {'polymorphic_identity': 'b'}
Now I need a way (probably a hybrid property) that will tell me how many checklists I have in a Cebola with notes <> ''.
I have added:
class Cebola(Base):
#hybrid_property
def number_of_comments(self):
return len([c for c in self.checklists if c.notes])
#number_of_comments(cls)
def number_of_comments(cls):
???
I have found a similar problem in SQLAlchemy - Writing a hybrid method for child count, but my example is a bit more complex.
Below should get you started:
class Cebola(Base):
__tablename__ = 'cebolas'
id = Column(Integer, primary_key=True, autoincrement=True)
#hybrid_property
def number_of_comments(self):
# return len([c for c in self.checklists if c.notes])
# #NOTE:1: below will prevent error for those that do not have `notes` column
# #NOTE:2: in any case this may issue a lot of SQL statements to load all subclasses of checklists relationship, so is not really efficient
return len([c for c in self.checklists
if hasattr(c, 'notes') and c.notes])
#number_of_comments.expression
def number_of_comments(cls):
from sqlalchemy import select
ce, cl, cla, clb = [
_.__table__ for _ in (Cebola, Checklist, ChecklistA, ChecklistB)]
s = (
select([func.count(cl.c.id).label("number_of_comments")])
.select_from(cl.outerjoin(cla).outerjoin(clb))
.where(or_(cla.c.notes != None, clb.c.notes != None))
.where(cl.c.cebola_id == cls.id)
.correlate(cls)
.as_scalar()
)
return s
Usage in a query:
q = session.query(Cebola, Cebola.number_of_comments)
for cebola, number_of_comments in q.all():
print(cebola, number_of_comments)
Related
I'm trying to do something I'm fairly sure is simple using AIOPG, which can use sqlalchemy's core API. My SQL is not great, so that's where I'm falling down here.
models
class School(Base):
__tablename__ = 'schools'
id = Column(Integer, primary_key=True, nullable=False)
sa_school = School.__table__
class SubjectCategory(Base):
__tablename__ = 'subject_categories'
id = Column(Integer, primary_key=True, nullable=False)
name = Column(String(63))
sa_subject_category = SubjectCategory.__table__
class Subject(Base):
__tablename__ == 'subjects'
id = Column(Integer, primary_key=True, nullable=False)
name = Column(String(63))
category = Column(Integer, ForeignKey('subject_categories.id'), nullable=False)
sa_subject = Subject.__table__
class SchoolSubject(Base):
__tablename__ = 'school_subjects'
id = Column(Integer, primary_key=True, nullable=False)
school = Column(Integer, ForeignKey('schools.id'), nullable=False)
subject = Column(Integer, ForeignKey('subjects.id'), nullable=False)
sa_school_subject = SchoolSubject.__table__
So I'm just trying to get all schools which teach subjects that have a certain subject_category ID.
Currently I have:
from sqlalchemy import select, join
school_c = sa_school.c
school_subj_c = sa_school_subject.c
async def get_schools(subject=None, subj_cat=None)
query = select(
[school_c.id, school_c.name]
).select_from(sa_school.join(sa_school_subject)
if subj_cat:
# Then I need to filter where a school_subj.subject.category == subj_cat
pass
elif subject:
query = query.where(sa_school_subject.c.subject == subj)
cur = await conn.execute(query)
return [dict(b) async for b in cur]
After a bit of faffing, I can simply do:
query = select(
[school_c.id, school_c.name]
).select_from(sa_school.join(sa_school_subject.join(sa_subject)))
I have 1 Parent(Parent) tables and 2 Children(Son, Daughter) tables with relationship Many To Many. So i have also 2 secondary tables for each child.
class Parent(db.Model):
id=db.Column(db.Integer, nullable=False, autoincrement=True, primary_key=True)
name = db.Column(db.String(45), nullable=False)
sons= db.relationship('Son',
secondary=parent_has_son,
back_populates='parents')
daughters = db.relationship('Daughter',
secondary=parent_has_daughter,
back_populates='parents')
Class Son(db.Model):
id=db.Column(db.Integer, nullable=False, autoincrement=True, primary_key=True)
name = db.Column(db.String(45), nullable=False)
age = db.Column(db.Integer)
son_other_col = .Column(db.String(45))
parents = db.relationship('Parent',
secondary=parent_has_son,
back_populates='sons')
Class Daughter(db.Model):
id=db.Column(db.Integer, nullable=False, autoincrement=True, primary_key=True)
name = db.Column(db.String(45), nullable=False)
age = db.Column(db.Integer)
daughter_other_col1 = .Column(db.String(45))
daughter_other_col2 = .Column(db.String(45))
parents = db.relationship('Parent',
secondary=parent_has_daughter,
back_populates='daughters')
parent_has_son = db.Table('parent_has_son ', db.metadata,
db.Column('parent_id', db.Integer, ForeignKey('Parent.id')),
db.Column('son_id', db.Integer, ForeignKey('Son.id')))
parent_has_daughter = db.Table('parent_has_daughter ', db.metadata,
db.Column('parent_id', db.Integer, ForeignKey('Parent.id')),
db.Column('daughter_id', db.Integer, ForeignKey('Daughter.id')))
So now I can :
db.session.query(Parent).options(joinedload('sons')).options(joinedload('daughters)).filter(Parent.id == 1).first()
that will return me a Parent object and i can access children via : Parent.daughters, Parent.sons.
But i want a query to return a list with sons and daughters like:
[Son1, Daughter2, Son2]
if i use this query:
db.session.query(Son, Daughter).select_from(Parent).join(Parent.sons).join(Parent.daughters).all()
it returns me something like :
[(Son1, Daughter1), (Son2, Daughter1), etc)]
which is not that i want.
I need a query to return me a list with parent's sons and daughters and order them by age. Is that possible??
Create base model Child with children relationship and use with_polymorphic:
son_and_daughter = with_polymorphic(Child, [Son, Daughter], flat=True)
then use it the following:
session.query(Parent).options(
joinedload(Parent.children.of_type(son_and_daughter))
)
See Inheritance loading
I have the following 3 SQLAlchemy Models:
class MyClassA(db.Model):
myclassa_id = db.Column(db.Integer, nullable=False, primary_key=True)
myfield_1 = db.Column(db.String(1024), nullable=True)
myclassbs = db.relationship("MyClassB", backref="myclassa")
class MyClassC(db.Model):
myclassc_id = db.Column(db.Integer, nullable=False, primary_key=True)
myfield_2 = db.Column(db.String(1024), nullable=False, unique=True)
class MyClassB(db.Model):
legal_document_content_format_id = db.Column(db.Integer, nullable=False, primary_key=True)
myclassa_id = db.Column(db.Integer, db.ForeignKey(MyClassA.myclassa_id), nullable=False)
myclassc_id = db.Column(db.Integer, db.ForeignKey(MyClassC.myclassc_id))
myfield_3 = db.Column(db.String(1024))
__table_args__ = (db.UniqueConstraint('myclassa_id', 'myclassc_id', name='unique_constraint_mcaid_mccid'),)
Here is what I want to do:
Suppose I have a -- an instance of MyClassA.
I want to return b -- the instance of MyClassB -- that meets the following 2 criteria:
b is a child record of a
b is a child record of the instance of MyClassC which has my_field2 == "Hello"
If no record meets those criteria, throw an exception.
How can I do this in a few compact lines of code??
q = (
db.session.query(MyClassB)
.join(MyClassA) # this will satisfy first criteria
.join(MyClassC) # criteria 2 part 1
.filter(MyClassC.my_field2 == "Hello") # criteria 2 part2
)
b_list = q.all()
assert b_list
But for this to work you also need to add relationship between B and C:
class MyClassC(db.Model):
# ...
myclassbs = db.relationship("MyClassB", backref="myclassc")
If you do not want to do that, you can modify the query not to rely on the relationship definition.
I have a #hybrid_property which references a nested relationship self.establishment_type.establishment_base_type.name == 'QSR'. It works on a Location object as in assert location.is_qsr == True, but not in a filter. I have tried adding a #is_qsr.expression function, but can't get any of them working. How can I enable a filter such as query(Location).filter(Location.is_qsr == True)?
class Location(Base):
__tablename__ = 'houses'
id = Column(Integer, primary_key=True)
establishment_type_id = Column(
Integer, ForeignKey('establishment_types.id')
)
establishment_type = relationship('EstablishmentType')
#hybrid_property
def is_qsr(self):
if self.establishment_type:
if self.establishment_type.establishment_base_type:
return self.establishment_type.establishment_base_type.name == 'QSR'
return False
class EstablishmentType(Base):
__tablename__ = 'establishment_types'
id = Column(Integer, primary_key=True)
establishment_base_type_id = Column(
Integer, ForeignKey('establishment_base_types.id')
)
establishment_base_type = relationship('EstablishmentBaseType')
class EstablishmentBaseType(Base):
__tablename__ = 'establishment_base_types'
id = Column(Integer, primary_key=True)
You can use .has on relationships:
#is_qsr.expression
def is_qsr(cls):
return cls.establishment_type.has(
EstablishmentType.establishment_base_type.has(
EstablishmentBaseType.name == "QSR"))
This doesn't produce the most efficient query in the world (it does a EXISTS (SELECT 1 FROM ...)) but a decent optimizer should be able to figure it out.
I am using SQLAlchemy's Single Table Inheritance for Transaction, StudentTransaction, and CompanyTransaction:
class Transaction(Base):
__tablename__ = 'transaction'
id = Column(Integer, primary_key=True)
# Who paid? This depends on whether the Transaction is a
# CompanyTransaction or a StudentTransaction. We use
# SQLAlchemy's Single Table Inheritance to make this work.
discriminator = Column('origin', String(50))
__mapper_args__ = {'polymorphic_on': discriminator}
# When?
time = Column(DateTime, default=datetime.utcnow)
# Who administered it?
staff_id = Column(Integer, ForeignKey('staff.id'))
staff = relationship(
'Staff',
primaryjoin='and_(Transaction.staff_id==Staff.id)'
)
# How much?
amount = Column(Integer) # Negative for refunds, includes the decimal part
# Type of transaction
type = Column(Enum(
'cash',
'card',
'transfer'
))
class CompanyTransaction(Transaction):
__mapper_args__ = {'polymorphic_identity': 'company'}
company_id = Column(Integer, ForeignKey('company.id'))
company = relationship(
'Company',
primaryjoin='and_(CompanyTransaction.company_id==Company.id)'
)
class StudentTransaction(Transaction):
__mapper_args__ = {'polymorphic_identity': 'student'}
student_id = Column(Integer, ForeignKey('student.id'))
student = relationship(
'Student',
primaryjoin='and_(StudentTransaction.student_id==Student.id)'
)
Then, I have a Student which defines a one-to-many relationship with StudentTransactions:
class Student(Base):
__tablename__ = 'student'
id = Column(Integer, primary_key=True)
transactions = relationship(
'StudentTransaction',
primaryjoin='and_(Student.id==StudentTransaction.student_id)',
back_populates='student'
)
#hybrid_property
def balance(self):
return sum([transaction.amount for transaction in self.transactions])
The problem is, invoking Student yields: NotImplementedError: <built-in function getitem> for the return line in Student.balance() function.
What am I doing wrong?
Thanks.
a hybrid property is a construct that allows a Python descriptor to be produced which behaves in one way at the instance level, and in another way at the class level. At the class level we wish for it to produce a SQL expression. It's not legal to use plain Python functions like sum() or list comprehensions in order to produce SQL expressions.
In this case, if I were querying from the "student" table and I wished to produce a summation of the "amount" column in the "transaction" table, I'd probably want to use a correlated subquery with a SQL aggregate function. SQL we'd look to see here would resemble:
SELECT * FROM student WHERE (
SELECT SUM(amount) FROM transaction WHERE student_id=student.id) > 500
our hybrid has to take control and produce this expression:
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
Base = declarative_base()
class Transaction(Base):
__tablename__ = 'transaction'
id = Column(Integer, primary_key=True)
discriminator = Column('origin', String(50))
__mapper_args__ = {'polymorphic_on': discriminator}
amount = Column(Integer)
class StudentTransaction(Transaction):
__mapper_args__ = {'polymorphic_identity': 'student'}
student_id = Column(Integer, ForeignKey('student.id'))
student = relationship(
'Student',
primaryjoin='and_(StudentTransaction.student_id==Student.id)'
)
class Student(Base):
__tablename__ = 'student'
id = Column(Integer, primary_key=True)
transactions = relationship(
'StudentTransaction',
primaryjoin='and_(Student.id==StudentTransaction.student_id)',
back_populates='student'
)
#hybrid_property
def balance(self):
return sum([transaction.amount for transaction in self.transactions])
#balance.expression
def balance(cls):
return select([
func.sum(StudentTransaction.amount)
]).where(StudentTransaction.student_id==cls.id).as_scalar()
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)
s.add_all([
Student(transactions=[StudentTransaction(amount=50), StudentTransaction(amount=180)]),
Student(transactions=[StudentTransaction(amount=600), StudentTransaction(amount=180)]),
Student(transactions=[StudentTransaction(amount=25), StudentTransaction(amount=400)]),
])
print s.query(Student).filter(Student.balance > 400).all()
the output at the end:
SELECT student.id AS student_id
FROM student
WHERE (SELECT sum("transaction".amount) AS sum_1
FROM "transaction"
WHERE "transaction".student_id = student.id) > ?
2014-04-19 19:38:10,866 INFO sqlalchemy.engine.base.Engine (400,)
[<__main__.Student object at 0x101f2e4d0>, <__main__.Student object at 0x101f2e6d0>]