Get a list of entities that match ALL the supplied ids for a related entity field in a many-to-many association - doctrine-orm

I am trying to get a list of users from the database that have ALL the tags in a criteria.
The User entity has a many-to-many association to a Tag entity.
The or version where just one of the tags have to match is working using the following code
$tagIds = array(29,30);
$this->createQueryBuilder('u')
->select('u','t')
->leftJoin('u.tags','t')
->where("t IN(:tagIds)")
->setParameter("tagIds",$tagIds)
;
Can anybody help me with getting it to work so ALL tag ids must match ?
Keep in mind this is a query to get a list of users, not just one user , so i guess every user must be checked to see if they match all the supplied tag ids.
I have tried a bunch of queries but not having any luck so far...

simple bruteforce query:
$tagIds = array(29,30);
$qb = $this->createQueryBuilder('u');
$qb
->select('u')
;
foreach($tagIds as $idx => $tagId)
{
$joinAlias = "t{$idx}";
$qb->leftJoin('u.tags', $joinAlias)
->addSelect($joinAlias)
->andWhere("{$joinAlias}.id = $tagId AND $joinAlias IS NOT NULL")
;
}
this is really bruteforce and costly query, you join each tag as a separate join, if you have lots of users and tags, this will take ages to execute.
since database is the bottleneck of your application, you should make a simple query to the database and then parse the data in your application, so you should use your query and then check which users have those 2 tags in their collections.

Ok... after a lot of searching this seems to work for me :
$tagIds = array(29,30);
$this->createQueryBuilder('u')
->select('u','t')
->leftJoin('u.tags','t')
->where("t IN(:tagIds)")
->groupBy('u.id')
->having('COUNT(DISTINCT t.id) = ' . count($tagIds))
->setParameter("tagIds",$tagIds)
;

Related

django split data and apply search istartswith = query

I have a Project and when searching a query I need to split the data (not search query) in to words and apply searching.
for example:
my query is : 'bot' (typing 'bottle')
but if I use meta_keywords__icontains = query the filter will also return queries with 'robot'.
Here meta_keywords are keywords that can be used for searching.
I won't be able to access data if the data in meta_keywords is 'water bottle' when I use meta_keywords__istartswith is there any way I can use in this case.
what I just need is search in every words of data with just istartswith
I can simply create a model for 'meta_keywords' and use the current data to assign values by splitting and saving as different data. I know it might be the best way. I need some other ways to achieve it.
You can search the name field with each word that istartswith in variable query.
import re
instances = Model.objects.filter(Q(name__iregex=r'[[:<:]]' + re.escape(query)))
Eg: Hello world can be searched using the query 'hello' and 'world'. It don't check the icontains
note: It works only in Python3

Elastic search querying

I'm having some issues with my elastic search querying. I have the following fields, patientid, patientfirstname, patientmidname, and patientlastname. I want to be able to enter in either one of those 4 fields and get matching results returned. So far my query works only if I use a patientid. If i type something like harry (firstname) or middle/last name it doesn't query it. Individual term querying works for each of them.
q = Q({"bool": { "should": [ {"term":{"patientid":text}}, {"wildcard":{"patientlastname":"*"+text+"*"}}, {"wildcard":{"patientfirstname":"*"+text+"*"}}, {"wildcard":{"patientmidname":"*"+text+"*"}} ]}})
r = Search().query(q)[0:10000]
the matching depends on your analyzers, what I would recommend is to just use:
Search().query('multi_match', query=text, fields=['patientid', 'patientlastname', 'patientfirstname', 'patientmidname'])
which will query across those fields (you can read about different types of multi_match query in [0]).
You just need to make sure that all the patient name fields are properly analyzed (see [1] for details)
0 - https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html
1 - https://www.elastic.co/guide/en/elasticsearch/reference/6.4/analysis.html#_index_time_analysis

How do I use django's Q with django taggit?

I have a Result object that is tagged with "one" and "two". When I try to query for objects tagged "one" and "two", I get nothing back:
q = Result.objects.filter(Q(tags__name="one") & Q(tags__name="two"))
print len(q)
# prints zero, was expecting 1
Why does it not work with Q? How can I make it work?
The way django-taggit implements tagging is essentially through a ManytoMany relationship. In such cases there is a separate table in the database that holds these relations. It is usually called a "through" or intermediate model as it connects the two models. In the case of django-taggit this is called TaggedItem. So you have the Result model which is your model and you have two models Tag and TaggedItem provided by django-taggit.
When you make a query such as Result.objects.filter(Q(tags__name="one")) it translates to looking up rows in the Result table that have a corresponding row in the TaggedItem table that has a corresponding row in the Tag table that has the name="one".
Trying to match for two tag names would translate to looking up up rows in the Result table that have a corresponding row in the TaggedItem table that has a corresponding row in the Tag table that has both name="one" AND name="two". You obviously never have that as you only have one value in a row, it's either "one" or "two".
These details are hidden away from you in the django-taggit implementation, but this is what happens whenever you have a ManytoMany relationship between objects.
To resolve this you can:
Option 1
Query tag after tag evaluating the results each time, as it is suggested in the answers from others. This might be okay for two tags, but will not be good when you need to look for objects that have 10 tags set on them. Here would be one way to do this that would result in two queries and get you the result:
# get the IDs of the Result objects tagged with "one"
query_1 = Result.objects.filter(tags__name="one").values('id')
# use this in a second query to filter the ID and look for the second tag.
results = Result.objects.filter(pk__in=query_1, tags__name="two")
You could achieve this with a single query so you only have one trip from the app to the database, which would look like this:
# create django subquery - this is not evaluated, but used to construct the final query
subquery = Result.objects.filter(pk=OuterRef('pk'), tags__name="one").values('id')
# perform a combined query using a subquery against the database
results = Result.objects.filter(Exists(subquery), tags__name="two")
This would only make one trip to the database. (Note: filtering on sub-queries requires django 3.0).
But you are still limited to two tags. If you need to check for 10 tags or more, the above is not really workable...
Option 2
Query the relationship table instead directly and aggregate the results in a way that give you the object IDs.
# django-taggit uses Content Types so we need to pick up the content type from cache
result_content_type = ContentType.objects.get_for_model(Result)
tag_names = ["one", "two"]
tagged_results = (
TaggedItem.objects.filter(tag__name__in=tag_names, content_type=result_content_type)
.values('object_id')
.annotate(occurence=Count('object_id'))
.filter(occurence=len(tag_names))
.values_list('object_id', flat=True)
)
TaggedItem is the hidden table in the django-taggit implementation that contains the relationships. The above will query that table and aggregate all the rows that refer either to the "one" or "two" tags, group the results by the ID of the objects and then pick those where the object ID had the number of tags you are looking for.
This is a single query and at the end gets you the IDs of all the objects that have been tagged with both tags. It is also the exact same query regardless if you need 2 tags or 200.
Please review this and let me know if anything needs clarification.
first of all, this three are same:
Result.objects.filter(tags__name="one", tags__name="two")
Result.objects.filter(Q(tags__name="one") & Q(tags__name="two"))
Result.objects.filter(tags__name_in=["one"]).filter(tags__name_in=["two"])
i think the name field is CharField and no record could be equal to "one" and "two" at same time.
in python code the query looks like this(always false, and why you are geting no result):
from random import choice
name = choice(["abtin", "shino"])
if name == "abtin" and name == "shino":
we use Q object for implement OR or complex queries
Into the example that works you do an end on two python objects (query sets). That gets applied to any record not necessarily to the same record that has one AND two as tag.
ps: Why do you use the in filter ?
q = Result.objects.filter(tags_name_in=["one"]).filter(tags_name_in=["two"])
add .distinct() to remove duplicates if expecting more than one unique object

Filter for elements using exists through a reverse foreign key relationship

A relevant image of my model is here: http://i.stack.imgur.com/xzsVU.png
I need to make a queryset that contains all cats who have an associated person with a role of "owner" and a name of "bob".
The sql for this would be shown below.
select * from cat where exists
(select 1 from person inner join role where
person.name="bob" and role.name="owner");
This problem can be solved in two sql queries with the following django filters.
people = Person.objects.filter(name="bob", role__name="owner")
ids = [p.id for p in people]
cats = Cat.objects.filter(id__in=ids)
My actual setup is more complex than this and is dealing with a large dataset. Is there a way to do this with one query? If it is impossible, what is the efficient alternative?
I'm pretty sure this is your query:
cats = Cat.objects.filter(person__name='bob', person__role__name='owner')
read here about look ups spanning relationships

Doctrine join filter

I know conditional filters aren't yet available for queries (as per "26.1.4. Applying Filter Rules to any Query" in the Known Limitations section of the Doctrine2 manual) , so I wanted to ask the experts what their preferred solution to the following problem is:
My site has product objects that each have many reviews. The reviews have a status field. I don't want to unnecessarily pull in reviews that haven't been approved during the automatic association that Doctrine2 does so wonderfully.
My current solution/hack is to use single table inheritance (STI) with a discriminator for "status" and have an ApprovedProductReview extending the ProductReview class based on a status of "APPROVED"
I should add that I am currently simply calling
$em->find('Entities\Product', $pid);
to get my product, and Doctrine2 does all the associations automatically. Should I instead be instantiating a product by providing a DQL query?
What I'd really like is a way to override the magic that Doctrine2 provides based on the annotations, and simply be able to use DQL to lazily get the correct subset of reviews.
Suggestions?
You can use the WITH statement in DQL:
$dql = "SELECT p, r
FROM Entities\Product p
LEFT JOIN p.reviews r WITH r.status = :status
WHERE p.id = :id"
$q = $em->createQuery($dql);
$q->setParameter('id', $id);
$q->setParameter('status', $status);
$product = $q->getSingleResult();