Is it possible to query a recursive relationship in D2 for all fields that don't have a related element specified?
The following should work:
SELECT c FROM RecursiveEntity c WHERE p.parent IS NULL
Related
I've got these models:
class Container(models.Model):
...
class Meta:
constraints = [
models.CheckConstraint(
check=~Q(elements=None),
name='container_must_have_elements'
),
]
class Element(models.Model):
container = models.ForeignKey(Container),
related_name='elements',
on_delete=models.CASCADE
)
I want to enforce the constraint that every Container object must have at least one Element referencing it via the foreign key relation.
As you can see I already added a check constraint. However, the negation operator ~ on the Q object seems to be forbidden. I get django.db.utils.NotSupportedError: cannot use subquery in check constraint when I try to apply the generated migration.
Without the negation operator the constraint seems to be valid (it only fails due to a data integrity error).
Is there another way I can express this constraint so it is supported by CheckConstraint?
(E.g. is there a way to check if the set of elements is not empty?)
I'll answer my own question by summarizing the question's comments.
A check constraint is intended to check every row in a table for a condition, which only takes the row itself into consideration and does not join other tables for this.
Sticking with SQL, one can formulate extended constraints including other tables by defining a function in SQL and calling it from within the constraint.
The CheckConstraint introduced in Django 2.2 only supports conditions on the table itself by using Q objects.
Update:
Since Django 3.1, CheckConstraints not only support Q objects but also boolean Expressions. See the Django 3.2 documentation.
Suppose I have two models -
class A(models.Model):
a_id=models.CharField(max_length=255,primary_key=True)
a_name=models.CharField(max_length=255)
class B(models.Model):
a=models.ForeignKey(A)
b_name=models.CharField(max_length=255)
I want to filter B objects which belong to a particular a_id. I can either do this -
B.objects.filter(a=a_id)
or
B.objects.filter(a__a_id=a_id)
Is there any difference between the two, in terms of efficiency, speed or functionality?
B.objects.filter(a=a_id) is much more efficient because it simply filters the values of B.a and avoids joining table A altogether, while B.objects.filter(a__a_id=a_id) requires joining table A by a_id and then filtering a_id with a.
I have two entities (AdminMembers\Entity\Members and AdminEvents\Entity\Invitees) that are joined with a OneToMany relationship. Because the relationship is defined in the entities, I can use the following DQL statement to query the data:
$dql = "SELECT i FROM AdminEvents\Entity\Invitees i WHERE i.eventID=$eventID";
And, through this configuration I can use statements like $invitee->getMember()->getMemberLastName() in my ZF2 view script to get the data from the joined entities.
Now, if I want to sort the data in the DQL statement by fields in AdminMembers\Entity\Members, I run into a problem. The following statement
$dql = "SELECT i FROM AdminEvents\Entity\Invitees i WHERE i.eventID=$eventID ORDER BY i.memberLastName, i.memberFirstName";
throws the error message
Class AdminEvents\Entity\Invitees has no field or association named memberLastName
This is because the fields memberLastName and memberFirstName are defined in AdminMembers\Entity\Members. Although an association does exist, I'm certain I’m just missing some syntax in referencing memberLastName and memberFirstName.
I know that I can expand the DQL statement to join the entities in the DQL statement, which would allow me to identify the joined table and relate the elements to table identifier. But, since the tables are already joined in the entity definitions, they shouldn’t have to be joined again.
Is there a special syntax for referencing joined-table entities in the DQL statement without "rejoining" the tables in the statement?
You should join the members entity to be able to sort by its fields. See docs:
$dql = "SELECT i, m FROM AdminEvents\Entity\Invitees i JOIN i.member m WHERE i.eventID=$eventID ORDER BY m. memberLastName, m.memberFirstName";
I've always found the Django orm's handling of subclassing models to be pretty spiffy. That's probably why I run into problems like this one.
Take three models:
class A(models.Model):
field1 = models.CharField(max_length=255)
class B(A):
fk_field = models.ForeignKey('C')
class C(models.Model):
field2 = models.CharField(max_length=255)
So now you can query the A model and get all the B models, where available:
the_as = A.objects.all()
for a in the_as:
print a.b.fk_field.field2 #Note that this throws an error if there is no B record
The problem with this is that you are looking at a huge number of database calls to retrieve all of the data.
Now suppose you wanted to retrieve a QuerySet of all A models in the database, but with all of the subclass records and the subclass's foreign key records as well, using select_related() to limit your app to a single database call. You would write a query like this:
the_as = A.objects.select_related("b", "b__fk_field").all()
One query returns all of the data needed! Awesome.
Except not. Because this version of the query is doing its own filtering, even though select_related is not supposed to filter any results at all:
set_1 = A.objects.select_related("b", "b__fk_field").all() #Only returns A objects with associated B objects
set_2 = A.objects.all() #Returns all A objects
len(set_1) > len(set_2) #Will always be False
I used the django-debug-toolbar to inspect the query and found the problem. The generated SQL query uses an INNER JOIN to join the C table to the query, instead of a LEFT OUTER JOIN like other subclassed fields:
SELECT "app_a"."field1", "app_b"."fk_field_id", "app_c"."field2"
FROM "app_a"
LEFT OUTER JOIN "app_b" ON ("app_a"."id" = "app_b"."a_ptr_id")
INNER JOIN "app_c" ON ("app_b"."fk_field_id" = "app_c"."id");
And it seems if I simply change the INNER JOIN to LEFT OUTER JOIN, then I get the records that I want, but that doesn't help me when using Django's ORM.
Is this a bug in select_related() in Django's ORM? Is there any work around for this, or am I simply going to have to do a direct query of the database and map the results myself? Should I be using something like Django-Polymorphic to do this?
It looks like a bug, specifically it seems to be ignoring the nullable nature of the A->B relationship, if for example you had a foreign key reference to B in A instead of the subclassing, that foreign key would of course be nullable and django would use a left join for it. You should probably raise this in the django issue tracker. You could also try using prefetch_related instead of select_related that might get around your issue.
I found a work around for this, but I will wait a while to accept it in hopes that I can get some better answers.
The INNER JOIN created by the select_related('b__fk_field') needs to be removed from the underlying SQL so that the results aren't filtered by the B records in the database. So the new query needs to leave the b__fk_field parameter in select_related out:
the_as = A.objects.select_related('b')
However, this forces us to call the database everytime a C object is accessed from the A object.
for a in the_as:
#Note that this throws an DoesNotExist error if a doesn't have an
#associated b
print a.b.fk_field.field2 #Hits the database everytime.
The hack to work around this is to get all of the C objects we need from the database from one query and then have each B object reference them manually. We can do this because the database call that accesses the B objects retrieved will have the fk_field_id that references their associated C object:
c_ids = [a.b.fk_field_id for a in the_as] #Get all the C ids
the_cs = C.objects.filter(pk__in=c_ids) #Run a query to get all of the needed C records
for c in the_cs:
for a in the_as:
if a.b.fk_field_id == c.pk: #Throws DoesNotExist if no b associated with a
a.b.fk_field = c
break
I'm sure there's a functional way to write that without the nested loop, but this illustrates what's happening. It's not ideal, but it provides all of the data with the absolute minimum number of database hits - which is what I wanted.
Can I have a DQL join between unrelated entities using WITH DQL operator? OR is definign relationship absolutely mandatory?
I have a unidirectional relationship with Category and CategorySubsription. Where CategorySubscription has a many to one unidirectional relationship with Category. I want to grab a list of Categories c and left join CategorySubscription cs WITH cs.category_id = c.id AND cs.user_id = value.
Can I do this somehow?
Starting with Doctrine version 2.3 you can, as mentioned in a blog.
It's also mentioned in the docs if you know where to look. Scroll down to the last example under "15.2.4. DQL SELECT Examples":
Joins between entities without associations were not possible until version 2.4, where you can generate an arbitrary join with the following syntax:
<?php
$query = $em->createQuery('SELECT u FROM User u JOIN Blacklist b WITH u.email = b.email');
I know it says "not possible until version 2.4", but it definitely works starting with 2.3!
You can try using multiple from() methods and join conditions put in where() or andWhere methods()