Doctrine - querybuilder - how to make to results from "join" query were not alternately? - symfony

I have to table Stats and Stat_values. These tables are in relatioship many to one (A stat can have a lot of stat_value)
I created query via querybuilder:
return $this->getEntityManager()
->createQueryBuilder()
->select(array('s', 'v'))
->from("CMailingDefaultBundle:Stat", "s")
->leftJoin("CMailingDefaultBundle:StatValue", "v")
->where("s.project = :project")
->andWhere("v.isCurrent = 1")
->setParameter("project", $project )
->getQuery()
->getResult();
It works good, but I don't have result in this way (I make it simpler, because all structure of array is so big):
[0] => Stats.field1, Stats.field2, ..., Stat_values.field1, Stat_values, ...
[1] => Stats.field1, Stats.field2, ..., Stat_values.field1, Stat_values, ...
etc...
but I have:
[0] => Stats.field1, Stats.field2, ...
[1] => Stat_values.field1, Stat_values ...
etc...
It is "litle bit" annoying. I tried change select arguments to "s, v" - the results are the same.
Do you have any ideas how to make to datas was ordered in first way?

Could it be because you're not requesting the relation, just a seperate entity?
Instead of:
->from("CMailingDefaultBundle:Stat", "s")
->leftJoin("CMailingDefaultBundle:StatValue", "v")
Try:
->from("CMailingDefaultBundle:Stat", "s")
->leftJoin("s.StatValue", "v")
Assuming the Stat entity has a relation called StatValue...

You can use another hydration method.
Doctrine provides 5 hydration methods, as listed below:
/**
* Hydrates an object graph. This is the default behavior.
*/
const HYDRATE_OBJECT = 1;
/**
* Hydrates an array graph.
*/
const HYDRATE_ARRAY = 2;
/**
* Hydrates a flat, rectangular result set with scalar values.
*/
const HYDRATE_SCALAR = 3;
/**
* Hydrates a single scalar value.
*/
const HYDRATE_SINGLE_SCALAR = 4;
/**
* Very simple object hydrator (optimized for performance).
*/
const HYDRATE_SIMPLEOBJECT = 5;
HYDRATE_SCALAR gives you what you are looking for.
So change the call of: getResult(); to getResult(3); or a little nicer by using the actual constant name: getResult(AbstractQuery::HYDRATE_SCALAR)

Related

Doctrine ORM many-to-many find all by taglist

I'v got simple m2m relation (book -> book_mark <- mark). I want to find item(book) by 1-2-3... x-count of tags(marks). Example: Book1 got these tags: [Mark1, Mark2, Mark3], Book2 got these tags: [Mark1, Mark3, Mark4]. Search list is [Mark1, Mark2]. I want to find only items, which have ALL tags from Search list, i.e. only Book1 in this example.
I have tried many ways and spend much time google it, but didn't find the answer.
Closest that I have is this:
return $this->createQueryBuilder('b')
->select('b, m')
->leftJoin('b.marks_list', 'm')
->andWhere(':marks_list MEMBER OF b.marks_list')
->setParameter('marks_list', $marksList)
->getQuery()->getArrayResult();
But it's looking for books which have at least 1of the parameters, not all of them together
Next, I'v decided that I'm absolutely wrong and start thinking this way:
public function findAllByMarksList(array $marksList)
{
$qb = $this->createQueryBuilder('b')
->select('b, m')
->leftJoin('b.marks_list', 'm');
for ($i = 0; $i<count($marksList); $i++){
$qb->andWhere('m.id in (:mark'.$i.')')
->setParameter('mark'.$i, $marksList[$i]);
}
return $qb->getQuery()->getArrayResult();
}
But here I faced another problem: This code is checking only 1 mark and then always returns an empty set if the number of parameters is more than 1.
Best regards.
So, updated answer... It works (I have relation many to many between reviews and brands) it's the same situation, but for example if you have
Book 1 - mark1, mark2, mark3
Book 2 - mark1, mark2, mark3, mark4
with this code you will also find the book2, because all of it marks are in this list.
If you need to find book which haves only these 3 tags, you additionally need to add checking for count. (Count of tags = count of tagList)
public function test()
{
// just selecting for list for test
$brandsList = $this->_em->createQueryBuilder()
->select('b')
->from('ReviewsAdminBundle:Brands', 'b')
->where('b.id in (:brandIds)')
->setParameter('brandIds', [6,4])
->getQuery()
->getResult();
dump($brandsList);
// query part
$qb = $this->createQueryBuilder('r')
->select('r')
->leftJoin('r.brand', 'brands')
->where('r.published = 1');
foreach ($brandsList as $oneBrand) {
/** #var Brands $oneBrand */
$identifier = $oneBrand->getId();
$qb->andWhere(':brand'.$identifier.' MEMBER OF r.brand')
->setParameter('brand'.$identifier, $identifier);
}
dump($qb->getQuery()->getResult());
die;
}
ps. Additionally you can check doctrine2 queryBuilder must return only result matching with array values (ids): 0/Null and/or One and/or Many id(s) have to return One result (close to our situation)
And, I think there's not better way of accomplishing this. Either you use multiple andWheres to compare id OR use MEMBER OF

Symfony 3 one-to-many, get parent with all children if one child satisfy the codition

I have these two tables
I want to get all products(with all the children) that have at least one child with log_id = 13. Let's say I have the following rows in eorder_product_config table:
The function that retrieves the products looks like this:
public function getProducts($logId){
$q = $this
->createQueryBuilder('p')
->select('p', 'pc')
->where('pc.logisticStatus = :logId')
->setParameter('logId', $logId)
->getQuery();
return $q->getResult();
}
This will get the product(id = 18) with only 2 children(id = 46,48) in the productConfigs collection and I want have all 5 children if there is at least one that has log_id = 13.
I've found a workaround using subqueries:
public function getProducts($logId){
// search for configs that have log_id = 13
$subQuery = $this->createQueryBuilder('pp')
->select('DISTINCT pp.id')
->leftJoin('pp.productConfigs', 'ppc')
->leftJoin('ppc.logisticStatus', 'pls')
->where('ppc.logisticStatus = :logId');
//->andWhere('ppc.id = p.id'); // used for EXIST query method
// main query
$q = $this->createQueryBuilder('p');
$q->select('p', 'pc');
$q->leftJoin('p.productConfigs', 'pc')
// inject subquery, check to see if current product is in
// the subquery result
$q->where($q->expr()->in('p.id', $subQuery->getDQL()));
//$q->where($q->expr()->exists($subQuery->getDQL()))
$q->setParameter('logId', $logId);
return $q->getQuery()->getResult();
}
***I've seen that using the EXIST query does't work as it should that's why I choose the IN query. But in the raw sql query they both return same results.

How to save row order which is pointed in "IN()" expression?

I have a array of entity IDs - my task is fetch that entities in the order which is pointed in the array. I have found that combination of "IN()" and "FIND_IN_SET" can solve that task.
I built a query in repository class with the help of QueryBuilder:
$qb = $this->createQueryBuilder('v');
$qb
->select('v')
->addSelect("FIND_IN_SET('v.id', '$vehiclesStr')")
->andWhere('v.id IN(:vehicles)')
->setParameter('vehicles', $vehiclesArr)
;
return $qb->getQuery()->getResult();
As you see, I use "FIND_IN_SET" function from beberlei/DoctrineExtensions. It was registered accordingly to that issue.
Suppose $vehiclesStr = '219,197,213,198'; and respectively
$vehiclesArr = [219,197,213,198];
The problem - order is not saving. I receive following result, where vehicles are ordered by ASC e.g. 197, 198, 213..:
UPDATE / built SQL by Doctrine:
SELECT
v0_.vehicle_id AS vehicle_id_0,
v0_.number AS number_1,
v0_.cargo_movers AS cargo_movers_2,
v0_.vat AS vat_3,
v0_.bargain AS bargain_4,
v0_.cargo_search_radius AS cargo_search_radius_5,
v0_.adr AS adr_6,
v0_.tir AS tir_7,
v0_.passing_cargo AS passing_cargo_8,
v0_.description AS description_9,
v0_.created_at AS created_at_10,
v0_.updated_at AS updated_at_11,
v0_.transport_service_id AS transport_service_id_12,
v0_.vehicle_photo_id AS vehicle_photo_id_13,
v0_.vehicle_driver_id AS vehicle_driver_id_14,
v0_.vehicle_type_id AS vehicle_type_id_15,
v0_.vehicle_body_size_id AS vehicle_body_size_id_16,
v0_.vehicle_full_size_id AS vehicle_full_size_id_17,
v0_.vehicle_show_to_cargo_sender_id AS vehicle_show_to_cargo_sender_id_18,
v0_.vehicle_body_equipment_id AS vehicle_body_equipment_id_19,
v0_.vehicle_loading_type_id AS vehicle_loading_type_id_20,
v0_.vehicle_price_around_town_id AS vehicle_price_around_town_id_21,
v0_.vehicle_price_out_of_town_id AS vehicle_price_out_of_town_id_22
FROM
vehicle v0_
WHERE
v0_.vehicle_id IN (?)
ORDER BY
FIND_IN_SET('v.id', '219,188') ASC
Parameters: [[219, 188]]
You're not using ORDER BY clause at all. Putting additional field in SELECT clause won't change order.WHy would it?
Try to use orderBy instead of addSelect.
$qb = $this->createQueryBuilder('v');
$qb
->select('v')
->andWhere('v.id IN(:vehicles)')
->setParameter('vehicles', $vehiclesArr)
->orderBy("FIND_IN_SET(v.id, '$vehiclesStr')", 'ASC');
;
return $qb->getQuery()->getResult();
Assuming $vehiclesStr has correct value of course.

Workaround or solution needed on how to perform a Symfony Repository call with two selects, whereby the second gets cast to an object?

I am trying to perform a left outer join for objects called Data, by putting a condition (status = 3) on the join table JoinData and then left joining the Data.
class JoinDataRepository extends EntityRepository
{
public function getDataWithStatusEqualsThree()
{
$qb = $this->createQueryBuilder('jd')
->select('jd','d')
->where('jd.status = 3')
->leftJoin('jd.data', 'd')
->addOrderBy('d.created', 'DESC');
return $qb->getQuery()
->getResult();
}
}
The result looks like this - having attributes of the two objects selected 1) the JoinData and 2) the Data attributes (which i actually would like to have as object):
SELECT
n0_.status AS status0
...
n1.attribute AS attribute0
...
FROM
ngl2_join_data n0_
LEFT JOIN ngl2_data n1_ ON n0_.data_id = n1_.id
WHERE
n0_.status = 2
ORDER BY
n1_.created DESC
The method returns a list of JoinData objects. Is it possible to return just Data objects? Or is there a workaround just using the QueryManager...?
What about this:
class JoinDataRepository extends EntityRepository
{
public function getDataWithStatusEqualsThree()
{
$qb = $this->createQueryBuilder('jd')
->select('d')
->where('jd.status = 3')
->leftJoin('jd.data', 'd')
->addOrderBy('d.created', 'DESC');
return $qb->getQuery()
->getResult();
}
}

Call to undefined method Slice in Doctrine

I have this function,
public function getWall(){
$q = $this->createQueryBuilder('f');
$q->leftJoin("f.profilo", 'p');
$q->leftJoin("p.utente", 'u');
$q->where('(f.foto_eliminata IS NULL OR f.foto_eliminata != 1)');
$q->andWhere('p.fase_registrazione = :fase');
$q->andWhere('u.locked = :false');
$q->slice(0, 20);
$q->setParameter(':fase', 100);
$q->setParameter('false', false);
$q->orderBy('f.created_at', 'desc');
$dql = $q->getQuery();
$results = $dql->execute();
return $results;
}
but I get this error,
Call to undefined method Doctrine\ORM\QueryBuilder::slice()
Ok, so, u get this error, cause QueryBuilder has no such method. But Collection has. If u want to use slice, a possible variant is:
use Doctrine\Common\Collections;
public function getWall(){
$result = $this->createQueryBuilder('f')
->leftJoin("f.profilo", 'p')
->leftJoin("p.utente", 'u')
->where('(f.foto_eliminata IS NULL OR f.foto_eliminata != 1)')
->andWhere('p.fase_registrazione = :fase')
->andWhere('u.locked = :false')
->setParameter('fase', 100)
->setParameter('false', false)
->orderBy('f.created_at', 'desc')
->getQuery()
->getResult();
// $result typed as array
return new Collections\ArrayCollection($result))->slice(0,20); // convert array to collection, then slice
}
By the way, it is not a good idea to 'limit' result of the query in a such way.
U can use setMaxResults(20), and not to select all objects at all.
About lazy collections (http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html): after selecting result objects, u can take some object from result collection: $r = $result[0] after that:
$portfilos = $r->getPortfolio(); // returns for example Collection with some objects;
// its Lazy, without SQL query!
$portfolios->slice(0, 20); // queries first 20 potfolios
To use slice is a rather good idea, if u have lots of objects in some relation.
p.s. sry, mb I didn't recognized your problem, but tried :)
EDITED
fixed errors in code.

Resources