SQLALCHEMY query on a many to many relationship with map table - mysql

I have a database model (see below). I'm working on a project that should filter query results based on user assigned categories - there can be several categories associated to a user. In other words, I want to show the User with Articles that matches his/her assigned categories. Articles can also be associated with several categories. How can I achieve this using sqlalchemy query?
categories_users = db.Table(
'categories_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('category_id', db.Integer(), db.ForeignKey('category.id'))
)
categories_articles = db.Table(
'categories_articles',
db.Column('article_id', db.Integer(), db.ForeignKey('article.id')),
db.Column('category_id', db.Integer(), db.ForeignKey('category.id'))
)
class Category(db.Model):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
articles = db.relationship('Article', secondary=categories_articles,
backref=db.backref('categories', lazy='dynamic'))
def __str__(self):
return self.name
class Article(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
def __str__(self):
return self.name
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True, nullable=False)
categories = db.relationship('Category', secondary=categories_users,
backref=db.backref('users', lazy='dynamic'))
def __str__(self):
return self.email

In a normal m2m you have an association table with join path between the two entities that is clearly defined by foreign keys. As such, you can just tell sqlalchemy which table you want to use to associate two other tables, and it does the rest (As you've done here: secondary=categories_articles). Joining User's to Articles is slightly more complicated.
You want a many to many relationship between User and Article, so you can get the articles that users are interested in based on common categories. Or simply:
User <===> some join path involving categories <===> Article.
Our user table has a foreign key to the categories_users table and our categories_articles table has a foreign key to our articles table. Most importantly, both the categories_users table and the categories_articles table have the category_id field and so we can join those two tables together.
So, you can add an articles relationship to your User model with the following code:
articles = db.relationship(
'Article',
secondary=\
'join(categories_users, categories_articles, '
'categories_users.c.category_id==categories_articles.c.category_id)'
)
Accessing the User.articles attribute then emits this SQL:
SELECT article.id AS article_id, article.name AS article_name
FROM article, categories_users
INNER JOIN categories_articles
ON categories_users.category_id = categories_articles.category_id
WHERE %(param_1)s = categories_users.user_id
AND article.id = categories_articles.article_id
where it's clear to see what is happening. categories_users gets joined with categories_articles effectively making a new table where user rows are combined with article rows on their common categories. SQLAlchemy can then treat this as a "normal" many to many relationship with a single association table and leverage the existing foreign keys to piece together the rest of the relationship and construct the necessary query.

Related

SQL Alchemy Could not determine join condition between parent/child tables but foreign keys are specified

I got table that reference twice to another table, but got AmbiguousForeignKeysError. I specified foreign keys, but have no luck.
My simplified models are:
class User(Base):
id = Column(Integer, primary_key=True)
manager_of = relationship("Project", back_populates="manager")
teamlead_of = relationship("Project", back_populates="teamlead")
class Project(Base):
id = Column(Integer, primary_key=True)
manager_id = Column(Integer, ForeignKey("user.id"))
manager = relationship(
"User", back_populates="manager_of", foreign_keys=[manager_id], uselist=False
)
teamlead_id = Column(Integer, ForeignKey("user.id"))
teamlead = relationship(
"User", back_populates="teamlead_of", foreign_keys=[teamlead_id], uselist=False
)
Error that I got:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship User.manager_of - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
What am i doing wrong?

Flask SQLAlchemy many-to-many relationship issue

I'm having an issue with my many-to-many relationship in Flask SQLAlchemy. I have a general understanding of how this works, but I can't seem to be able to wrap my head around my example.
I'm making a tennis score keeping app and I currently have two models, Player and Score and and intermediary table Match (players can challenge each other and keep track of the matches they played). The issue is that my intermediary table has three columns that point to the same column in the Player class (player id) and I cant figure out how to make the relationship column. My code so far is as follows:
match = db.Table("match",
db.Model.metadata,
db.Column("id", db.Integer, primary_key=True),
db.Column("player1", db.Integer, db.ForeignKey("players.id")),
db.Column("player2", db.Integer, db.ForeignKey("players.id")),
db.Column("score", db.Integer, db.ForeignKey("scores.id")),
db.Column("winner", db.Integer, db.ForeignKey("players.id"))
)
class Player(db.Model):
__tablename__ = "players"
id = db.Column(db.Integer, primary_key=True)
fullname = db.Column(db.String)
# scores = db.relationship("Score", secondary=match)
class Score(db.Model):
__tablename__ = "scores"
id = db.Column(db.Integer, primary_key=True)
score = db.Column(db.String)
date = db.Column(db.String)
# players = db.relationship("Player", secondary=match)
I know I'm supposed to show the relationship in both the Player and Score class to the match table (the commented out part), but if I try to create it, it gives me the following error:
"AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Player.scores - there are multiple foreign key paths linking the tables via secondary table 'match'. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference from the secondary table to each of the parent and child tables."
If I create it without the columns 'scores' and 'players', it creates it just fine, but then I can't query the intermediary table or add rows to it. (Or if that's possible, I don't really know how)
Can someone please explain what I'm doing wrong? Overall I understand how a many-to-many relationship is supposed to work, but I just can't wrap my head around what I'm supposed to do here. screams in confusion
So I found the answer to my question and I'm just gonna leave it here, in case it helps someone else struggling with the same issue...
So I was trying to use a secondary table, but that's only meant to include foreign keys, however my relationship table has other data (id, score, winner...) which turns it into an Association Object based many-to-many relationship. And that one requires mapping the relationship from Parent to the Association object instead of directly onto the child.
class Association(Base):
__tablename__ = 'association'
left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
extra_data = Column(String(50))
parent = db.relationship("Child", back_populates="children")
child = db.relationship("Parent", back_populates="parents")
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 = db.relationship("Association", back_populates="child")

Best way to get random records from Django MySQL query

I have a Django MySQL table that holds a list of items that have been submitted to my server. The table is currently very small (low 100's) but I expect it to eventually get to several 100,000s.
I want my database query to return a random set of 30 results from this table.
My table model is:
class Item(models.Model):
ITEM_TYPES = (
('V', 'Vine'),
('Y', 'YouTube'),
('P', 'Photo'), # Photo is stored by us on a CDN somewhere
('F', 'Flickr'),
('I', 'Instagram'),
('D', 'DeviantArt'),
('5', '500px'),
)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=60, default='')
url = models.CharField(max_length=250, default='', unique=True)
item_type = models.CharField(max_length=1, choices=ITEM_TYPES)
keywords = models.ManyToManyField(Keyword, related_name='keywords')
credits_applied = models.IntegerField(default=5000)
credits_left = models.IntegerField(default=10)
credits_gifted = models.IntegerField(default=0)
date_added = models.DateTimeField(auto_now_add=True)
liked = models.IntegerField(default=0)
disliked = models.IntegerField(default=0)
active = models.BooleanField(default=True)
comment = models.CharField(max_length=100, blank=True)
approved = models.BooleanField(default=0)
My query currently looks like this:
Item.objects.filter(item_type='P', active=True, keywords__name__in=item_categories)[30]
I know that I can randomize the results using order_by('?') in my query but this apparently gets to be an extremely inefficient and time consuming way to do this for large tables.
I have searched extensively for a solution but have failed to find one. I would imagine this is something that quite a lot of people want to do?
The closest solution I have found in Stackoverflow is here:
In Django, how do I select 100 random records from the database?
But the accepted answer there is for the '?' option which scales very very badly for large tables. The other answer is only suitable if you are doing random queries once or just a few times. I need to do random queries 1000s of times a day.
The only solution I can think of is to do a query to return a list of IDs that satisfy the query conditions, and then randomly select 30 IDs from that list, and then do another query to return the records for those IDs. Is there a better way?

display a list of courses order by the number of comments of each course

I have two classes : Course and Comment.
I would like to display a list of courses ordered by the number of comments of each course. For example, the course that have the greatest number of comments should be the first on the list ....Someone please help me find an efficient query to do that. Thanks.
class Course(db.Model):
id = db.Column(db.Integer, primary_key = True )
course_name =db.Column(db.String(120))
course_description = db.Column(db.Text)
course_comments = db.relationship('Comment', backref ='course', lazy ='dynamic')
class Comment(db.Model):
id = db.Column(db.Integer, primary_key = True )
comment_date = db.Column(db.DateTime, default=db.func.now())
comment = db.Column(db.Text)
course_id = db.Column(db.Integer, db.ForeignKey('course.id'))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
Well that you want to do, in terms of SQL, is count the number of instances in a group and then order by that counting. This question gives some background: Counting the number of child records in a one-to-many relationship with SQL only
So in SQL I think you want to do is something like:
SELECT course_id, count(*)
FROM comment
GROUP BY course_id
ORDER BY count(*);
This gives you the course id's from the comments table sorted by the number of comments. This answer shows us how to do this in sqlalchemy: https://stackoverflow.com/a/4086229/1216837
import desc
import func.count
import order_by
session.query(Comment.course_id,func.count(Comment.course_id)).\
group_by(Comment.course_id).\
order_by(desc(func.count(Comment.course_id))).\
all()

Sqlalchemy : association table for many-to-many relationship between groups and members. How can I delete a relationship?

I have those tables setup : http://pastie.org/627764
...
# This is the association table for the many-to-many relationship between
# groups and members - this is, the memberships.
user_group_table = Table('user_group', metadata,
Column('user_name', Integer, ForeignKey('user.user_name',
onupdate="CASCADE", ondelete="CASCADE")),
Column('group_name', Integer, ForeignKey('group.group_name',
onupdate="CASCADE", ondelete="CASCADE"))
)
class Group(DeclarativeBase):
__tablename__ = 'group'
group_name = Column(Unicode(16), primary_key=True)
users = relation('User', secondary=user_group_table, backref='groups')
...
I'm trying to delete the relation between one user and one of his group but I can't really come up with a query that does it. Any way to do this using Sqlalchemy?
Thanks for your time.
You mean you want to remove a User from a Group?
# fetch the mapped classes
group = Session.query(Group).some_filters().one()
user = Session.query(User).some_filters().one()
# group.users is a list of all users in this group
# remove one and it will be removed from the DB
group.users.remove( user )
Session.commit()