symfony (doctrine): DQL query, how to exclude results? - sql

I have very basic knowledge of SQL and don't how to modify my query so it returns the expected result.
My entities Activity has a field members (a manyToMany relation). An activity can be done by several members.
I want to select all the activities a specific member does NOT take part into. I know the member's id (my variable $selfId).
Here's what I'm doing in ActivityRepository:
public function getCollectiveActivities($selfId)
{
$qb = $this->createQueryBuilder('a');
$qb->join('a.members', 'm')
->where('m.id != :selfId')
->setParameter('selfId', $selfId);
return $qb
->getQuery()
->getResult();
}
My problem: when an activity "owns" two or more members, if one of them has $selfId as id, this activity ends up in my query results.
But since one of the members of this activity has the id $selfId, it should not be returned. What am I doing wrong?
EDIT
I think I need to be more restrictive, I just don't know how.
Let's say I have two activities:
activity1 which is owned by member1 and member2
activity2 which is owned by member3 and member4
$selfId = 3 (this means I don't want to fetch activities owned by member3)
From what I understand, the join clause might return lines like these:
activity1 | memberId: 1 (different from selfId = 3 so activity1 will be fetched)
activity1 | memberId: 2 (different from selfId = 3 so activity1 will be fetched)
activity2 | memberId: 3 (= selfId so activity2 shouldn't be fetched)
activity2 | memberId: 4 (different from selfId = 3 so activity2 WILL be fetched. PROBLEM???)
EDIT 2
Others already faced the same problem and found this solution and this one, but they seem a bit hacky. Any clue on how to improve them would be welcome.

You have to specifically select the results you want to be hydrated. The problem you're seeing is that you're just selecting activity.
Then when you call $activity->getMembers() members are lazy loaded, and this doesn't take into account your query.
You can avoid this like so:
public function getCollectiveActivities($selfId)
{
$qb = $this->createQueryBuilder('a');
$qb->addSelect('m');
$qb->join('a.members', 'm')
->where('m.id != :selfId')
->setParameter('selfId', $selfId);
return $qb
->getQuery()
->getResult();
}
This means your fetched activity will already have its members hydrated and restricted by your query condition.

I ended up adapting a solution posted by #NtskX.
public function getCollectiveActivities($selfId)
{
$qbMyCollectives = $this->createQueryBuilder('ca');
$qbMyCollectives
->select('ca.id')
->leftJoin('ca.members', 'm')
->where('m.id = :selfId')
->setParameter('selfId', $selfId);
$qb = $this->createQueryBuilder('a');
$qb
->where($qb->expr()->notIn('a.id', $qbMyCollectives->getDQL()))
->setParameter('selfId', $selfId); // I had to declare the parameter one more time
return $qb
->getQuery()
->getResult();
}
I really thought someone would show up with a better way to do the join, so any other answer is much welcome.

public function getCollectiveActivities($selfId)
{
return $this->createQueryBuilder('a')
->join('a.members', 'm')
->where($qb->expr()->neq('c.selfId', $selfId))
->getQuery()
->getResult();
}

Related

Laravel 8 relationship with two columns in each table

I have a multiple DB connection in Laravel 8 projects, I can't modify one of them which is mainly used in ROR project.
I need to fetch data from two tables only if id1, id2 columns have same value in both the tables. I went on google but did not find anything.
I tried some examples, either it is giving me N+1 query issue or second table o/p always null
function example()
{
return $this->belongsTo(Model::class, 'id1', 'id1')->where('id2', $this->id2);
}
function example()
{
return $this->belongsTo(Model::class, 'id1,id2', 'id1,id2');
}
I came up with a solution from https://laravel.io/forum/06-26-2014-one-to-one-relationship-with-two-fieldscolumns-connection-how
I used join with Laravel relation query:
HouseHold::where("printedcardno","=","17130103-0124")->with(array('members' => function($query)
{
$query->leftJoin('PatientCard', function($join)
{
$join->on('PatientCard.HouseHoldID', '=', 'shp_members.HouseHoldID')
->on('PatientCard.MemberID', '=', 'shp_members.MemberID');
});
}))->get()->take(10)->toArray();
I think this should be good approach as it fixed N+1 query issue and getting desired o/p.

Join multiple tables used as Indexing - Laravel

I have three tables - Chairman, Designation, Members.
MY requirement is to map the member to chairman and assign member a role.
I was able to fetch the list of members under the chairman when I had chairman_id and designation_id in the members table.
Since the chairman change, most of the members stay intact. So I came up with an idea of indexing them
Table - membermap
id | chairman_id | designation_id | member_id
So the list is preserved how many chairmans come and go. I dont need to create new profile for new chairman rather than map to it.
I am now sure how do I do it,
So far I was able to pull the ID but I am not sure how do I join the tables
Tables
Chairman
id| name
Designation
id|designation
Members
id|members
Here is my controller
$mapmember = Statechairman::findOrFail($id)->statechairmembersmap;
dd($mapmember);
In this Iam getting the statechairmembersmap but it's fetching all the result and not limiting the match.
I also tried to join the query using the DB
$mapmember = DB::table('statechairmen')
->join('state_chairman_members_maps', 'state_chairman_members_maps.chairman_id','statechairmen.id')
->join('statemembers','statemembers.id','state_chairman_members_maps.members_id')
->select('state_chairman_members_maps.*')->get();
but this result show me the Table - membermap but not the other results.
My Models:
Chairman :
public function statechairmembersmap(){
return $this->hasMany('App\StateChairmanMembersMap','chairman_id','id');
}
public function statemembers(){
return $this->hasMany('App\Statemembers','chairman_id', 'id');
}
public function statedesignation(){
return $this->hasMany('App\Statedesignation','id','designation_id');
}
membermap:
protected $table = 'state_chairman_members_maps';
protected $dates = ['deleted_at'];
public function statechairman(){
return $this->belongsTo('App\Statechairman','id');
}
public function statedesignations(){
return $this->belongsTo('App\Statedesignation','designation_id','id');
}
public function statemembers(){
return $this->belongsTo('App\Statemembers','members_id','id');
}
Please assist me where I doing wrong.
Thanks a lot for checking the question out.
Finally after a lot of strugle, I was able to find it by myself.
$mapmembers = DB::table('state_chairman_members_maps')
->join('statechairmen','statechairmen.id','=','state_chairman_members_maps.chairman_id')
->join('statemembers','statemembers.id','=','state_chairman_members_maps.members_id')
->join('statedesignations','statedesignations.id','=','state_chairman_members_maps.designation_id')
->where('chairman_id','=',$id)
->get();
Here is what I came up with.
Here I have joined 3 tables and mapped the id comming from the chairman to filter the result. I getting the results.

Count one-to-one relationship in grails

I have a problem doing a query. That I want to do is access with a query to the data in my "String code" in MyDomainA through a query from MyDomainB. The relationship between the tables is unidirectional one to one.
Is possible use a gorm way to do this??
Domain A:
class LicenceType {
String code
String description
Double price
static constraints = {
}
}
TABLE DOMAIN A
code description
A this is A
B this is B
C this is C
Domain B: (have the unidirectional relationship)
class VoiceUser {
LicenceType licenceType
String username
String email
String nameID
}
TABLE DOMAIN B
User
1
2
3
4
That I want to do is know how many users have the same code(code is a column of DomainA and both tables have a unidirectional relationship as I indicated before).
This is that I'm trying to do that is wrong...
Controller:
def resulta = VoiceUser.executeQuery('SELECT a.code, COUNT(b.nameID) FROM VoiceUser AS b INNER JOIN b.licenceType AS a GROUP BY a.code')
def resultCount = resulta[0]
This is some example result that I hope...
Users with code A = 2
Users with code B = 2
Users with code C = o
The trick is to do a group by on the code and a count() on the user. You can do this using either HQL or a criteria query.
HQL
Here's an example in HQL:
VoiceUser.executeQuery('SELECT licence.code, COUNT(user) FROM VoiceUser AS user INNER JOIN user.licenceType AS licence GROUP BY licence.code')
If you're familiar with SQL, most of this should make sense right away. An important difference is the syntax for joining domain classes. HQL deals with domain classes, not tables.
Criteria query
And here's the equivalent criteria query.
VoiceUser.withCriteria {
projections {
licenceType {
groupProperty('code')
}
count('id')
}
}
Alternative queries
The queries shown above return a List<List> like this:
[
['A', 2],
['B', 2],
['C', 0]
]
If you provide a LicenceType (or its code) as input to the query, then you can get the count for just that LicenceType. For instance, here are examples which retrieve the user count for licence code 'A'.
HQL
def result = VoiceUser.executeQuery('SELECT COUNT(user) FROM VoiceUser AS user INNER JOIN user.licenceType AS licence WHERE licence.code = :code', [code: 'A'])[0]
Criteria query
def result = VoiceUser.createCriteria().get {
licenceType {
eq('code', 'A')
}
projections {
count('id')
}
}
Additional resources
I've got a series of articles which explain HQL, criteria, and where queries in detail; such as how to use projections and joins. Feel free to check them out.

How to change Doctrine "findBy/findOneBy" functions's behaviors to reduce the number of queries?

I'm working on a Symfony2 using Doctrine.
I would like to know how to change the behavior of "findBy" functions when retrieving my entities.
For example, if you call "findAll()", it returns all products.
$entities = $em->getRepository('ShopBundle:Product')->findAll();
However, how to reduce the number of queries, because, by default, it will create a new query each time I want to get a member linked to a join column. So if I get 100 entities, it will process 101 queries (1 to get all entities and 1 by entity to get join column).
So today, I use createQuery() function by specifying the joins. Is there a way to configure something about findBy functions to skip createQuery method ?
Thanks in advance !
K4
You can fetch out this in below way
public function findUser() {
$query = $this->getEntityManager()
->createQuery('SELECT us.id as id, us.name as user_name FROM Bundle:User us');
try {
return $query->getResult();
} catch (\Doctrine\ORM\NoResultException $e) {
return null;
}
}

Simple SQL to Eloquent Query (Laravel)

I have two tables: users (Users) and groups (Groups).
Users
-----------------
id | username | group
1 | Bob | 2
Groups
-----------------
id | name
1 | firstgroup
2 | secondgroup
I would like to display: users.ID, users.username, group.name (1, Bob, secondgroup)
An SQL statement like so would work:
SELECT Users.id, Users.username, Groups.name
FROM Users
INNER JOIN
Groups ON Groups.id = Users.group
However, I'm struggling to write this in Eloquent, since there is no "FROM". At the moment I'm going for something along the lines of the below, using JOINS (http://laravel.com/docs/queries#joins)
$users = Users::select('id','username', 'Groups.name')->joins('Groups.id', '=', 'id')->get();
Now this isn't working - I think the joins has to come before the select but I just can't work it out :(
I think you're confusing a few things here...
You're mixing Eloquent with the lower-level DB::table('foo')->select() syntax. When you want to use Eloquent I suggest you take a look at the docs about relationships in Eloquent.
You should define your models like so:
class User extends Eloquent {
public function group()
{
return $this->belongsTo('Group', 'group');
// second parameter is necessary because you didnt
// name the column "group_id" but simply "group"
}
}
class Group extends Eloquent {
public function users()
{
return $this->hasMany('User', 'group');
}
}
This sets up all the joins you might be needing later. You can then simply use User::with('group')->all(); and have the query built and run for you.
Database: Query Builder(DB) is not a Eloquent(ORM):
Database query builder you have to inform the table names and the fields, like it says on in your related link of laravel docs: "...provides a convenient, fluent interface to creating and running database queries." like these query below:
$users = DB::table('users')
->join('contacts', 'users.id', '=', 'contacts.user_id')
->join('orders', 'users.id', '=', 'orders.user_id')
->select('users.*', 'contacts.phone', 'orders.price')
->get();
Eloquent is a ORM - Object related Mapping, it means that your class User is related to the table users (look at you files Migrations) and this class extends the Model Class, thus you can access the methods like these bellow:
class User extends Models
{
public static function usersWithGroups(){
return User::select('id', 'name', 'email')->with('groups')->get();
}
}
Observe that method is into the class User, so you can access that in a static way "User::", using Eloquent you'll have many hidden static methods that will improve you time codding, because you are inheriting de Model methods, to more details visit the Eloquent Docs at: Eloquent Docs