Django querying many to many relations - django

I have 3 models: group, word, phrase
As you can see word belongs to group and there is a many to many relation between phrases and words:
class Group(models.Model):
name = models.TextField()
class Word(models.Model):
group = models.ForeignKey(Group, on_delete=models.SET_NULL, null=True,
related_name = "words")
class Phrase(models.Model):
words = models.ManyToManyField(Word, null=True,related_name = "phrases")
name = models.TextField()
And right now I need to find all phraseswith name ="xxx"
that have at least one word from group with group id = '177'
How do I do this?

Phrase.objects.filter(name="xxx", words__group__id=177)
You can filter all phrases with name "xxx" and one of words' group_id equals 177.

You can do this with a filter that both filters on the name field, and the words__group_id=177:
Phrase.objects.filter(
name='xxx',
words__group_id=177
).distinct()
We here thus use a JOIN on the words and the phrase_words table, and then filter such that the group_id of the Word is 177.
The .distinct() can be important, since if mutliple words are in the group with id 177, then that Phrase would be repeated multiple times.
This works in case id is the primary key of the Group. If you defined another column named id, and that column is not the primary key (which is quite uncommon), we can use:
Phrase.objects.filter(
name='xxx',
words__group__id=177
).distinct()
Mind that here we use two consecutive underscores (__) instead of one (_) to look "through" the foreign key. In this case, this will result in an extra JOIN at the database level, and therfore can be a bit less efficient.

Related

Flask Sqlalchemy - query many to many with several values

I have a many-to-many relationship between matches and teams, where more than one team can play in a match and each team can have more than one match. The models are:
class Match(db.Model):
__tablename__ = 'match'
id = Column(
db.Integer,
primary_key=True,
autoincrement=True)
teams = db.relationship('Team', secondary='team_match_link')
class Team(db.Model):
__tablename__ = 'team'
id = Column(
db.Integer,
primary_key=True,
autoincrement=True)
matches = db.relationship('Match', secondary='team_match_link')
class TeamMatchLink(db.Model):
__tablename__ = 'team_match_link'
match_id = Column(
db.Integer,
db.ForeignKey('match.id'),
primary_key=True
)
team_id = Column(
db.Integer,
db.ForeignKey('team.id'),
primary_key=True
)
Given two teams [T1, T2], how do I query for the matches that contain EXACTLY these two teams and no other teams?
This gives me the matches that contain at least the two teams and then I could check if there are other teams in these matches of course. But it looks ugly and I'm sure there is a better/more effcient way? Ideally the solution should work for n teams without sending me to loop hell.
res1 = match.query.filter(match.team.any(id=T1.id)).all()
res2 = match.query.filter(match.team.any(id=T2.id)).all()
res = [i for i in res1 if i in res2]
How about querying the match team links and aggregate by counting the number of entries.
Query and some explanations below:
your_teams = [T1.id, T2.id]
result = db.session.query(Match, TeamMatchLink).filter(TeamMatchLink.team_id.in_(your_teams)).group_by(TeamMatchLink.match_id).having(func.count(TeamMatchLink.team_id) == len(your_teams)).join(Match).all()
The query consists of several parts and ideas strung together:
team_link_query = db.session.query(TeamMatchLink).filter(TeamMatchLink.team_id.in_(your_teams)))
This is selecting all team links that have your desired teams in them.
applicable_matches_query = team_link_query.group_by(TeamMatchLink.match_id).having(func.count(TeamMatchLink.team_id) == len(your_teams))
This aggregates the team match links to find those that have all the teams in them that you require. (A match will appear nr of times that a team link will match the your_teams list)
Finally, a join will get you the matches. You could of course reduce the query to give you only match ids from the team match link table directly.

Django filter table by foreign keys in related manager column

I have three models in my Django application:
class MainData(models.Model):
# bunch of fields
class Label(models.Model):
label = models.CharField(max_length=512, null=True, unique=True)
class MapData(models.Model):
labelMatch = models.ForeignKey(Label, on_delete=models.CASCADE)
mainMatch = models.ForeignKey(MainData, on_delete=models.CASCADE)
Through my application, I have the user enter a label in a search box. What I would like to do is return the MainData rows whos' MapData.label_match field is the entry in Label.
For example, say the user enters the string 'main32' into the search box. My current thought is to first find Label rows that match 'main32', then use the RelatedManager labelmatch_set to get all of the MapData rows that MapData.mainMatch points to. So if there are 10 MapData rows whose labelMatch points to a Label entry with label='main32', I then want to retrieve all 10 MainData rows that the foreign key mainMatch points to.
Hopefully I explained that ok. I have gotten to:
matching_label_rows = Label.objects.filter(input)
matching_main_data_rows = matching_label_rows.mainMatch_set.????
How can I retrieve the pointed to MainData rows from matching_label_rows.mainMatch_set? And can this operation be done as a one-liner?
Instead of finding the matching Labels first, you can filter MainData on the mapdata__labelMatch relationship:
matching_main_data_rows = MainData.objects.filter(mapdata__labelmatch__label__icontains=input)
Lookups that span relationships

Django Model Fields Indexing

I only know that indexing is helpful and it queries faster.
What is the difference between following two?
1.
class Meta:
indexes = [
models.Index(fields=['last_name', 'first_name',]),
models.Index(fields=['-date_of_birth',]),
]
2.
class Meta:
indexes = [
models.Index(fields=['first_name',]),
models.Index(fields=['last_name',]),
models.Index(fields=['-date_of_birth',]),
]
Example 1:
The first example creates a single index on the last_name and first_name field.
indexes = [
models.Index(fields=['last_name', 'first_name',]),
]
It will be useful if you search on the last name and first name together, or the last name by itself (because last_name is the first field in the index).
MyModel.objects.filter(last_name=last_name, first_name=first_name)
MyModel.objects.filter(last_name=last_name)
However, it will not be useful for searching for the first_name by itself (because first_name is not the first field in the index).
MyModel.objects.filter(first_name=first_name) # not useful
Example 2:
The second example creates an index for the first_name field and a separate index for the last_name field.
indexes = [
models.Index(fields=['first_name',]),
models.Index(fields=['last_name',]),
]
It will be useful if you do lookups based on first name or last name in your code
MyModel.objects.filter(first_name=search)
MyModel.objects.filter(last_name=search)
Django Model Index was introduced in Django 1.11
what is Model.indexes:
By default, indexes are created with an ascending order for each column. To define an index with a descending order for a column, add a hyphen before the field’s name.
For your query,
models.Index(fields=['last_name', 'first_name','-date_of_birth',]), would create SQL with (last_name, first_name, date_of_birth DESC).
Lets move to your question,
you asked difference between 2 queries,
both will take models.Index(fields=['-date_of_birth',]),
because least one will override the assigned variables. from your question least is dateofbirth so it will override above two lines.
so as per documentation preferable method is,
because indexing field should be in single list.. so django will prepare SQL indexing from list of fields...
models.Index(fields=['last_name', 'first_name', '-date_of_birth']),

Django, m2m count annotation related model with condition

so my models:
class User(models.Model):
username = models.CharField(max_lenght=255)
class Group(models.Model):
users = models.ManyToManyField('User')
class ActiveGroup(models.Model):
group = models.ForeignKey('Group', null=True, blank=True, default=None)
user = models.OneToOneField('User')
So the deal is every user can be a member of any group ( which is represented by m2m field on Group model). But can be active member in only one of them at the same time ( thats why oneToOneField relation on activegroup model).
Problem: need to make a queryset for Group models with count of users which are active.
so its suppose to be like
SELECT group.id, COUNT(
SELECT COUNT(*) FROM activegroup WHERE activegroup.group_id = group.group.id
) as users_count FROM group.
I've tried django.db.models Case and When things but nothing helps.
I would suggest you changing your database design. You have rows with partially-empty entries AND duplication. And that's not good. If this project is expected to grow, you might face problems in the future.
For example: Group -> User and ActiveGroup.group -> user. It is the same information stored two times. I'd change it before going to production. My suggestion would be using through:
https://docs.djangoproject.com/en/1.10/topics/db/models/#extra-fields-on-many-to-many-relationships
You could create a boolean field called "active" and simply switch that every time the active group is changed.
But of course, these are only suggestions. :)
Your solution is:
Group.objects.annotate(total=Count('activegroup__user'))
ag = Group.objects.annotate(total=Count('activegroup__user'))
for g in ag:
print(g, g.total)
>> group_1 2
group_2 1
In your model, you might face another group called null, which will be users without group.
https://docs.djangoproject.com/en/1.10/ref/models/querysets/#annotate

How to ask the Django manytomany manager to match several relations at once?

I've got this model:
class Movie(models.Model):
# I use taggit for tag management
tags = taggit.managers.TaggableManager()
class Person(models.Model):
# manytomany with a intermediary model
movies = models.ManyToManyField(Movie, through='Activity')
class Activity(models.Model):
movie = models.ForeignKey(Movie)
person = models.ForeignKey(Person)
name = models.CharField(max_length=30, default='actor')
And I'd like to match a movie that has the same actors as another one. Not one actor in common, but all the actors in common.
So I don't want this:
# actors is a shortcut property
one_actor_in_common = Movie.object.filter(activities__name='actor',
team_members__in=self.movie.actors)
I want something that would make "Matrix I" match "Matrix II" because they share 'Keanu Reeves' and 'Laurence Fishburne', but not match "Speed" because they share 'Keanu Reeves' but not 'Laurence Fishburne'.
The many to manytomany manager cannot match several relations at once. Down at the database level it all comes down to selecting and grouping.
So naturally speaking the only question the database is capable to answer is this: List acting activites that involve these persons, group them by movie and show only those movies having the same number of those acting activities as persons.
Translated to ORM speak it looks like this:
actors = Person.objects.filter(name__in=('Keanu Reaves', 'Laurence Fishburne'))
qs = Movie.objects.filter(activity__name='actor',
activity__person__in=actors)
qs = qs.annotate(common_actors=Count('activity'))
all_actors_in_common = qs.filter(common_actors=actors.count())
The query this produces is actually not bad:
SELECT "cmdb_movie"."id", "cmdb_movie"."title", COUNT("cmdb_activity"."id") AS "common_actors"
FROM "cmdb_movie"
LEFT OUTER JOIN "cmdb_activity" ON ("cmdb_movie"."id" = "cmdb_activity"."movie_id")
WHERE ("cmdb_activity"."person_id" IN (SELECT U0."id" FROM "cmdb_person" U0 WHERE U0."name" IN ('Keanu Reaves', 'Laurence Fishburne'))
AND "cmdb_activity"."name" = 'actor' )
GROUP BY "cmdb_movie"."id", "cmdb_movie"."title", "cmdb_movie"."id", "cmdb_movie"."title"
HAVING COUNT("cmdb_activity"."id") = 2
I also have a small application that I used to test this, but I don't know wether someone needs it nor where to host it.