Lets say there's a table "posts" which contains blog posts, and another table "favorites" which links a username to a post. Many users can favorite a post, so the relationship is one post to many favorites.
I am trying to figure out the syntax to join posts to favorites, but I only want those favorites where the user is the current user.
I want some thing like:
current_user = 'testuser'
posts.query.outerjoin(favorites, and_(posts.post_id == favorites.post_id, favorites.username == current_user)).limit(10).all()
This get me really close, except it seems like the "favorites.username == current_user" condition is basically getting ignored. This is what I am looking for in actual SQL:
SELECT *
FROM posts p
LEFT JOIN (
SELECT * FROM favorites f WHERE f.user_id = 'testuser'
) ff ON ff.post_id = p.post_id
LIMIT 10
It's also worth mentioning that I have defined the relationship on posts like:
favorites = db.relationship("favorites")
And I have defined the foreign key on favorites like this:
post_id = db.Column(db.String(255), db.ForeignKey('posts.post_id'))
How can I accomplish this in SQLAlchemy?
Really you just need to replace the outerjoin with join, and the filter would work just fine.
Also, if your favorites table contains no additional information and only links users and posts, you should consider simply defining a `Many to Many' relationship. In the documentation examples Parent/Child would be your User/Post.
Update-1: just to answer second part of the question given your comment, the query below should give you an idea:
current_user = 2
subq = (db.session.query(favorites)
.filter(favorites.user_id == current_user).subquery('ff'))
q = (db.session.query(posts, subq.c.score)
.outerjoin(subq, subq.c.post_id == posts.post_id))
q = q.order_by(subq.c.score.desc())
for post, score in q:
print(post, score)
Related
I am having problems deleting records from PostTag table that contains Post and Tag column. This is my relation table:
tags = db.Table('PostTag',
db.Column('Tag', db.Integer, db.ForeignKey('Tag.Id')),
db.Column('Post', db.Integer, db.ForeignKey('Post.Id'))
)
and
tags = db.relationship(Tag, secondary=tags, backref=db.backref('Post', lazy='dynamic'))
When I do this:
from models.Post import Post
posts = Post.query.join(Post.tags).filter(Post.Id == id).all()
if(posts):
return posts
return False
and then
for posttag in post[0].tags:
db.session.delete(posttag)
db.session.flush()
Rows from many-to-many relation are deleted but also records from Tag table.
I just need to delete records from PostTag table for some condition (Post=1 for example)
I searched the internet but I did not find anything conclusive. I do not need cascade on many-to-many relationship.
This is sql log:
297 Query DELETE FROM `PostTag` WHERE `PostTag`.`Tag` = 14 AND `PostTag`.`Post` = 3
297 Query DELETE FROM `PostTag` WHERE `PostTag`.`Tag` = 14 AND `PostTag`.`Post` = 18
297 Query DELETE FROM `Tag` WHERE `Tag`.`Id` = 14
Last row 297 Query DELETE FROMTagWHERETag.Id= 14 should not be there.
UPDATE / maybe solution
I kind of solved this with:
sql = 'DELETE FROM PostTag WHERE Post=%s'
db.engine.execute(sql, post, ())
But that is not just ORM way. Same goes for insert. I will try to get this resolved ORM way. I will post answer if I make this problem go away.
Try this:
post = db.session.query(Post).get(1)
post.tags = []
db.session.commit()
Here we are redefining the collection post.tags to the empty array and committing the changes. To explain this I'll refer to the SQLAlchemy docs:
Collections in SQLAlchemy are transparently instrumented. Instrumentation means that normal operations on the collection are tracked and result in changes being written to the database at flush time.
So SQLAlchemy keeps track of the changes we make to the collection post.tags and updates it on commit.
If we had only one tag (say sometag) we could use the remove method like this:
post = db.session.query(Post).get(1)
post.tags.remove(sometag)
db.session.commit()
Try to remove objects from the collection instead:
post[0].tags.clear()
You could also remove individual items:
post[0].tags.remove(posttag)
I am having problems deleting records from PostTag table that contains Post and Tag column. This is my relation table:
tags = db.Table('PostTag',
db.Column('Tag', db.Integer, db.ForeignKey('Tag.Id')),
db.Column('Post', db.Integer, db.ForeignKey('Post.Id'))
)
and
tags = db.relationship(Tag, secondary=tags, backref=db.backref('Post', lazy='dynamic'))
When I do this:
from models.Post import Post
posts = Post.query.join(Post.tags).filter(Post.Id == id).all()
if(posts):
return posts
return False
and then
for posttag in post[0].tags:
db.session.delete(posttag)
db.session.flush()
Rows from many-to-many relation are deleted but also records from Tag table.
I just need to delete records from PostTag table for some condition (Post=1 for example)
I searched the internet but I did not find anything conclusive. I do not need cascade on many-to-many relationship.
This is sql log:
297 Query DELETE FROM `PostTag` WHERE `PostTag`.`Tag` = 14 AND `PostTag`.`Post` = 3
297 Query DELETE FROM `PostTag` WHERE `PostTag`.`Tag` = 14 AND `PostTag`.`Post` = 18
297 Query DELETE FROM `Tag` WHERE `Tag`.`Id` = 14
Last row 297 Query DELETE FROMTagWHERETag.Id= 14 should not be there.
UPDATE / maybe solution
I kind of solved this with:
sql = 'DELETE FROM PostTag WHERE Post=%s'
db.engine.execute(sql, post, ())
But that is not just ORM way. Same goes for insert. I will try to get this resolved ORM way. I will post answer if I make this problem go away.
Try this:
post = db.session.query(Post).get(1)
post.tags = []
db.session.commit()
Here we are redefining the collection post.tags to the empty array and committing the changes. To explain this I'll refer to the SQLAlchemy docs:
Collections in SQLAlchemy are transparently instrumented. Instrumentation means that normal operations on the collection are tracked and result in changes being written to the database at flush time.
So SQLAlchemy keeps track of the changes we make to the collection post.tags and updates it on commit.
If we had only one tag (say sometag) we could use the remove method like this:
post = db.session.query(Post).get(1)
post.tags.remove(sometag)
db.session.commit()
Try to remove objects from the collection instead:
post[0].tags.clear()
You could also remove individual items:
post[0].tags.remove(posttag)
I am using sql alchemy in my project.
I have one problem when two or more tables are joined or have foreign key relation then i am unable to query on joined tables attribute in where condition.
Eg. I have notice table and a user table user.id is foreign key of notice.sender
now I want to search notice by user.name
notice table:[id, sender(FK user.id), receiver(FK user.id), subject, message, status]
user table: [id, name, email, address, status]
Join in notice model:
sender_user = relationship('User', primaryjoin='Notice.sender==user.id', backref=backref("sender_user"))
receiver_user = relationship('User', primaryjoin='Notice.receiver==user.id', backref=backref("receiver_user"))
SQL alchemy filter query:
user_name='john'
notice=db_session.query(Notice)
notice = notice.filter(Notice.sender_user.name == user_name)
Following query doesn't works:
notice=db_session.query(Notice)
notice = notice.filter(Notice.user.name == user_name)
Please help!
You need to acquire a session object, then do:
query = session.query(Notice).filter(Notice.sender_user.name == user_name)
results = query.all()
If that does not work (you don't get the results you expect), try doing this:
session.query(Notice, User.name).filter((Notice.sender_user.id == User.id) & (User.name == user_name))
results = query.all()
I suppose you also have a field called sender which is the id of the user. Then, this should also work:
session.query(Notice, User.name).filter((Notice.sender == User.id) & (User.name == user_name))
Retry all of them, I made some small modifications. (you can see them in revision history). It would be weird if they don't work... (If they don't, try posting the question on the SQLAlchemy mailing list)
I have a data structure that declares relationships like this (pseudocode):
class User:
...
class Rating:
rater = User
post = Post
class Post:
ratings = hasmany(Rating)
page_id = ...
I'm building a website using these models, and I'd I'm lazy, so I pass my template the current logged in User, and a bunch of Posts on the current page. My page needs to know what rating the logged in user gave to each Post, so I use SQLAlchemy's one-instance-per-session feature:
posts = session.query(Post).filter(Post.page_id==current_pageid)
ratings = session.query(Post, Rating)\
.filter(Rating.rater==user.id)\
.filter(Post.page_id==current_pageid)
for post in posts:
post.user_rating = None # default value
for post, rating in ratings:
post.user_rating = rating
Then I pass my template the posts list. Is this ugly awful practice? Can I do it some better way?
What you are doing is good enough, except that your query is lacking a WHERE clause between the Post and Rating:
# ...
.filter(Post.id==Rating.post_id)\
But you can also get the result in one query:
qry = (session.query(Post, Rating).
outerjoin(Rating, and_(Post.id==Rating.post_id, Rating.user_id==user.id)).
filter(Post.page_id==current_pageid)
)
res = qry.all() # you can return *res* already to a view, but to get to your results, do below as well:
for post, rating in res:
post.user_rating = rating
posts = [post for post, rating in res]
return posts
Note that in your case posts is not really a list, but a query, and if you iterate over it second time, you might lose the user_rating attribute. You should be cautious returning session-bound objects like query to a view. It is safer to return lists like in the same code above. To fix your code, just add .all() to the query:
posts = session.query(Post).filter(Post.page_id==current_pageid).all()
Yes, it's bad practice. And it even might (in theory) beat you at some moment, e.g. when you query from the same session without clearing it for some post SQLAlchemy will return you the same cached object with already filled rating for some user unrelated to the current context. In practice it will work find in most cases.
Why not just pass a list of (post, rating) pairs to template? Most modern template engines available for Python can iterate over the list of pairs.
BTW, you can fetch both posts and ratings with single query (rating object will be None for OUTER JOIN when it's missing):
session.query(Post, Rating).select_from(Post)\
.outerjoin(Rating, (Post.id==Rating.post_id) & (Rating.rater==…))\
.filter(Post.page_id==…)
I am trying to understand the 1-to-many relationships in datastore; but I fail to understand how query and update the record of a user when the model includes ReferenceProperty. Say I have this model:
class User(db.Model):
userEmail = db.StringProperty()
userScore = db.IntegerProperty(default=0)
class Comment(db.Model):
user = db.ReferenceProperty(User, collection_name="comments")
comment = db.StringProperty()
class Venue(db.Model):
user = db.ReferenceProperty(User, collection_name="venues")
venue = db.StringProperty()
If I understand correctly, the same user, uniquely identified by userEmail can have many comments and may be associated with many venues (restaurants etc.)
Now, let's say the user az#example.com is already in the database and he submits a new entry.
Based on this answer I do something like:
q = User.all()
q.filter("userEmail =", az#example.com)
results = q.fetch(1)
newEntry = results[0]
But I am not clear what this does! What I want to do is to update comment and venue fields which are under class Comment and class Venue.
Can you help me understand how this works? Thanks.
The snippet you posted is doing this (see comments):
q = User.all() # prepare User table for querying
q.filter("userEmail =", "az#example.com") # apply filter, email lookup
- this is a simple where clause
results = q.fetch(1) # execute the query, apply limit 1
the_user = results[0] # the results is a list of objects, grab the first one
After this code the_user will be an object that corresponds to the user record with email "az#example.com". Seing you've set up your reference properties, you can access its comments and venues with the_user.comments and the_user.venues. Some venue of these can be modified, say like this:
some_venue = the_user.venues[0] # the first from the list
some_venue.venue = 'At DC. square'
db.put(some_venue) # the entry will be updated
I suggest that you make a general sweep of the gae documentation that has very good examples, you will find it very helpful:
http://code.google.com/appengine/docs/python/overview.html
** UPDATE **: For adding new venue to user, simply create new venue and assign the queried user object as the venue's user attribute:
new_venue = Venue(venue='Jeferson memorial', user=the_user) # careful with the quoting
db.put(new_venue)
To get all Comments for a given user, filter the user property using the key of the user:
comments = Comment.all().filter("user =", user.key()).fetch(50)
So you could first lookup the user by the email, and then search comments or venues using its key.