I am selecting 3 entities via regular LEFT JOIN in DQL. They are related via join tables which also have entities defined for them as well as annotated relationships.
The query executes without issue but my results are returned as a flat array. I would expect an array with the three entities as array elements for each index:
SELECT e1, e2, e3 FROM AppBundle:EntityOne
JOIN AppBundle:JoinEntityOneWithTwo e1_2 WITH e1_2.entity1 = e1.id
JOIN AppBundle:EntityTwo e2 WITH e1_2.entity2 = e2.id
JOIN AppBundle:JoinEntityOneWithThree e1_3 WITH e1_3.entity1 = e1.id
JOIN AppBundle:EntityThree e3 WITH e3.id = e1_3.entity3
WHERE e1.some_field IN ('some','values','in','e1');
When I call getResult() on the query, either hydrating as an object or an array, I get a flat results set:
array(
/AppBundle/Entity/EntityOne ( ... ),
/AppBundle/Entity/EntityTwo ( ... ),
/AppBundle/Entity/EntityThree ( ... ),
/AppBundle/Entity/EntityOne ( ... ),
/AppBundle/Entity/EntityTwo ( ... ),
/AppBundle/Entity/EntityThree ( ... ),
/AppBundle/Entity/EntityTwo ( ... ),
/AppBundle/Entity/EntityThree ( ... ),
/AppBundle/Entity/EntityOne ( ... ),
)
I would expect, or like to have a multi dimensional array:
Array(
[0] => Array(
[0] /AppBundle/Entity/EntityOne,
[1] /AppBundle/Entity/EntityTwo,
[2] /AppBundle/Entity/EntityThree
),
[1] => . . .
)
The results are not related by row. Nor are they in any predictable order that I can group them by with array_chunk()
The generated sql runs fine. The DQL returns accurate results -- they're just not formulated in a way that I would expect. I am following Doctrines (elusive) documentation on eager loading joins:
http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html
This question is very similar to
Doctrine DQL returns multiple types of entities
But my select order is different as my join tables are many-to-many.
Much thanks!
A variant of this question was asked:
Getting Doctrine DQL results the SQL way
I'm pretty surprised that doctrine does not support eager loading via join table.
Call getScalarResult() instead of getResult() on your query and you will get all fields of all joined tables merged into one array per record:
# Replace this call ...
$query->getQuery()->getResult();
# ... by that call ...
$query->getQuery()->getScalarResult();
Here is the best that I could come up with:
//in my entity repository:
public function getFoo($hydrate = true) {
$sql = "SELECT e1, e2, e3 FROM entity_one
JOIN entity_one_entity_two e1_2 ON e1_2.entity_one_id = e1.id
JOIN entity_two e2 ON e1_2.entity_two_id = e2.id
JOIN entity_one_entity_three e1_3 ON e1_3.entity_one_id = e1.id
JOIN entity_three e3 ON e3.id = e1_3.entity_three.id
WHERE e1.some_field IN ('some','values','in','e1')";
$stmt = $con->prepare($sql);
$stmt->execute();
return ($hydrate)
? $this->hydrateResponses($stmt->fetchAll(PDO::FETCH_ASSOC))
: $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function hyrdateResponses($responses) {
//call find by ids on repositories from array.
}
This works great because you're performing 3XN the number of queries when you're only trying to get results from one!
Related
Say, I want to have an alias table as my DB::table
(
SELECT A, B, C
FROM TABLE
WHERE D = 'xx'
) custom_table
How is it possible to convert this into Eloquent's DB::table?
I tried,
DB::table(
DB::select(
DB::raw(
SELECT * FROM (SELECT A, B, C FROM TABLE WHERE D = :param1) AS custom_table
),
[ "param1" => "xx" ]
)
)
But this seems to give me
ErrorException: Array to string conversion
Treat that as 2 different query builders and merge their binding like below :
$xx = 'xx';
$innerQuery = DB::select('A','B','C')->table('TABLE')->where('D', '=',$xx);
$mainQuery = DB::table(DB::raw('(' . $innerQuery->toSql() . ') as custom_table'))
->mergeBindings($innerQuery)
->groupBy('custom_table.C')
->get();
This will also help you retail the $innerQuery builder instance for your later use as you have mentioned in the question.
I am implementing category filters with many to many relations in doctrine using symfony3. I have an entity Business and Category with many to many association. The new table with many to many relations looks like below
business_id category_id
1 1
1 2
2 1
2 2
3 1
Now I want to get all the businesses which are having category_id=1 and category_id=2.
It should select the business id 1,2.
My Sql Query:-
SELECT * FROM business
LEFT JOIN business_category ON business_category.business_id=business.id
WHERE business_category.category_id = 1 AND business_category.category_id = 2
Any SQL or Doctrine query would work.
I would really appreciate for any help.
To get the businesses which exists in both categories your write your query builder as follows,I assume your entities are mapped with proper many to many relationship
$repo = $this->getDoctrine()->getRepository('YourBundle:Business');
$repo = $this->createQueryBuilder('b')
->addSelect('COUNT(DISTINCT c.id) AS total_categories')
->innerJoin('b.categories', 'c');
$categoryIds = array(1,2);
$repo->add('where', $qb->expr()->in('c', $categoryIds))
->groupBy('b.id')
->having('total_categories = '.count($categoryIds))
->getQuery()
->getResult();
For reference see another answer here
you can try
$qb->select( 'p' )
->from( 'AppBundle:Project', 'p' )
->innerJoin( 'p.users', 'u' )
->where( 'u.id=:userIdDaily' )
->andWhere('u.id=:UserID')
->setParameter( 'userIdDaily', $UserObj )
->setParameter( 'UserID', $UserObj->getId() )
;
$query = $qb->getQuery();
$results = $query->getResult();
i have project and user many to many relation. i use this to fetch data with multiple where clauses
i have two table, tutorial:id,title and tutorial_tags:id,tutorial_id,title
the relation in tutorial model is defined like this :
function TutorialTag(){
return $this->hasMany('App\TutorialTag');
}
i want to left join tutorials with tutorial_tags , like (please ignore syntax errors):
select tutorials.* , tutorial_tags.* from `tutorials` left Join
`tuotrial_tags` ON tutorials.id = tutorial_tags.tutorial_id
but i want to be able to use Conditions on tutorial_tags in case user want to search a particular tags:
select tutorials.* , tutorial_tags.* from `tutorials` left Join
`tuotrial_tags` ON tutorials.id = tutorial_tags.tutorial_id
where tutorial_tags.title = 'ABC'
if i use whereHas like this :
$tutorials = Tutorial::whereHas('TutorialTag',function ($query){
if(isset($_GET['tag']))
$query->where('title',$_GET['tag']);
})->get();
i dont get tutorials that are without any tag, basically it works like inner Join.
and if i use with :
$tutorials = Tutorial::with(['TutorialTag'=>function($query){
if(isset($_GET['tag']))
$query->where('title',$_GET['tag']);
}])->get();
then ill get two separate queries with no effect on eachother, basically the where condition on tutorial_tags has no effect on tutorials and i get all the tutorials even the ones without sreached tag, here is the query log :
Array
(
[0] => Array
(
[query] => select * from `tutorials`
[bindings] => Array
(
)
[time] => 0
)
[1] => Array
(
[query] => select * from `tutorial_tags` where `tutorial_tags`.`tutorial_id` in (?, ?, ?) and `title` = ?
[bindings] => Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => ABC
)
[time] => 2
)
)
how can i get left Join like query with optional condition on the right table
This is not an efficient sql query, but you can solve the issue with laravel collections and an extra query.
Grab all the intersections:
$tutorials1 = Tutorial::whereHas('TutorialTag',function ($query){
if(isset($_GET['tag']))
$query->where('title',$_GET['tag']);
})->get();
Grab the rest:
$tutorials2 = Tutorial::doesntHave('TutorialTag')->get();
Merge both collections:
$tutorials = $tutorials1->merge($tutorials2);
To get the left join query do the following
$tutorials = Tutorial::leftJoin('tutorial_tags', 'tutorials.id', '=', 'tutorial_tags.tutorial_id')
->where(function($query){
if(isset($_GET['tag']) {
$query->where('tutorial_tags.title' , $_GET['tag'];
}
});
This will give you the following outputs:
Case 1: If tag parameter was provided
SELECT * FROM tutorials LEFT JOIN tutorial_tags ON tutorials.id = tutorial_tags.tutorial_id WHERE tutorial_tags.title = 'ABC';
Case 2: When no tag parameter has been provided
SELECT * FROM tutorials LEFT JOIN tutorial_tags ON tutorials.id = tutorial_tags.tutorial_id;
The whereHas() method returns only those models where any relationship matches the condition in the closure. The with() method only returns relationships that match the condition in the closure. What you need to do is use them both. You can also use the when() method to filter on the boolean condition of the input value being filled.
$tag = $request->input('tag');
$tutorials = Tutorial->when($tag, function ($query) use ($tag) {
$query
->whereHas('tutorialTags', fn ($q) => $q->where('title', $tag)
->with(['tutorialTags' => fn ($q) => $q->where('title', $tag]);
})
->get();
You also should be following proper Laravel conventions that will make your life easier, especially when sharing code with others.
You should never see $_GET used in your code anywhere. I've removed it in favour of getting it from the request object, but it should properly be set as a route parameter. Relationship methods that return more than one model (such as a HasMany) should have plural names. Relationships names should be in camel case.
After spending a while I'm able to fix this with Lazy Eager Loading
$tutorials = Tutorial::all();
$tutorials->load(['TutorialTag' => function ($query) {
$query->where('title', $_GET['tag']);
}]);
I have a few tables that are joined through a distant relationship - for example:
A.id = B.a_id, B.id = C.b_id, C.id = D.c_id
And given A.id, I want to delete all the rows in D that are associated with A.id.
Since Model::deleteAll() does not accept any joins, only conditions, how do I go about it?
All the models (A, B, C, D) already have belongTo relationships defined.
My last resort would be raw SQL, but I would like to know if there's a way in CakePHP to do it.
I could not find similar questions as they all were about deleting ALL the associated data, rather than just one table's data via an associated key.
Use Containable behavior to find D records
public function deleteD($idA){
$this->ModelA->Behaviors->load('Containable');
$options = array(
'contain' => array(
'ModelB' => array(
'ModelC' = array(
'ModelD'
)
)
),
'conditions' => array('ModelA' => $idA)
);
$findDIds = $this->ModelA->find('all',$options);
debug($findDIds); // find right path to ModelD
$ids = Hash::extract($findDIds,'{n}.ModelD.id');
$this->loadModel('ModelD');
foreach($ids as $id){
$this->ModelD->delete($id);
}
}
Note, I not tested this function.
I have defined the following criteria in Yii, and tries to use it to fetch an array of customers.
$criteria = new CDbCriteria(array(
"condition"=>"hidden = 0".(Yii::app()->user->GetState('is_admin') ? "" : " AND franchisesMunicipalities.franchise_id=".Yii::app()->user->getState('fid')),
"with" => array('municipality','municipality.franchisesMunicipalities')
));
$customers = Customers::model()->findAll($criteria);
This (i thought) should result in that Yii joined the table Customers with the table Municipalities, and then in the same query join the table Municipalities with the table Franchises_Municipalities. However it doesn't work since it doesn't join franchisesMunicipalities.
The resulting query is this
SELECT `t`.`id` AS `t0_c0`,
`t`.`municipality_id` AS `t0_c1`,
`t`.`personal_code_number` AS `t0_c2`,
`t`.`name` AS `t0_c3`,
`t`.`adress` AS `t0_c4`,
`t`.`zip` AS `t0_c5`,
`t`.`phone` AS `t0_c6`,
`t`.`mobile` AS `t0_c7`,
`t`.`email` AS `t0_c8`,
`t`.`hidden` AS `t0_c9`,
`municipality`.`id` AS `t1_c0`,
`municipality`.`county_id` AS `t1_c1`,
`municipality`.`name` AS `t1_c2`
FROM `customers` `t`
LEFT OUTER JOIN `municipalities` `municipality`
ON ( `t`.`municipality_id` = `municipality`.`id` )
WHERE ( hidden = 0
AND municipality.franchisesmunicipalities.franchise_id = 7 )
LIMIT 30
As you can see it only joins on one relation. The relations in the model should be correctly defined, since i am able to use them in other contexts.
Why doesn't this work?
According to http://www.yiiframework.com/doc/api/1.1/CActiveRecord#with-detail I believe you should be doing it this way:
$criteria = new CDbCriteria(array(
"condition"=>"hidden = 0".(Yii::app()->user->GetState('is_admin') ? "" : " AND franchisesMunicipalities.franchise_id=".Yii::app()->user->getState('fid'))
));
$customers = Customers::model()->with('municipality','municipality.franchisesMunicipalities')->findAll($criteria);
Hopefully that works for you.