Django many-to-one related query with "and" condition - django

I have the following models struct:
class Drive(models.Model):
car_name = models.CharField(max_length=3,blank=True, null=True,choices=sp.CAR_NAMES ,help_text="The name of the car")
class DataEntity(models.Model):
parent_drive = models.ForeignKey(Drive,models.CASCADE)
type = models.IntegerField(blank=True, null=True,choices=sp.DATA_ENTITY_TYPES, help_text="The Type of the data")
And i'm trying to get all of the Drives that have DataEntity.type = 3 and DataEntity.type = 4
I tried to use the following:
query_set = Q{(AND: ('dataentity__type', 3), ('dataentity__type', 4))}
Drive.objects.filter(query_set).distinct()
but i got empty list...
I had a look on the sql statement:
SELECT ••• FROM `drive` INNER JOIN `data_entity` ON (`drive`.`id` = `data_entity`.`parent_drive_id`) WHERE (`data_entity`.`type` = 3 AND `data_entity`.`type` = 4)) subquery
The Django system put the condition inside the WHERE statement, and it cause the problem (there is no data DataEntity that contain the both types)
How can i make the right queryset in reason to get Drives that contain DataEntity.type = 3 and DataEntity.type = 4 ?
Thanks

You can try to do this:
Drive.objects.filter(dataentity__type__in=[3, 4]).distinct()

I found the solution.
When you used Q(dataentity__type=3)&Q(dataentity__type=4) the ORM system put the AND expression in the "Where" section:
SELECT ••• FROM drive` INNER JOIN data_entity ON (drive.id = data_entity.parent_drive_id) WHERE (data_entity.type = 3 AND data_entity.type = 4))
and i got 0 results since there is no dataentity that have two types.
But when i used Drive.objects.filter(Q(dataentity__type=3))&filter(Q(dataentity__type=4)).distinct()
I got the Drives that have dataentity of type 3 and also dataentity of type 4
The SQL Query:
SELECT ••• FROM `drive` INNER JOIN `data_entity` ON (`drive`.`id` = `data_entity`.`parent_drive_id`) LEFT OUTER JOIN `data_entity` T3 ON (`drive`.`id` = T3.`parent_drive_id`) WHERE (`data_entity`.`type` = 3 AND T3.`type` = 4)

Related

Why my annotation group by is not working as expected?

I wanna do a query like raw sql select count(b.status) from a left out join b on a.id = b.user_id group_by(b.status). I know how to get what I wanted using raw sql. But when I try to implement this using orm, it failed. Below is the code:
query_kwargs_dict = {'id__in': [10000, 10001]}
statistics = UserAuthModel.objects.filter(**query_kwargs_dict).select_related(
'obbyuserinfomodel'
).values('obbyuserinfomodel__status').annotate(count=Count('obbyuserinfomodel__status')).query
print('statistics', statistics)
And the print statement output is:
statistics SELECT `users_obby_info`.`status`, COUNT(`users_obby_info`.`status`) AS `count` FROM `users_auth` LEFT OUTER JOIN `users_obby_info` ON (`users_auth`.`id` = `users_obby_info`.`user_id`) WHERE `users_auth`.`id` IN (10000, 10001) GROUP BY `users_auth`.`id`, `users_obby_info`.`status` ORDER BY `users_auth`.`id` DESC
What confusing me is the group_by. It should be grouped by the field specified in values. In my case, it is obbyuserinfomodel__status , but the output sql statement showed it was grouped by users_auth.id and users_obby_info.status together. What I want is just users_obby_info.status.
Below are the two tables(I have omitted some fields for simplicity):
class UserAuthModel(AbstractBaseUser):
...
class ObbyUserInfoModel(models.Model):
user = models.OneToOneField(UserAuthModel, on_delete=models.CASCADE)
...
status = models.CharField(max_length=15, default=ACCOUNTSTATUS.UNACTIVATED.value, null=True)

Django inner join on nulls

I currently have Django models like this
MyFirstObject(models.Model):
some_field = models.BooleanField(default=False)
MySecondObject(models.Model):
first_object = models.ForeignKey(MyFirstObject, db_column='firstObjectId')
Because of various issues, our data integrity is corrupt. So, I need to find instances where MyFirstObject has been deleted, but MySecondObject still has a row w a foreign key to it.
The database would look similar to:
TABLE my_first_object
id someField
1 a
2 a
3 b
TABLE my_second_object
id firstObjectId
1 1
2 3
3 4
Notice row 3 of the TABLE my_second_object has an firstObjectID that does not have a corresponding record in the my_first_object table. I want to find all instances like that.
If I was doing raw SQL, I would do
SELECT my_second_object.id, my_second_object.firstObjectId
FROM my_second_object
LEFT JOIN ON ( my_second_object.firstObjectId = my_first_object.id )
WHERE my_first_object.id IS NULL
In Djago, I am trying
MySecondObject.objects.filter(my_first_object__id__isnull=true)
But when I look at the query that results, it is doing an inner join instead of left join. Does anyone have suggestions? Thanks!
Try like this:
first_object_ids = MyFirstObject.objects.values_list('id')
get_second_objects = MySecondObject.objects.exclude(my_first_object_id__in = first_object_ids)

Symfony One-To-Many, unidirectional with Join Table query

I have some One-To-Many, unidirectional with Join Table relationships in a Symfony App which I need to query and I can't figure out how to do that in DQL or Query Builder.
The Like entity doesn't have a comments property itself because it can be owned by a lot of different types of entities.
Basically I would need to translate something like this:
SELECT likes
FROM AppBundle:Standard\Like likes
INNER JOIN comment_like ON comment_like.like_id = likes.id
INNER JOIN comments ON comment_like.comment_id = comments.id
WHERE likes.created_by = :user_id
AND likes.active = 1
AND comments.id = :comment_id
I've already tried this but the join output is incorrect, it selects any active Like regardless of its association with the given comment
$this->createQueryBuilder('l')
->select('l')
->innerJoin('AppBundle:Standard\Comment', 'c')
->where('l.owner = :user')
->andWhere('c = :comment')
->andWhere('l.active = 1')
->setParameter('user', $user)
->setParameter('comment', $comment)
I see 2 options to resolve this:
Make relation bi-directional
Use SQL (native query) + ResultSetMapping.
For the last option, here is example of repository method (just checked that it works):
public function getLikes(Comment $comment, $user)
{
$sql = '
SELECT l.id, l.active, l.owner
FROM `like` l
INNER JOIN comment_like ON l.id = comment_like.like_id
WHERE comment_like.comment_id = :comment_id
AND l.active = 1
AND l.owner = :user_id
';
$rsm = new \Doctrine\ORM\Query\ResultSetMappingBuilder($this->_em);
$rsm->addRootEntityFromClassMetadata(Like::class, 'l');
return $this->_em->createNativeQuery($sql, $rsm)
->setParameter('comment_id', $comment->getId())
->setParameter('user_id', $user)
->getResult();
}
PS: In case of Mysql, 'like' is reserved word. So, if one wants to have table with name 'like' - just surround name with backticks on definition:
* #ORM\Table(name="`like`")
I find the Symfony documentation very poor about unidirectional queries.
Anyway I got it working by using DQL and sub-select on the owning entity, which is certainly not as fast. Any suggestion on how to improve that is more than welcomed!
$em = $this->getEntityManager();
$query = $em->createQuery('
SELECT l
FROM AppBundle:Standard\Like l
WHERE l.id IN (
SELECT l2.id
FROM AppBundle:Standard\Comment c
JOIN c.likes l2
WHERE c = :comment
AND l2.owner = :user
AND l2.active = 1
)'
)
->setParameter('user', $user)
->setParameter('comment', $comment)
;

SQL Update From Where Query

I have 2 tables with information in them. I need to update the SelfServiceUserName column in table A_CLIENT with the value from the SubstVarValue column of the A_DEV_SUBSTVAR_VALUE table when the ClientUID and DeviceID match and the SubstVarName from the A_DEV_SUBSTVAR_VALUE table = samaccount name. Here is the query I've tried to run but I keep getting errors:
UPDATE A_CLIENT
SET SelfServiceUserName = (SELECT SubstVarValue
FROM A_DEV_SUBSTVAR_VALUE
WHERE A_DEV_SUBSTVAR_VALUE.SubstVarName = 'samaccountname')
WHERE A_CLIENT.ClientUID = A_DEV_SUBSTVAR_VALUE.DeviceID
I always write a join between the two tables first to get the rows I want side by side and make sure I have the JOIN clause correct.
SELECT p.ProductID, p.ProductName, p.Price AS OldPrice, n.Price as NewPrice
FROM Products as p
JOIN NewPrices as n on p.ProductID = n.ProductID
Once I have done that it's easy to change it into an update statement by replacing the SELECT clause with an UPDATE and SET:
UPDATE p
SET Price = n.Price
FROM Products as p
JOIN NewPrices as n on p.ProductID = n.ProductID
Note you don't alias the Price on the left side of the SET clause, because it is necessarily from the p (Product) table, so there is no ambiguity. You must still alias the Price on the right of the equals because it could be the field coming from either the p (Product) or n (NewPrice) table.
You could also use a CTE (Common Table Expression) if your SQL engine supports it:
WITH x AS (
SELECT p.ProductID, p.ProductName, p.Price AS OldPrice, n.Price as NewPrice
FROM Products as p
JOIN NewPrices as n on p.ProductID = n.ProductID
)
UPDATE x set OldPrice = NewPrice
Try something like
update a_client c
inner join a_dev_substvar_value d on
c.clientuid = d.deviceid
set
c.selfserviceusername = d.substvarvalue
where
d.substvarname = 'samaccountname';
Note, you should try avoid writing select statements in your were clause because it is run for ever row returned. This can be a big performance hit.
That should work.

SqlAlchemy core union_all not adding parentheses

I have the following sample code:
queries = []
q1 = select([columns]).where(table.c.id == #).limit(#)
queries.append(q1)
q2 = select([columns]).where(table.c.id == #).limit(#)
queries.append(q2)
final_query = union_all(*queries)
The generated SQL should be this:
(select columns from table where id = # limit #)
UNION ALL
(select columns from table where id = # limit #)
But, I'm getting
select columns from table where id = # limit #
UNION ALL
select columns from table where id = # limit #
I tried using subquery, as follows for my queries:
q1 = subquery(select([columns]).where(table.c.id == #).limit(#))
The generated query then looks like this:
SELECT UNION ALL SELECT UNION ALL
I also tried doing
q1 = select([columns]).where(table.c.id == #).limit(#)).subquery()
But, I get the error:
'Select' object has no attribute 'subquery'
Any help to get the desired output with my subqueries wrapped in parentheses?
Note: this is not a duplicate of this question, because I'm not using Session.
EDIT
Okay, this works, but I don't believe it is very efficient, and it's adding an extra select * from (my sub query), but it works.
q1 = select('*').select_from((select(columns).where(table.c.id == #).limit(#)).alias('q1'))
So, if anyone has any ideas to optimize, or let me know if this is as good as it gets. I would appreciate it.
The author of SQLAlchemy seems to be aware of this and mentions a workaround for it on the SQLAlchemy 1.1 changelog page. The general idea is to do .alias().select() on each select.
stmt1 = select([table1.c.x]).order_by(table1.c.y).limit(1).alias().select()
stmt2 = select([table2.c.x]).order_by(table2.c.y).limit(2).alias().select()
stmt = union(stmt1, stmt2)