Querying related tables in sqlalchemy - python

So I have two tables Employee and Details like this.
class Employee(Base):
__tablename__ = 'employees'
id = Column(Integer, Sequence('employee_id_seq'), primary_key=True)
name = Column(String(50), nullable=False)
............
class Detail(Base):
__tablename__ = 'details'
id = Column(Integer, Sequence('detail_id_seq'), primary_key=True)
start_date = Column(String(50), nullable=False)
email = Column(String(50))
employee_id = Column(Integer, ForeignKey('employee.id'))
employee = relationship("Employee", backref=backref('details', order_by=id))
............
Now what I want to do is get all the employees and their corresponding details, here is what I tried.
for e, d in session.query(Employee, Detail).filter(Employee.id = Detail.employee_id).all():
print e.name, d.email
The problem with this is that it prints everything twice. I tried using .join() and also prints the results twice.
What I want to achieve is like
print Employee.name
print Employee.details.email

If you really care only about few columns, you can specify them in the query directly:
q = session.query(Employee.name, Detail.email).filter(Employee.id == Detail.employee_id).all()
for e, d in q:
print e, d
If you do really want to load object instances, then I would do it differently:
# query all employees
q = (session.query(Employee)
# load Details in the same query
.outerjoin(Employee.details)
# let SA know that the relationship "Employee.details" is already loaded in this query so that when we access it, SA will not do another query in the database
.options(contains_eager(Employee.details))
).all()
# navigate the results simply as defined in the relationship configuration
for e in q:
print(e)
for d in e.details:
print(" ->", d)
As to your duplicate result problem, I believe you have some "extra" in your real code which produces this error...

Related

AttributeError: 'Query' object has no attribute 'is_clause_element' when joining table with query

AttributeError: 'Query' object has no attribute 'is_clause_element' when joining table with query
I have a query that counts the amount of keywords a company has and then sorts them by the amount of keywords they have.
query_company_ids = Session.query(enjordplatformCompanyToKeywords.company_id.label("company_id"),func.count(enjordplatformCompanyToKeywords.keyword_id)).group_by(enjordplatformCompanyToKeywords.company_id).order_by(desc(func.count(enjordplatformCompanyToKeywords.keyword_id))).limit(20)
I then want to get information about these companies like image, title, info etc and send it to the frontend (this is done later by looping through companies_query).
Though I have trouble in building the connection between the query_company_ids query and enjordplatformCompanies table.
I have tried two ways of doing this:
companies_query = Session.query(enjordplatformCompanies, query_company_ids).filter(enjordplatformCompanies.id == query_company_ids.company_id).all()
companies_query = Session.query(enjordplatformCompanies, query_company_ids).join( query_company_ids, query_company_ids.c.company_id == enjordplatformCompanies.id).all()
But both of them result in the error: AttributeError: 'Query' object has no attribute 'is_clause_element'
Question
How can I join the query_company_ids query and enjordplatformCompanies table?
Thanks
Here are the table definitions
class enjordplatformCompanies(Base):
__tablename__ = "enjordplatform_companies"
id = Column(Integer, primary_key=True, unique=True)
name = Column(String)
about = Column(String)
image = Column(String)
website = Column(String)
week_added = Column(Integer)
year_added = Column(Integer)
datetime_added = Column(DateTime)
created_by_userid = Column(Integer)
company_type = Column(String)
contact_email=Column(String)
adress=Column(String)
city_code=Column(String)
city=Column(String)
class enjordplatformCompanyToKeywords(Base):
__tablename__ = "enjordplatform_company_to_keywords"
id = Column(Integer, primary_key=True, unique=True)
company_id = Column(Integer,ForeignKey("enjordplatform_companies.id"))
keyword_id = Column(Integer,ForeignKey("enjordplatform_keywords.id"))
I copied your example query above and was getting a lot of weird errors until I realized you use Session instead of session. I guess make sure you are using an instance instead of the class or sessionmaker.
Below I create an explicit subquery() to get the company id paired with its keyword count and then I join the companies class against that, applying the order and limit to the final query.
with Session(engine) as session, session.begin():
subq = session.query(
enjordplatformCompanyToKeywords.company_id,
func.count(enjordplatformCompanyToKeywords.keyword_id).label('keyword_count')
).group_by(
enjordplatformCompanyToKeywords.company_id
).subquery()
q = session.query(
enjordplatformCompanies,
subq.c.keyword_count
).join(
subq,
enjordplatformCompanies.id == subq.c.company_id
).order_by(
desc(subq.c.keyword_count)
)
for company, keyword_count in q.limit(20).all():
print (company.name, keyword_count)
This isn't the exact method but explains the intention of calling .subquery() above:
subquery

Exclude something from where clause by using SQLAlchemy core

I have the following model:
class Vote(BaseModel):
__tablename__ 'vote'
id = sa.Column(sa.Integer, autoincrement=True, index=True, primary_key=True)
value = sa.Column(sa.Integer, nullable=False)
rated_user_id = sa.Column(
sa.Integer, sa.ForeignKey('user.id', ondelete='cascade'))
rating_user_id = sa.Column(
sa.Integer, sa.ForeignKey('user.id', ondelete='cascade'))
And I just want to make a query with gives me joined data., nevertheless I don't know how to make this query. This is my approach:
query = sa.select(
[votes, users.alias('u1'), users.alias('u2')],
use_labels=True
).select_from(votes.join(users.alias('u1'),votes.c.rated_user_id == users.alias('u1').c.id).join(users.alias('u2'), votes.c.rating_user_id == users.alias('u2').c.id))
Buy it doesn't work because it includes "user" as "u1" in FROM clause.
Thanks!
Each invocation of alias() produces a unique alias object, even if you give them the same label. Instead give the aliases a name and use the same object in every part of your query:
u1 = users.alias('u1')
u2 = users.alias('u2')
query = sa.select([votes, u1, u2], use_labels=True).\
select_from(votes.
join(u1, votes.c.rated_user_id == u1.c.id).
join(u2, votes.c.rating_user_id == u2.c.id))

querying from a many to many relationship sqlalchemy

I have this database scheme, a many-to-many relationship:
Base = declarative_base()
association_table = Table('association', Base.metadata,
Column('book_id', Integer, ForeignKey('book.book_id')),
Column('author_id', Integer, ForeignKey('author.author_id')),
)
class Book(Base):
__tablename__ = 'book'
book_id = Column(Integer, primary_key=True)
title = Column(String(50), nullable = False)
authors = relationship('Author', secondary = association_table, backref=backref('books', lazy= 'dynamic'))
class Author(Base):
__tablename__ = 'author'
author_id = Column(Integer, primary_key=True)
name = Column(String(50), nullable = False)
where I want to query data from. I know how to query from objects I created, like:
a=Author(name='Author One')
b=Book(title='Book Title One')
session.add(b)
session.add(a)
session.commit()
a.books.append(b)
session.commit()
# repeat above steps with new variables a2 and b2
# a.books.append(b2)
for i in a.books:
print(i.title)
However, how do I query directly from the table to see what authors are related to a specific book? I.e., the next time I wan to use the database, but don't have the objects anymore. Things I tried:
for u in session.query(Book.title).\
filter(Book.book_id==Author.author_id).\
filter(Author.name=='Author One').\
all():
print(u) # doesn't seem to work, returns only one element.
x = session.query(Author).filter(Book.title.any(name='Book Title One')).all()
# gives AttributeError: Neither 'AnnotatedColumn' object nor 'Comparator' object has an attribute 'any' error.
But they all seem to fail, or return the incorrect amount.
You can recreate your objects in a new session by querying the database. For example, you can fetch an author
author = session.query(Author).filter_by(name='Author One').one()
then just iterate over their books collection:
print('Author\'s books:')
for b in author.books:
print(b.title)
Author's books:
Book One
Book Three
Or use the author object to query the Book model:
query = session.query(Book).filter(Book.authors.contains(author))
print('Books\' authors:')
for b in query:
print(b.title, ', '.join(a.name for a in b.authors))
Book's authors:
Book One Author One
Book Three Author Two, Author One
If you don't want to fetch the author object, you can query the Book model using the author's name like this:
query = session.query(Book).filter(Book.authors.any(name='Author One'))
print('Books by Author One:')
for b in query:
print(b.title, ', '.join(a.name for a in b.authors))
Books by Author One:
Book One Author One
Book Three Author Two, Author One

How to in aggregate distinct values in joined table in SQLAlchemy?

Here is the schema:
post_tag = Table("post_tag", Base.metadata,
Column("post_id", Integer, ForeignKey("post.id")),
Column("tag_id ", Integer, ForeignKey("tag.id")))
class Post(Base):
id = Column(Integer, primary_key=True)
tags = relationship("Tag", secondary=post_tag, backref="post", cascade="all")
collection_id = Column(Integer, ForeignKey("collection.id"))
class Tag(Base):
id = Column(Integer, primary_key=True)
description = Column("description", UnicodeText, nullable=False, default="")
post_id = Column(Integer, ForeignKey("post.id"))
class Collection(Base):
id = Column(Integer, primary_key=True)
title = Column(Unicode(128), nullable=False)
posts = relationship("Post", backref="collection", cascade="all,delete-orphan")
tags = column_property(select([Tag])
.where(and_(Post.collection_id == id, Tag.post_id == Post.id))
.correlate_except(Tag))
Basically, Post to Tag is many-to-many and Collection to Post is one-to-many.
I want to Collection.tags return a distinct set of tags of posts in collection.
However, I get the following error when I access Collection.tags:
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) only a single result allowed for a SELECT that is part of an expression
EDIT
The SQL its generate
SELECT (SELECT tag.id, tag.description, tag.post_id
FROM tag, post
WHERE post.collection_id = collection.id AND tag.post_id = post.id) AS anon_1, collection.id AS collection_id, collection.title AS collection_title
FROM collection
WHERE collection.id = 1
I believe that post_id = Column(Integer, ForeignKey("post.id")) is wrong as post_id is in post_tag. However, if I change it to post_tag.post_id, it throws AttributeError: 'Table' object has no attribute 'post_id'
EDIT2
I change it to
tags = column_property(select([Tag])
.where(and_(Post.collection_id == id, post_tag.c.post_id == Post.id,
post_tag.c.tag_id == Tag.id)))
While this works
SELECT tag.id, tag.description, tag.category_id, tag.post_id
FROM tag, post, post_tag
WHERE post.collection_id = 1 AND post_tag.post_id = post.id AND post_tag.tag_id = tag.id
but the query generate by SQLAlchemy does not
SELECT (SELECT tag.id, tag.description, tag.category_id, tag.post_id
FROM tag, post, post_tag
WHERE post.collection_id = collection.id AND post_tag.post_id = post.id AND post_tag.tag_id = tag.id) AS anon_1
FROM collection
WHERE collection.id = 1
Instead of a column_property() you need a relationship() with a composite "secondary". A column property is handy for mapping some (scalar) SQL expression as a "column" that is loaded along other attributes. On the other hand you seem to want to map a collection of related Tag objects:
class Collection(Base):
id = Column(Integer, primary_key=True)
title = Column(Unicode(128), nullable=False)
posts = relationship("Post", backref="collection", cascade="all,delete-orphan")
tags = relationship(
"Tag", viewonly=True,
primaryjoin="Collection.id == Post.collection_id",
secondary="join(Post, post_tag)",
secondaryjoin="Tag.id == post_tag.c.tag_id")
If you want to eager load the relationship, a bit like the column property would have, you could default to lazy="join". It's also possible to define the eager load strategy on a per query basis using Query.options():
session.query(Collection).\
options(joinedload(Collection.tags)).\
all()
Please note that your example has a typo(?) in the definition of the secondary table post_tags. The column tag_id has trailing whitespace in the name.

SQLAlchemy: Converting Self-Ref JOIN, COUNT, GROUP BY SELECT

I have been struggling for a day to get an SQL Select statement that works into the equivalent SQLAlchemy code. It involves two tables.
A Tags table
class Tags(Base):
__tablename__ = 't_tags'
uid = Column(Integer, primary_key=True)
category = Column(Enum('service', 'event', 'attribute', name='enum_tag_category'))
name = Column(String(32))
And a table that maps them to their originating parents
class R_Incident_Tags(Base):
__tablename__ ='r_incident_tags'
incident_uid = Column(String(48), ForeignKey('t_incident.uid'), primary_key=True)
tag_uid = Column(Integer, ForeignKey('t_tags.uid'), primary_key=True)
tag = relationship("Tags", backref="r_incident_tags")
incident_uid is a unique string to identify the parent.
The SELECT I have been struggling to represent in SQLAlchemy is as follows
SELECT DISTINCT s.name, e.name, count(e.name)
FROM "t_tags" AS s,
"t_tags" AS e,
"r_incident_tags" AS sr,
"r_incident_tags" AS er
WHERE s.category='service' AND
e.category='event' AND
e.uid = er.tag_uid AND
s.uid = sr.tag_uid AND
er.incident_uid = sr.incident_uid
GROUP BY s.name, e.name
Any assistance would be appreciated as I haven't even got close to getting something working after a whole day of effort.
Kindest Regards!
This should do the job:
s = aliased(Tags)
e = aliased(Tags)
sr = aliased(R_Incident_Tags)
er = aliased(R_Incident_Tags)
qry = (session.query(s.name, e.name, func.count(e.name)).
select_from(s, e, sr, er).
filter(s.category=='service').
filter(e.category=='event').
filter(e.uid == er.tag_uid).
filter(s.uid == sr.tag_uid).
filter(er.incident_uid == sr.incident_uid).
group_by(s.name, e.name)
)
But you could also use relationship-based JOINs instead of simple WHERE clauses.

Categories

Resources