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
Related
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
I have two tables, Products and Orders, inside my Flask-SqlAlchemy setup, and they are linked so an order can have several products:
class Products(db.Model):
id = db.Column(db.Integer, primary_key=True)
....
class Orders(db.Model):
guid = db.Column(db.String(36), default=generate_uuid, primary_key=True)
products = db.relationship(
"Products", secondary=order_products_table, backref="orders")
....
linked via:
order_products_table = db.Table("order_products_table",
db.Column('orders_guid', db.String(36), db.ForeignKey('orders.guid')),
db.Column('products_id', db.Integer, db.ForeignKey('products.id'))
# db.Column('license', dbString(36))
)
For my purposes, each product in an order will receive a unique license string, which logically should be added to the order_products_table rows of each product in an order.
How do I declare this third license column on the join table order_products_table so it gets populated it as I insert an Order?
I've since found the documentation for the Association Object from the SQLAlchemy docs, which allows for exactly this expansion to the join table.
Updated setup:
# Instead of a table, provide a model for the JOIN table with additional fields
# and explicit keys and back_populates:
class OrderProducts(db.Model):
__tablename__ = 'order_products_table'
orders_guid = db.Column(db.String(36), db.ForeignKey(
'orders.guid'), primary_key=True)
products_id = db.Column(db.Integer, db.ForeignKey(
'products.id'), primary_key=True)
order = db.relationship("Orders", back_populates="products")
products = db.relationship("Products", back_populates="order")
licenses = db.Column(db.String(36), nullable=False)
class Products(db.Model):
id = db.Column(db.Integer, primary_key=True)
order = db.relationship(OrderProducts, back_populates="order")
....
class Orders(db.Model):
guid = db.Column(db.String(36), default=generate_uuid, primary_key=True)
products = db.relationship(OrderProducts, back_populates="products")
....
What is really tricky (but also shown on the documentation page), is how you insert the data. In my case it goes something like this:
o = Orders(...) # insert other data
for id in products:
# Create OrderProducts join rows with the extra data, e.g. licenses
join = OrderProducts(licenses="Foo")
# To the JOIN add the products
join.products = Products.query.get(id)
# Add the populated JOIN as the Order products
o.products.append(join)
# Finally commit to database
db.session.add(o)
db.session.commit()
I was at first trying to populate the Order.products (or o.products in the example code) directly, which will give you an error about using a Products class when it expects a OrderProducts class.
I also struggled with the whole field naming and referencing of the back_populates. Again, the example above and on the docs show this. Note the pluralization is entirely to do with how you want your fields named.
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.
I have defined a python class "Students", like this:
class Students(DeclarativeBase):
__tablename__ = 'students'
id_ = Column('id', Integer, primary_key=True)
name = Column('nombre', Unicode(50))
date_of_birth = Column(Date)
If I do select * from students, I can see all of these columns plus a few more, namely: _created and _updated.
I need to use the values stored in the columns _created and _updated. So I try to access them like this:
#get student with id = 1
>>> s = dbs.query(Students).get(1)
# print its name
>>> print(s.name)
Richard
# try to print when it was created
>>> print (s._created)
AttributeError: 'Students' object has no attribute '_created'
Of course I get that message because the attribute _created is not defined in the model.
How can I access the value stored in the table Students even though it is not an attribute of the class Student?
SQLAlchemy needs the definition of each column it will access. (There are ways to auto-discover by reflecting the database, but explicit is better than implicit.) Add the column definitions to the model. I'm assuming they're DateTimes. You can use default= and onupdate= to provide new values when a row is inserted or updated.
class Student(Base):
__tablename__ = 'student'
id = Column('id_', Integer, primary_key=True)
# other columns...
created = Column('_created', DateTime, nullable=False, default=datetime.utcnow)
updated = Column('_updated', DateTime, onupdate=datetime.utcnow)
Hi I have a simple question - i have 2 tables (addresses and users - user has one address, lot of users can live at the same address)... I created a sqlalchemy mapping like this:
when I get my session and try to query something like
class Person(object):
'''
classdocs
'''
idPerson = Column("idPerson", Integer, primary_key = True)
name = Column("name", String)
surname = Column("surname", String)
idAddress = Column("idAddress", Integer, ForeignKey("pAddress.idAddress"))
idState = Column("idState", Integer, ForeignKey("pState.idState"))
Address = relationship(Address, primaryjoin=idAddress==Address.idAddress)
class Address(object):
'''
Class to represent table address object
'''
idAddress = Column("idAddress", Integer, primary_key=True)
street = Column("street", String)
number = Column("number", Integer)
postcode = Column("postcode", Integer)
country = Column("country", String)
residents = relationship("Person",order_by="desc(Person.surname, Person.name)", primaryjoin="idAddress=Person.idPerson")
self.tablePerson = sqlalchemy.Table("pPerson", self.metadata, autoload=True)
sqlalchemy.orm.mapper(Person, self.tablePerson)
self.tableAddress = sqlalchemy.Table("pAddress", self.metadata, autoload=True)
sqlalchemy.orm.mapper(Address, self.tableAddress)
myaddress = session.query(Address).get(1);
print myaddress.residents[1].name
=> I get TypeError: 'RelationshipProperty' object does not support indexing
I understand residents is there to define the relationship but how the heck can I get the list of residents that the given address is assigned to?!
Thanks
You define a relationship in a wrong place. I think you are mixing Declarative Extension with non-declarative use:
when using declarative, you define your relations in your model.
otherwise, you define them when mapping model to a table
If option-2 is what you are doing, then you need to remove both relationship definitions from the models, and add it to a mapper (only one is enought):
mapper(Address, tableAddress,
properties={'residents': relationship(Person, order_by=(desc(Person.name), desc(Person.surname)), backref="Address"),}
)
Few more things about the code above:
Relation is defined only on one side. The backref takes care about the other side.
You do not need to specify the primaryjoin (as long as you have a ForeignKey specified, and SA is able to infer the columns)
Your order_by configuration is not correct, see code above for the version which works.
You might try defining Person after Address, with a backref to Address - this will create the array element:
class Address(object):
__tablename__ = 'address_table'
idAddress = Column("idAddress", Integer, primary_key=True)
class Person(object):
idPerson = Column("idPerson", Integer, primary_key = True)
...
address_id = Column(Integer, ForeignKey('address_table.idAddress'))
address = relationship(Address, backref='residents')
Then you can query:
myaddress = session.query(Address).get(1);
for residents in myaddress.residents:
print name
Further, if you have a lot of residents at an address you can further filter using join:
resultset = session.query(Address).join(Address.residents).filter(Person.name=='Joe')
# or
resultset = session.query(Person).filter(Person.name=='Joe').join(Person.address).filter(Address.state='NY')
and resultset.first() or resultset[0] or resultset.get(...) etc...