SQLAlchemy count function for nested join subquery - python

I'm very new at SQLAlchemy and as you can see I have 3 models:
categories = db.Table(
'categories',
db.Column('post_id', db.Integer, db.ForeignKey('post.id')),
db.Column('category_id', db.Integer, db.ForeignKey('category.id'))
)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
nickname = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
posts = db.relationship('Post', backref='author', lazy='dynamic')
about_me = db.Column(db.String(140))
last_seen = db.Column(db.DateTime)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
categories = db.relationship("Category",
secondary="categories",
backref="posts")
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
I can count how many post each user has by some subquery like this:
stmt = db.session.query(models.Post.user_id, func.count('*').label('post_count')).group_by(models.Post.user_id).subquery()
db.session.query(models.User, stmt.c.post_count).outerjoin(stmt, models.User.id==stmt.c.user_id).order_by(models.User.id)
but I can't figure it out the right query to calculate how many categories each users has.

You could for example add a join to categories in your subquery and count distinct Post.ids and category_ids grouped by user_id:
stmt = db.session.query(
models.Post.user_id,
func.count(models.Post.id.distinct()).
label('post_count'),
func.count(models.categories.c.category_id.distinct()).
label('category_count')
).\
join(models.categories).\
group_by(models.Post.user_id).\
subquery()
db.session.query(models.User,
stmt.c.post_count,
stmt.c.category_count).\
outerjoin(stmt, models.User.id==stmt.c.user_id).\
order_by(models.User.id)
The post id's have to be distinct as well in case a post has multiple categories, which will manifest as multiple rows for the post after the join.

Related

sqlalchemy query(join) to retrieve sum of votes for a post

I have 3 models
class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True, nullable=False)
content = Column(String, nullable=False)
published = Column(Boolean)
owner_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User")
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String)
password = Column(String)
class Vote(Base):
__tablename__ = "votes"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
post_id = Column(Integer, ForeignKey("posts.id"), primary_key=True)
I would like to generate a query that retrieves one or all posts and automatically sum up all of the votes for said post. I know how to do this in sql but not sure how to accomplish this using SQLAlchemy.
The sql query would look like this:
SELECT posts.*, count(votes.post_id) as votes from posts left join votes on (posts.id = votes.post_id) where posts.id = 1 group by posts.id;
Also is there a way to make it so i don't have to create a custom query and instead have the post model automatically populate the sum of all votes with a reference or something like that?

How to query two association tables in Flask-SQLAlchemy?

I have two tables: courses and students, and two association tables: registered_students and attended_students. I am trying to query the two association tables to find the students who registered and attended a course. I found answers to how to query one association table, but I have two. I'm quite new to SQLAlchemy, so the complexity of it is beyond me.
Here are my models:
class Course(db.Model):
__tablename__ = 'courses'
__table_args__ = (db.UniqueConstraint('topic', 'date', name='topic_date_unique'),)
id = db.Column(db.Integer, primary_key=True)
topic = db.Column(db.String(64), nullable=False)
date = db.Column(db.DateTime, nullable=False)
registered_students = db.relationship('Student', secondary=registrations, lazy='dynamic',
backref=db.backref('registered_course', lazy='dynamic'))
attended_students = db.relationship('Student', secondary=attendances, lazy='dynamic',
backref=db.backref('attended_course', lazy='dynamic'))
class Student(db.Model):
__tablename__ = 'students'
__table_args__ = (db.UniqueConstraint('email', 'phone', name='email_phone_unique'), )
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), nullable=False)
email = db.Column(db.String(64), nullable=False)
phone = db.Column(db.Integer, nullable=False)
registrations = db.Table('registrations',
db.Column('course_id', db.Integer, db.ForeignKey('courses.id'), primary_key=True),
db.Column('student_id', db.Integer, db.ForeignKey('students.id'), primary_key=True)
)
attendances = db.Table('attendances',
db.Column('course_id', db.Integer, db.ForeignKey('courses.id'), primary_key=True),
db.Column('student_id', db.Integer, db.ForeignKey('students.id'), primary_key=True)
)
In the end, I decided to go with a no ORM solution. Maybe it's not the best, but it works for me. If you know how to do it in Flask-SQLAlchemy, please share.
def get_registered_and_attended_students_for(course_id):
connection = sqlite3.connect(DATABASE)
cursor = connection.cursor()
return cursor.execute(f"""SELECT registrations.student_id FROM registrations INNER JOIN attendances ON registrations.course_id = attendances.course_id
WHERE registrations.student_id = attendances.student_id AND attendances.course_id = {course_id}""").fetchall()

Use Flask-SqlAlchemy to query relationship database

I'm trying to use Flask-SQLAlchemy to query out database for the user profile page
So far I don't have a solution for this problem, only able to query all the User data by using users.query.all()
Each user has their own role_id, department_id, researchfield_id.
How can i query out all the Role, Department, ResearchField data that has relationship with User through ID?
class User(UserMixin, db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
password_hash = db.Column(db.String(128))
is_admin = db.Column(db.Boolean, default=False)
department_id = db.Column(db.Integer, db.ForeignKey('departments.id'))
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
research_id = db.Column(db.Integer, db.ForeignKey('researchfields.id'))
class Department(db.Model):
__tablename__ = "departments"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(sqlalchemy.types.NVARCHAR(100), unique=True)
user = db.relationship('User', backref='department',
lazy='dynamic')
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(sqlalchemy.types.NVARCHAR(100), unique=True)
users = db.relationship('User', backref='role',
lazy='dynamic')
class ResearchField(db.Model):
__tablename__ = "researchfields"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60), index=True)
parent_id = db.Column(db.Integer, db.ForeignKey("researchfields.id") , nullable=True)
users = db.relationship('User', backref='researchfield', lazy='dynamic')
If I understand correctly, what you're seeking for is a way to filter out users based on a specific model. Because in your example, the other way around is redundant - every user has only one department, so no need to filter out departments for that user. In order to achieve that, I would use the backref method provided by SQLAlchemy from the User model.
Here's an example consisting of two of the models:
from sqlalchemy.orm import backref
class User(UserMixin, db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
password_hash = db.Column(db.String(128))
is_admin = db.Column(db.Boolean, default=False)
department_id = db.Column(db.Integer, db.ForeignKey('departments.id'))
department = db.relationship("Department", backref=backref("users", lazy="dynamic"))
class Department(db.Model):
__tablename__ = "departments"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(sqlalchemy.types.NVARCHAR(100), unique=True)
Now you can use:
department = Department.query.filter_by(id=1).first()
print(department.users.filter_by(is_admin=True).all()) # get all admins with that department
Every user has only one department, so you could just get the user's department by:
user = User.query.filter_by(id=1).first()
print(user.department) # prints Department object

How to filter many to many that has all item in SQLAlchemy?

class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, unique=True, nullable=False)
user = db.relationship("User", backref="group", cascade="delete")
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, unique=True, nullable=False)
group_id = db.Column(db.Integer, db.ForeignKey(Group.id, ondelete="CASCADE"), nullable=False)
group_names = ["foo", "bar"]
User.query.filter(User.group.any(Group.name.in_(group_names))).all()
This query can filter User that are at least in foo or bar group. However, I want to query the users that are in BOTH groups.
Is there a way to do in SQLAlchemy WITHOUT using a for-loop to create a chain of or queries?

many to many relationship with three tables (sql-alchemy)

I am trying to get a many to many relationship working. I have three tables
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
class Groups(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(1000))
class Members(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
group_id = db.Column(db.Integer, db.ForeignKey('groups.id'))
I would like to have the option group.members, which should give me all User objects which are member of that group. I implemented it the following way
members = db.relationship('User', secondary="join(Members, User, Members.user_id == User.id)", primaryjoin="and_(Groups.id == Members.group_id)")
this seems to work, but when I delete a group it gives me (sometimes) the error
AttributeError: 'Join' object has no attribute 'delete'
so I guess this is not the right way to implement such a relation.
Any ideas how to do this correctly?
thanks
carl
Perhaps a simpler way to implement this is as follows (adapted from the documentation on Flask-SQLAlchemy
members = db.Table('members',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('group_id', db.Integer, db.ForeignKey('groups.id'))
)
class Groups(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(1000))
members = db.relationship('User', secondary=members, backref=db.backref('group', lazy='dynamic'))
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
Instead of using a Model for the joining table (members), let's just use a simple table.
With this configuation, you can easily add/remove members and groups:
u = User(username='matt')
g = Groups(name='test')
db.session.add(u)
db.session.add(g)
db.session.commit()
g.members.append(u)
db.session.commit()
db.session.delete(g)
db.session.commit()

Categories