How to Access Data Within Django ManytoMany with SQL Query - sql

I have two models in a Django site as follows, one of which is a many to many relationship:
class Seller(models.Model):
account = models.ForeignKey(Account, related_name='sellers',null=True, on_delete=models.SET_NULL)
bio = models.TextField(null=True, blank=True)
city = models.CharField(max_length=50, null=True, blank=True)
and
class Account(models.Model):
username = models.CharField(max_length=50, blank=True, null=True, unique=True)
password = models.CharField(max_length=64)
name = models.CharField(max_length=50, blank=True, null=True)
I am trying to run an SQL query on my Postgresql database, but I cannot find a clear way to to write an SQL query to access information within the many to many relationship.
I have the seller ID, and I want to get the username within Account. If I try the following, which clearly is not correct, it won't work but I'm stumped as what to do next:
SELECT seller_seller.bio, seller_seller.account.id
FROM seller_seller, admin_account
WHERE ...no clue!
Can someone point me in the right direction? Thank you!

You can simply get Seller object matching the seller_id you have by following query:
>>> seller = Seller.objects.get(pk=seller_id) # Note it would raise SellerDoesNotExists if matching pk not found
Then using above seller object you can get username by:
>>> seller.account.username
But problem with above query is that it does an extra query for fetching username.
So to avoid extra query you can use select_related which would perform InnerJoin with related Account
>>> from django.db.models import F
>>> seller_id = 1 # seller id that you have
>>> qs = (Seller.objects.filter(pk=seller_id).select_related('account')
.annotate(username=F('account__username')))
>>> print(qs.first().username) # Note : It would raise AttributeError if no object found matching the condition.

Related

Django annotate multiple aggregators over grouped values

Due to the structure of my project, I need to have multiple aggregations over three interlocked tables. With structure looking somewhat like this:
class ItemMeta(models.Model):
item = models.ForeignKey(
Item, on_delete=models.SET_NULL, null=True
)
class = models.CharField(max_length=2048, null=True, blank=True)
department = models.CharField(max_length=2048, null=True, blank=True)
class Item(models.Model):
amount = models.DecimalField(max_digits=18, decimal_places=2)
status = models.CharField(max_length=2048, null=True, blank=True, choices=ItemStatus.choices)
class CartItem(models.Model):
author = models.ForeignKey(to=UserModel, on_delete=model.CASCADE)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
class ItemStatus(models.TextChoices):
UNDER_CONSIDERATION = 'UNDER_CONSIDERATION', 'Under consideration'
IMPOSED = 'IMPOSED', 'Imposed'
PAID = 'PAID', 'Paid'
And I need to have item grouping by class, department and status both in cart and outside of it. I also need to have aggregations of combined amounts of items in different statuses, as well as counts of different items in cart and existing. So the structure of the response has to always contain 5 values: sum of paid, imposed and considered items, and count of items existing and in cart of the calling user. I inherited from last poor sod this piece of code to do these:
def _sum(self, status):
return Coalesce(Sum('amount', filter=Q(status=status)), 0.0, output_field=FloatField())
def annotate_kwargs(self):
return {
'under_consideration_amount': self._sum(ItemStatus.UNDER_CONSIDERATION),
'imposed_amount': self._sum(ItemStatus.IMPOSED),
'paid_amount': self._sum(ItemStatus.PAID),
'count': Count('pk', distinct=True),
'in_cart': Count('pk', distinct=True, filter=Q(cartitem__author=self.user)),
}
def get(self):
return self.queryset \
.values(*self.group_by) \
.annotate(**self.annotate_kwargs())
Which basically takes the Item queryset and groupes it according to request and then annotates it. Problem is, it returns lies, as is highlighted in the docs. Methinks having 3 different tables has something to do with it, but at this point i have no way to change the model structure, so it has to stay as it is or have as little change as possible. My question is how to have these aggregations? I tried using subquery, but i don't know how to make it work with .values clause

sqlalchemy join with sum and count of grouped rows

Hi i am working on a little prediction game in flask with flask-sqlalchemy I have a User Model:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
nick = db.Column(db.String(255), unique=True)
bets = relationship('Bet', backref=backref("user"))
and my Bet model
class Bet(db.Model):
id = db.Column(db.Integer, primary_key=True)
uid = db.Column(db.Integer, db.ForeignKey('user.id'))
matchid = db.Column(db.Integer, db.ForeignKey('match.id'))
points = db.Column(db.Integer)
Both are not the full classes but it should do it for the question. A user can gather points for predicting the match outcome and gets different amount of points for predicting the exact outcome, the winner or the difference.
I now want to have a list of the top users, where i have to sum up the points which i'm doing via
toplist = db.session.query(User.nick, func.sum(Bet.points)).\
join(User.bets).group_by(Bet.uid).order_by(func.sum(Bet.points).desc()).all()
This works quite good, now there maybe the case that two players have the same sum of points. In this case the amount of correct predictions (rewarded with 3 points) would define the winner. I can get this list by
tophits = db.session.query(User.nick, func.count(Bet.points)).\
join(User.bets).filter_by(points=3).all()
They both work well, but I think there has to be a way to get both querys together and get a table with username, points and "hitcount". I've done that before in SQL but i am not that familiar with SQLAlchemy and thought knots in my brain. How can I get both queries in one?
In the query for tophits just replace the COUNT/filter_by construct with equivalent SUM(CASE(..)) without filter so that the WHERE clause for both is the same. The code below should do it:
total_points = func.sum(Bet.points).label("total_points")
total_hits = func.sum(case(value=Bet.points, whens={3: 1}, else_=0)).label("total_hits")
q = (session.query(
User.nick,
total_points,
total_hits,
)
.join(User.bets)
.group_by(User.nick)
.order_by(total_points.desc())
.order_by(total_hits.desc())
)
Note that i changed a group_by clause to use the column which is in SELECT, as some database engines might complain otherwise. But you do not need to do it.

django inner join same column different results

I think I may have a solution here -
Django query where in
It's basically approaching my query differently than I have posted below (I hope).
Coming from an SQL background I have a query that looks like so (updated)-
SELECT a.jobmst_name AS Parent, b.jobmst_name as Job from jobmst a
inner join jobmst b on b.jobmst_prntid = a.jobmst_id
WHERE (a.jobmst_name = 'Dept' and a.jobmst_prntid IS NULL) OR b.jobmst_prntname LIKE '\Dept\%' AND b.jobmst_dirty <> 'X'
UNION
SELECT jobmst_prntname as Parent, jobmst_name as Job FROM jobmst
WHERE (jobmst_name = 'Dept' AND jobmst_prntid IS NULL)
Which will return a list like the following -
Parent Job
NULL Dept
01. Dept_sub01 01.01 Trade Recon
02. Dept_sub02 02.04 Dept_sub02.04
02.04 Dept_sub02.04 02.04.02 Dept_sub02.04
02.04 Dept_sub02.04 02.04.04 Dept_sub02.04
02.04 Dept_sub02.04 02.04.05 Dept_sub02.04
02.04.01 Dept_sub02.04.01 02.04.01.01 Dept_sub02.04.01
02.04.01 Dept_sub02.04.01 02.04.01.02 Dept_sub02.04.01
Dept 01. Dept_sub01
Dept 02. Dept_sub02
Dept 03. Dept_sub03
How do I do the following in Django? The UNION is the easy part so ignore that part of the query. The important part is as follows -
The jobmst_id is the primary key of the table. The jobmst_prntid is the the same as the jobmst_id but not in the same row (as seen above).
I'm trying to generate json so that I can do a heirarchy tree but to do that I want my json to be setup like -
{parent_name: Dept, name: Dept01234}, {parent_name: Dept, name: Dept53452}
I've done inner joins on a table but the issue is that I want to tell it that the jobmst_id and jobmst_prntid are both going to give me a value from jobmst_name and it's going to be a different value on each key.
Hope this makes sense.
UPDATE - Found this which explains exactly what I'm trying to do -
django self join query using aliases
But it doesn't necessarily explain how to get it to essentially show 'jobmst_name', 'jobmst_name' while giving me the Parent name on one and the child name on the other even though I'm querying the same column.
EDIT - Here is the model I'm dealing with.
class Jobmst(models.Model):
jobmst_id = models.IntegerField(primary_key=True)
jobmst_type = models.SmallIntegerField()
jobmst_prntid = models.IntegerField('self', null=True, blank=True)
jobmst_active = models.CharField(max_length=1, blank=True)
evntmst_id = models.IntegerField(blank=True, null=True)
jobmst_evntoffset = models.SmallIntegerField(blank=True, null=True)
jobmst_name = models.TextField(blank=True)
jobmst_mode = models.SmallIntegerField(blank=True, null=True)
jobmst_owner = models.ForeignKey('Owner', db_column='jobmst_owner', related_name = 'Jobmst_Jobmst_owner', blank=True, null=True)
jobmst_desc = models.TextField(blank=True) # This field type is a guess.
jobmst_crttm = models.DateTimeField()
jobdtl_id = models.IntegerField(blank=True, null=True)
jobmst_lstchgtm = models.DateTimeField(blank=True, null=True)
jobmst_runbook = models.TextField(blank=True) # This field type is a guess.
jobcls_id = models.IntegerField(blank=True, null=True)
jobmst_prntname = models.TextField(blank=True)
jobmst_alias = models.CharField(max_length=10, blank=True)
jobmst_dirty = models.CharField(max_length=1, blank=True)
def __unicode__(self):
return self.jobmst_name
class Meta:
managed = False
db_table = 'jobmst'
For anyone who may get the same problem as I had I found the solution (ie. how to get MPTT working with a legacy database!)
I ran through the beginning of the MPTT tutorial here -
http://django-mptt.github.io/django-mptt/tutorial.html
I then opened my SSMS and looked at how django mptt was creating the table for the test. I then recreated the columns that were missing in my legacy table -
lft, rght, tree_id, level
as well as the FK/PK connections.
From there I simply had to load up the model in shell -
from polls.models import Jobmst
Jobmst.objects.rebuild()
Bam! It then created all the values I needed by running through the model.
Now I can do the rest of the tutorial and I can get my tree view output now just need to get it in json so I can import into d3.
If anyone finds this and has questions just msg me.

Django - Making a SQL query on a many to many relationship with PostgreSQL Inner Join

I am looking for a perticular raw SQL query using Inner Join.
I have those models:
class EzMap(models.Model):
layers = models.ManyToManyField(Shapefile, verbose_name='Layers to display', null=True, blank=True)
class Shapefile(models.Model):
filename = models.CharField(max_length=255)
class Feature(models.Model):
shapefile = models.ForeignKey(Shapefile)
I would like to make a SQL Query valid with PostgreSQL that would be like this one:
select id from "table_feature" where' shapefile_ezmap_id = 1 ;
but I dont know how to use the INNER JOIN to filter features where the shapefile they belongs to are related to a particular ezmap object
Something like this:
try:
id = Feature.objects.get(shapefile__ezmap__id=1).id
except Feature.DoesNotExist:
id = 0 # or some other action when no result is found
You will need to use filter (instead of get) if you want to deal with multiple Feature results.

Django sql order by

I'm really struggling on this one.
I need to be able to sort my user by the number of positive vote received on their comment.
I have a table userprofile, a table comment and a table likeComment.
The table comment has a foreign key to its user creator and the table likeComment has a foreign key to the comment liked.
To get the number of positive vote a user received I do :
LikeComment.objects.filter(Q(type = 1), Q(comment__user=user)).count()
Now I want to be able to get all the users sorted by the ones that have the most positive votes. How do I do that ? I tried to use extra and JOIN but this didn't go anywhere.
Thank you
It sounds like you want to perform a filter on an annotation:
class User(models.Model):
pass
class Comment(models.Model):
user = models.ForeignKey(User, related_name="comments")
class Like(models.Model):
comment = models.ForeignKey(Comment, related_name="likes")
type = models.IntegerField()
users = User \
.objects \
.all()
.extra(select = {
"positive_likes" : """
SELECT COUNT(*) FROM app_like
JOIN app_comment on app_like.comment_id = app_comment.id
WHERE app_comment.user_id = app_user.id AND app_like.type = 1 """})
.order_by("positive_likes")
models.py
class UserProfile(models.Model):
.........
def like_count(self):
LikeComment.objects.filter(comment__user=self.user, type=1).count()
views.py
def getRanking( anObject ):
return anObject.like_count()
def myview(request):
users = list(UserProfile.objects.filter())
users.sort(key=getRanking, reverse=True)
return render(request,'page.html',{'users': users})
Timmy's suggestion to use a subquery is probably the simplest way to solve this kind of problem, but subqueries almost never perform as well as joins, so if you have a lot of users you may find that you need better performance.
So, re-using Timmy's models:
class User(models.Model):
pass
class Comment(models.Model):
user = models.ForeignKey(User, related_name="comments")
class Like(models.Model):
comment = models.ForeignKey(Comment, related_name="likes")
type = models.IntegerField()
the query you want looks like this in SQL:
SELECT app_user.id, COUNT(app_like.id) AS total_likes
FROM app_user
LEFT OUTER JOIN app_comment
ON app_user.id = app_comment.user_id
LEFT OUTER JOIN app_like
ON app_comment.id = app_like.comment_id AND app_like.type = 1
GROUP BY app_user.id
ORDER BY total_likes DESCENDING
(If your actual User model has more fields than just id, then you'll need to include them all in the SELECT and GROUP BY clauses.)
Django's object-relational mapping system doesn't provide a way to express this query. (As far as I know—and I'd be very happy to be told otherwise!—it only supports aggregation across one join, not across two joins as here.) But when the ORM isn't quite up to the job, you can always run a raw SQL query, like this:
sql = '''
SELECT app_user.id, COUNT(app_like.id) AS total_likes
# etc (as above)
'''
for user in User.objects.raw(sql):
print user.id, user.total_likes
I believe this can be achieved with Django's queryset:
User.objects.filter(comments__likes__type=1)\
.annotate(lks=Count('comments__likes'))\
.order_by('-lks')
The only problem here is that this query will miss users with 0 likes. Code from #gareth-rees, #timmy-omahony and #Catherine will include also 0-ranked users.