I'm learning SQLAlchemy on a new project and I'm struggling how to get "parent" object when using group_by on a join table.
class Address(Base):
__tablename__ = "addresses"
id_address = Column(Integer, primary_key=True)
country = Column(String)
[...]
class Order(Base):
__tablename__ = "orders"
id_order = Column(Integer, primary_key=True)
id_billing = Column(Integer, ForeignKey("addresses.id_address"))
id_shipping = Column(Integer, ForeignKey("addresses.id_address"))
billing = relationship(Address, foreign_keys=id_billing)
shipping = relationship(Address, foreign_keys=id_shipping)
[...]
billing = aliased(models.Address, name="billing")
shipping = aliased(models.Address, name="shipping")
db.query(shipping.country) \
.join(billing, models.Order.billing) \
.join(shipping, models.Order.shipping) \
.group_by(shipping.country)
Returns a list, like:
[('DE',), ('ES',), ('GR',), ('HU',), ('LT',), ('LV',), ('RO',)]
What I would like is a list of models.Order objects containing only "models.Order.shipping.country".
Any hints or working examples are very appreciated!
Related
class Actions(Base):
__tablename__ = "actions"
id = Column(Integer, primary_key=True, index=True)
resource_id = Column(Integer, ForeignKey('resources.id'))
action_short_name = Column(String(20),nullable=True)
description = Column(String(250),nullable=True)
status = Column(String(1),nullable=True)
action_resource = relation(Resource, backref = "actions")
detailsQ = session.query(Actions).join(Resource).filter(Actions.resource_id == Resource.id).all()
I don't want to use join so how I am able to fetch data though I have made relations ?
Background
The documentation gives the following example of a parent-child-association being added by appending that association to p.children. The child is then accessed via p.children and I assume the other way is possible as well, i.e accessing the parent via c.parents.
class Association(Base):
__tablename__ = 'association'
left_id = Column(ForeignKey('left.id'), primary_key=True)
right_id = Column(ForeignKey('right.id'), primary_key=True)
extra_data = Column(String(50))
child = relationship("Child", back_populates="parents")
parent = relationship("Parent", back_populates="children")
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Association", back_populates="parent")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
parents = relationship("Association", back_populates="child")
# create parent, append a child via association
p = Parent()
a = Association(extra_data="some data")
a.child = Child()
p.children.append(a)
# iterate through child objects via association, including association
# attributes
for assoc in p.children:
print(assoc.extra_data)
print(assoc.child)
Problem
I have three classes that are almost identical to those in the example:
class TopicSubcription(Base):
__tablename__ = 'topic_subscription'
topic_id = Column(ForeignKey('topic.id'), primary_key=True)
user_id = Column(ForeignKey('user.id'), primary_key=True)
subscription_date = Column('subscription_date', DateTime, nullable=True)
topic = relationship("Topic", back_populates="subscribed_users")
user = relationship("User", back_populates="followed_topics")
class Topic(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
subscribed_users = relationship("Association", back_populates="topic")
class User(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
followed_topics = relationship("Association", back_populates="user")
but when I run the following I get a NotNullViolation error stating that user_id violates not-null constaint.
u: User = session.query(User).filter(User.id == 'example_id1').one()
t: Topic = session.query(Topic).filter(Topic.id == 'example_id2').one()
sub = TopicSubscription(func.now())
sub.topic = t
u.followed_topics.append(sub)
session.add(u)
session.commit()
Question
Am I misinterpreting the documentation or is it wrong?
I had something quite similar just now and was going crazy trying to work it out. Abstracting to the parent/child example from the docs, what worked in my case was:
p = Parent()
a = Association(parent=p, extra_data="some data")
a.child = Child()
p.children.append(a)
I am trying to join three tables in a single query in SQLAlchemy specifically but unable to do so.
I have done this so far :-
Test.query.join(Test.sections).join(QuestionSection.section).all()
I know it is wrong but I am trying to do it in a single query.
Here are my tables :-
Question section Model :-
class QuestionSection(Dictifiable, db.Model):
__tablename__ = 'question_section'
section_id = Column(Integer, ForeignKey('section.id'), primary_key=True)
question_id = Column(Integer, ForeignKey('question.id'), primary_key=True)
question = db.relationship('Question', backref='question_section')
section = db.relationship('Section', backref='question_section')
Section Table :-
class Section(Dictifiable, db.Model):
__tablename__ = 'section'
id = Column(Integer, Sequence('section_id_seq'), primary_key=True)
test_id = Column(Integer, ForeignKey('test.id'))
name = Column(String(50))
test = relationship("Test", back_populates="sections")
questions = db.relationship('Question',
secondary='question_section')
And Test table :-
id = Column(Integer, Sequence('test_id_seq'), primary_key=True)
name = Column(String(50))
sections = relationship("Section", back_populates="test")
tests = relationship("Corporate", secondary='corporate_test',
back_populates='tests')
I understood what I was doing wrong, I should join like this :-
Test.query.join(Test.sections).join(QuestionSection, QuestionSection.section_id == Section.id).all()
Let's say you have the following simplified example schema, which uses SQLAlchemy joined table polymorphic inheritance. Engineer and Analyst models have a Role relationship. The Intern model does not.
class Role(db.Model):
__tablename__ = 'role'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(16), index=True)
class EmployeeBase(db.Model):
__tablename__ = 'employee_base'
id = db.Column(db.Integer, primary_key=True)
some_attr = db.Column(db.String(16))
another_attr = db.Column(db.String(16))
type = db.Column(db.String(50), index=True)
__mapper_args__ = {
'polymorphic_identity': 'employee',
'polymorphic_on': type
}
class Engineer(EmployeeBase):
__tablename__ = 'engineer'
id = db.Column(db.Integer, db.ForeignKey('employee_base.id'), primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id'), index=True)
role = db.relationship('Role', backref='engineers')
__mapper_args__ = {
'polymorphic_identity': 'engineer',
}
class Analyst(EmployeeBase):
__tablename__ = 'analyst'
id = db.Column(db.Integer, db.ForeignKey('employee_base.id'), primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id'), index=True)
role = db.relationship('Role', backref='analysts')
__mapper_args__ = {
'polymorphic_identity': 'analyst',
}
class Intern(EmployeeBase):
__tablename__ = 'intern'
id = db.Column(db.Integer, db.ForeignKey('employee_base.id'), primary_key=True)
term_ends = db.Column(db.DateTime, index=True, nullable=False)
__mapper_args__ = {
'polymorphic_identity': 'intern',
}
If I want to find Employees with a Role name having "petroleum" somewhere in the name, how would I do that?
I've tried many, many approaches. The closest I've come is this, which only returns Analyst matches:
employee_role_join = with_polymorphic(EmployeeBase,
[Engineer, Analyst])
results = db.session.query(employee_role_join).join(Role).filter(Role.name.ilike('%petroleum%'))
If I try to do something like this, I get an AttributeError, because I'm searching on an attribute of the joined Role table:
employee_role_join = with_polymorphic(EmployeeBase,
[Engineer, Analyst])
results = db.session.query(employee_role_join).filter(or_(
Engineer.role.name.ilike('%petroleum%'),
Analyst.role.name.ilike('%petroleum%')))
You can try specifying the join ON clause explicitly since the issue with your first query seems to be that Role is joining only on the analyst.role_id column:
employee_role_join = with_polymorphic(EmployeeBase, [Engineer, Analyst])
results = session.query(employee_role_join).join(Role).filter(Role.name.ilike('%petroleum%'))
print(str(results))
SELECT employee_base.id AS employee_base_id,
employee_base.some_attr AS employee_base_some_attr,
employee_base.another_attr AS employee_base_another_attr,
employee_base.type AS employee_base_type,
engineer.id AS engineer_id,
engineer.role_id AS engineer_role_id,
analyst.id AS analyst_id,
analyst.role_id AS analyst_role_id
FROM employee_base
LEFT OUTER JOIN engineer ON employee_base.id = engineer.id
LEFT OUTER JOIN analyst ON employee_base.id = analyst.id
JOIN role ON role.id = analyst.role_id
WHERE lower(role.name) LIKE lower(?)
employee_role_join is an AliasedClass that exposes both Analyst and Engineer, which we can then use to create a join-ON clause like so:
results = session.query(employee_role_join)\
.join(Role, or_( \
employee_role_join.Engineer.role_id==Role.id, \
employee_role_join.Analyst.role_id==Role.id \
))\
.filter(Role.name.ilike('%petroleum%'))
which changes the resulting SQL to JOIN role ON engineer.role_id = role.id OR analyst.role_id = role.id
Define the role_id on EmployeeBase. Even though Intern doesn't have the relationship back to the role table, the field can be null for that case.
I changed EmployeeBase to this:
class EmployeeBase(db.Model):
__tablename__ = 'employee_base'
id = db.Column(db.Integer, primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id'), index=True)
given_name = db.Column(db.String(16))
surname = db.Column(db.String(16))
type = db.Column(db.String(50), index=True)
__mapper_args__ = {
'polymorphic_identity': 'employee',
'polymorphic_on': type
}
And removed the role_id column definition from all other employee models.
db.create_all()
petrolium_engineer = Role(name='Petrolium Engineer')
geotech_engineer = Role(name='Geotech Engineer')
analyst_petrolium = Role(name='Analyst of Petrolium')
db.session.add(petrolium_engineer)
db.session.add(geotech_engineer)
db.session.add(analyst_petrolium)
db.session.add(
Intern(given_name='Joe', surname='Blogs', term_ends=datetime.now())
)
db.session.add(
Engineer(given_name='Mark', surname='Fume', role=petrolium_engineer)
)
db.session.add(
Engineer(given_name='Steve', surname='Rocks', role=geotech_engineer)
)
db.session.add(
Analyst(given_name='Cindy', surname='Booker', role=analyst_petrolium)
)
db.session.commit()
petrolium_roles = db.session.query(EmployeeBase).join(Role).\
filter(Role.name.contains('Petrolium')).all()
for emp in petrolium_roles:
print(f'{emp.given_name} {emp.surname} is {emp.role.name}')
# Mark Fume is Petrolium Engineer
# Cindy Booker is Analyst of Petrolium
I have the following setup
class Content(Base):
"""Content object"""
__tablename__ = "content"
id = Column(Integer, primary_key=True)
name = Column(Unicode(255),unique=True, nullable=False)
title = Column(Unicode(255))
body = Column(UnicodeText)
created = Column(DateTime, default=func.now())
modified = Column(DateTime, onupdate=func.now())
type = Column(String(20))
__mapper_args__ = {
'polymorphic_on':type,
'polymorphic_identity':'content',
'with_polymorphic':'*'
}
class Locality(Content):
__tablename__ = "local"
id = Column(Integer, ForeignKey('content.id'),primary_key=True)
city_name = Column(Unicode(80))
__mapper_args__ = {'polymorphic_identity':'city'}
Now I dropped the Locality table using alembic.
Each time I query Content, I get
AssertionError: No such polymorphic_identity 'city' is defined
How do I drop this polymorphic_identity
I got over this by applying MySQL 'delete from' command on the content through MySQL Console, finding those contents whose type is 'city'
delete from content where content.type='city';