Using subqueries with UPDATE in Django's ORM - python

Say I have two models, which I'll call ModelA and ModelB. Both models have a few fields in common, (represented by field_one, field_two, and field_three). Additionally, ModelB has a foreign key to ModelA.
class ModelA(Model):
field_one = models.IntegerField()
field_two = models.TextField()
field_three = models.BooleanField()
class ModelB(Model):
field_one = models.IntegerField()
field_two = models.TextField()
field_three = models.BooleanField()
model_a = models.ForeignKey(ModelA, on_delete=models.CASCADE)
I need to update all instances of ModelB to update the field's values to the values of the associated ModelA instances. I need to do this operation entirely in the database, without needing to instantiate any model instances (NOT using .save() or bulk_update()).
I know how I can accomplish this in PostgreSQL using subqueries:
UPDATE model_b SET (field_one, field_two, field_three) =
(SELECT field_one, field_two, field_three FROM model_a
WHERE model_b.model_a_id = model_a.id);
How can I express the above query in Django's ORM?
This is the closest I have been able to get:
ModelB.objects.update(
field_one=Subquery(ModelA.objects.filter(id=OuterRef('model_a_id')).values(field_one)[:1]),
field_two=Subquery(ModelA.objects.filter(id=OuterRef('model_a_id')).values(field_two)[:1]),
field_three=Subquery(ModelA.objects.filter(id=OuterRef('model_a_id')).values(field_three)[:1])
})
However, this results in a subquery for every single field:
UPDATE model_b SET
field_one = (SELECT model_a.field_one FROM model_a WHERE model_a.id = model_b.model_a_id LIMIT 1),
field_two = (SELECT model_a.field_two FROM model_a WHERE model_a.id = model_b.model_a_id LIMIT 1),
field_three = (SELECT model_a.field_three FROM model_a WHERE model_a.id = model_b.model_a_id LIMIT 1);

Unfortunately the ORM doesn't support annotations spreading multiple columns and I'm not aware of a feature request for it.
If you want to stick to the ORM you'll have to take the possible performance hit (maybe PostgreSQL is smart enough to use only a single one; EXPLAIN would tell) otherwise you'll have to switch to .raw SQL.

by passing modelA as an argument in the modelB class arguments, ModelB and modelC will inherit of every field of modelA
class ModelA(Model):
field_one = models.IntegerField()
field_two = models.TextField()
field_three = models.BooleanField()
class ModelB(ModelA):
pass
class ModelC(ModelA):
pass

Related

Django ORM multiple tables query

I trying to make some queries in Django ORM (migration from SQL). My models looks like this
class Iv2(models.Model):
s_id = models.AutoField(primary_key=True)
l_eid = models.CharField(max_length=265)
t_id = models.CharField(max_length=265,unique=True)
class Sv2(models.Model):
id = models.AutoField(primary_key=True)
s_id = models.OneToOneField(Iv2, on_delete=models.PROTECT)
gdd = models.DateTimeField(default=datetime.now)
class Ev2(models.Model):
id = models.OneToOneField(Iv2, to_field='l_eid', on_delete=models.PROTECT)
s_id = models.ForeignKey(Iv2, on_delete=models.PROTECT)
car = models.CharField(max_length=265)
I want to write a query, given t_id(some real search value). I want to get the corresponding Sv2.gdd and Ev2.car
I'm thinking to get s_id and l_eid with the t_id. And when I get s_id. I can query Sv2 and with l_eid I can query Ev2.
Is it possible to achieve everything with one ORM query ? can prefetch/select_related work here?
"Given t_id of Iv2 get Sv2.gdd and Ev2.car"
First get the Sv2 instance by filtering its 1:1 relation by the
real search value t_id:
sv2 = Sv2.objects.filter(s_id__t_id=t_id).first()
sv2.gdd
Now you have 2 options to get Ev2.car
Add related name in Ev2 (more on related_name here )
Assuming your related_name is ev2 you can do:
sv2.ev2.car
Use the default django related_name modelname__set
sv2.ev2_set

django filter get parent to child

i am using django(3.1.5). and i am trying to get parent model to child model by filter query
i have model like -
class Product(models.Model):
product_name = models.CharField(max_length=255, unique=True)
is_feature = models.BooleanField(default=False)
is_approved = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
class ProductGalleryImage(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
product_gallery_image = models.FileField(upload_to='path')
is_feature = models.BooleanField(default=False)
i am getting data from SELECT * FROM products_product AS pp INNER JOIN products_productgalleryimage AS ppgi ON ppgi.product_id = pp.id WHERE ppgi.is_feature=1 AND pp.is_feature=1 AND is_approved=1 ORDER BY pp.created_at LIMIT 4 mysql query.
so how can i get data like this query in django filter query
Firstly you can add related_name to ProductGalleryImage for better query support like this
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='product_images')
Then your query should be like this
products=Product.objects.filter(is_approved=True, is_feature=True, product_images__is_feature=True).order_by('created_at')[:4]
You can simply loop over the other related model like so:
for product_gallery_image in product_instance.productgalleryimage_set.all():
print(product_gallery_image.product_gallery_image)
The productgalleryimage_set here is simply the related model name in lowercase with _set appended. You can change this by setting the related_name attribute on the foreign key.
Note: This will perform a query to fetch each of the product_gallery_image objects of some product instance.
If you want to get the first object only:
product_gallery_image = product_instance.productgalleryimage_set.first()
If you want to perform a join as in your example which will perform only one query you can use select_related (this will only work in forward direction for reverse direction look at prefetch_related):
product_gallery_images = ProductGalleryImage.objects.all().select_related('product')
for product_gallery_image in product_gallery_images:
print(product_gallery_image.product.product_name)
print(product_gallery_image.product_gallery_image)

Reduce the headache on database when using ModelChoiceField

please I need your help how to reduce the database call when using ModelChoiceField as it requires a queryset and I have to use it three times separately with a model that is recursively foreign key on itself, the code is below:
ModelForm code in the init function
self.fields['category'] = forms.ModelChoiceField(queryset=queryset)
self.fields['super_category'] = forms.ModelChoiceField(queryset=)
self.fields['product_type'] = forms.ModelChoiceField(queryset=)
the model class:
class Category(ProjectBaseModel, AuditLogMixin):
parent_id = models.ForeignKey('self', related_name='children', blank=True, null=True, on_delete=models.CASCADE,verbose_name=_('Parent'))
what i tried to do is collect all ids of the desired categories in array and make only one filter queryset with them like the following:
category = auction.category
super_category = category.parent_id
product_type = super_category.parent_id
ids= [category.id,super_category.id,product_type.id]
queryset = Category.objects.filter(id__in=ids)
How to proceed with that solution

Intermediate table with generic foreign key

I'm trying to replace a field in an intermediate table with a generic field. Using Django 1.6, MariaDB/MySQL.
I have a class (PermissionGroup) that links a resource to a group. Works fine. However I have several other tables that are similar - linking some id to a group id.
I thought I could replace these tables with one table that uses a generic foreign key, along with the group id. However this does not validate.
Here's the original, which works:
# core/models.py
class PermissionGroup(models.Model):
resource = models.ForeignKey('core.Resource')
group = models.ForeignKey('auth.Group')
class Resource(models.Model):
groups = models.ManyToManyField('auth.Group', through='core.PermissionGroup')
# auth/models.py
class Group(models.Model):
name = models.CharField(max_length=80, unique=True)
Now, trying to change the PermissionGroup to use a GenericForeignKey:
# core/models.py
class PermissionGroup(models.Model):
content_type = models.ForeignKey('contenttypes.ContentType')
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
group = models.ForeignKey('auth.Group')
class Resource(models.Model):
groups = models.ManyToManyField('auth.Group', through='core.PermissionGroup')
# auth/models.py
class Group(models.Model):
name = models.CharField(max_length=80, unique=True)
The django model validation now fails with:
core.resource: 'groups' is a manually-defined m2m relation through model PermissionGroup, which does not have foreign keys to Group and Resource
Is this simply not possible, or is another means to accomplish this?

How can I do INNER JOIN in Django in legacy database?

Sorry for probably simple question but I'm a newby in Django and really confused.
I have an ugly legacy tables that I can not change.
It has 2 tables:
class Salespersons(models.Model):
id = models.IntegerField(unique=True, primary_key=True)
xsin = models.IntegerField()
name = models.CharField(max_length=200)
surname = models.CharField(max_length=200)
class Store(models.Model):
id = models.IntegerField(unique=True, primary_key=True)
xsin = models.IntegerField()
brand = models.CharField(max_length=200)
So I suppose I can not add Foreign keys in class definitions because they change the tables.
I need to execute such sql request:
SELECT * FROM Salespersons, Store INNER JOIN Store ON (Salespersons.xsin = Store.xsin);
How can I achieve it using Django ORM?
Or I'm allowed to get Salespersons and Store separately i.e.
stores = Store.objects.filter(xsin = 1000)
salespersons = Salespersons.objects.filter(xsin = 1000)
Given your example query, are your tables actually named Salespersons/Store?
Anyway, something like this should work:
results = Salespersons.objects.extra(tables=["Store"],
where=["""Salespersons.xsin = Store.xsin"""])
However, given the names of the tables/models it doesn't seem to me that an inner join would be logically correct. Unless you always have just 1 salesperson per store with same xsin.
If you can make one of the xsin fields unique, you can use a ForeignKey with to_field to generate the inner join like this:
class Salespersons(models.Model):
xsin = models.IntegerField(unique=True)
class Store(models.Model):
xsin = models.ForeignKey(Salespersons, db_column='xsin', to_field='xsin')
>>> Store.objects.selected_related('xsin')
I don't see why you can't use the models.ForeignKey fields even if the database lacks the constraints -- if you don't explicitly execute the SQL to change the database then the tables won't change. If you use a ForeignKey then you can use Salespersons.objects.select_related('xsin') to request that the related objects are fetched at the same time.

Categories