Using Criteria object in a query builder in Doctrine - symfony

I have 2 query builders. One for fetching all results (paginated) and one for getting the total count.
The criteria for these query will be nearly the same, so I want to use a Criteria object to just attach to the query builder afterwards.
$queryBuilder = $this->createQueryBuilder('vo');;
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq("vo.creator", $currentUser));
$criteria->where(Criteria::expr()->eq("vo.other", $something));
$queryBuilder->addCriteria($criteria);
The problem is: In the middle of a lot of ANDs I also have an OR. Just in a plain query builder it reads:
$criteria->andWhere('vo.public = :isPublic OR vo.offerID IN (:sharedOfferIDs)');
Using Laravel Eloquent, you could beautifully do that
->where('vo.creator', '=', $currentUser)
->orWhere(function ($query) {
query->where('vo.public ', '=', true)
->where('vo.offerID', 'in', [3, 4]');
})
To be even clearer, I want the result SQL to be something like:
where vo.creator = $currentUser AND (vo.public = true OR vo.offerID in [3, 4])
How can I archive this using Doctrine Criteria?

Something like this:
$array = [3, 4];
$queryBuilder = $this->createQueryBuilder('vo');
$queryBuilder->where('vo.creator = :creator')
->andWhere('vo.public = true OR vo.offerID IN (:array) ')
->setParameter('creator', $currentUser)
->setParameter('array', $array);

Related

Is there a correct way in Doctrine to nest 'OR' where clauses?

I have a query that pulls in some jobs if they have an end date and are not completed which produces the desired result:
real code
$this->createQueryBuilder('j')
->where('j.completed = :false OR j.completed is NULL')
->andWhere('j.has_due_date = :true')
->andWhere('j.has_due_date = :true')
->setParameters( array(
'false' => FALSE,
'true' => TRUE
) )
->orderBy('j.due_date', 'asc')
->getQuery();
result
SELECT j FROM App\Entity\Jobs j
WHERE (
j.completed = :false
OR j.completed is NULL
)
AND j.has_due_date = :true
ORDER BY j.due_date ASC
I'm wanting to follow the DQL best practice for this and feel like there is another way or writing this and only having a single WHERE clause per call on a ->where variant (andWhere(), orWhere())
In my mind it's something like this but I can't find anything out there to confirm it:
pseudo code
$query = $this->createQueryBuilder('j')
->where( $requiredClass->
->where('j.completed = :false')
->orWhere('j.completed is NULL')
->setParameter('false', FALSE)
)
->andWhere('j.has_due_date = :true')
->setParameter('true', TRUE)
->orderBy('j.due_date', 'asc')
->getQuery();
Main questions are:
Is there a way to do this the second way?
am I overcomplicating things unnecessarily?
if there is a second way to do this as mentioned, the first statement works so why should it matter?
Using the query builder should be easier and more understandable
public function someFunction()
{
$qb = $this->createQueryBuilder('j');
$orX = $qb->expr()->orX();
$andX = $qb->expr()->andX();
$orX->add($qb->expr()->eq('j.completed', $qb->expr()->literal(false)));
$orX->add($qb->expr()->isNull('j.completed'));
$andX->add($qb->expr()->eq('j.has_due_date', $qb->expr()->literal(true)));
$qb->andWhere($orX, $andX)
->orderBy('j.due_date', 'asc')
->getQuery()
->getResult();
}
As stated in the official documentation for QueryBuilder best practice - the right way is to use the helper method QueryBuilder::expr() and also the Doctrine\ORM\Query\Expr helper class. High level API methods
i.e.:
// create QueryBuilder
$qb = $this->createQueryBuilder('j');
// build conditions
$qb->where(
$qb->expr()->orX(
$qb->expr()->eq('j.completed', ':false'),
$qb->expr()->isNull('j.completed')
)
)
->andWhere(
$qb->expr()->eq('j.has_due_date', ':true')
)
->andWhere(
$qb->expr()->eq('j.has_due_date', ':true')
)
->setParameters(array(
'false' => FALSE,
'true' => TRUE
))
->orderBy('j.due_date', 'asc');
// get query
$query = $qb->getQuery();
// execute and get result
$result = $query->getResult();

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 add dynamically select aliases inside a query

Context: Given the fact that the following query :
$queryBuilder = $this->createQueryBuilder("cv")
->leftJoin('cv.user', 'u')
->where('cv.game = :game')
->setParameter('game', $game);
Will trigger 1+X distinct queries (one to get all the CV, then if u.user is used in the template, will trigger X other queries to fetch users).
If I want to optimize and to reduce those multiple unoptimized queries to 1 single query, i'll do so :
$queryBuilder = $this->createQueryBuilder("cv")
->select('cv, u')
->leftJoin('cv.user', 'u')
->where('cv.game = :game')
->setParameter('game', $game);
This Way, i'll be able to save X queries.
Now, my problem is in my repository, I have conditional joins and I want to chain the select aliases at different places in my code.
Like (simplified example) :
$queryBuilder = $this->createQueryBuilder("cv")
->select('cv, u')
->leftJoin('cv.user', 'u')
->where('cv.game = :game')
->setParameter('game', $game);
if ($myCondition === true) {
$queryBuilder->add('select', 'l');
$queryBuilder->join('cv.level', 'l');
}
But it seems that the add->('select') does not stack like an addWhere().
Are there any other solutions than using a custom solution like this :
$queryBuilder = $this->createQueryBuilder("cv")
->leftJoin('cv.user', 'u')
->where('cv.game = :game')
->setParameter('game', $game);
$aliases = array('cv', 'u');
if ($myCondition === true) {
$aliases[] = 'l';
$queryBuilder->add('select', 'l');
$queryBuilder->join('cv.level', 'l');
}
$queryBuilder->select(implode(',', $aliases);
Thanks.
// Replace
$queryBuilder->add('select', 'l');
// With
$queryBuilder->addSelect('l');
And a bit of unsolicited advice. I know how much "un optimized queries" bother most people, including myself. However, consider doing some bench marks on large data sets. It's surprising how fast lazy loading is. Very little difference even with thousands of queries.

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.

Convert SQL to LINQ query to get max

hi guys i am stuck converting below sql to LINQ query.
all i want is to have maximum number from list of (FA-00001 ,FA-00059)
SELECT MAX(CAST(SUBSTRING(ReferenceId, PATINDEX('%-%', ReferenceId) + 1, LEN(ReferenceId) - PATINDEX('%-%', ReferenceId)) AS int)) AS MaxReferenceId FROM [ClientRC].[dbo].[EHO_Action]
is this possible to convert to LINQ? thanks
An alternative approach using anonymous projection:
var y = (from record in
(from record in db.ClientRC
select new
{
Group = "x",
ReferenceNumber = Convert.ToInt32(record.ReferenceId.Split('-')[1])
})
group record by new { record.Group } into g
select new
{
MaxReferenceId = g.Max(p => p.ReferenceNumber)
});
http://msdn.microsoft.com/en-us/library/bb386972.aspx
var myvar = (from v in db.object where v!=null select v.id).Max();
MSDN has lots of examples for stuff like this.
Or, you can execute queries directly against a datacontext if you're using entity framework. Just make sure if you're doing anything with parameters you're parameterizing the query and not taking user input directly into it.
http://msdn.microsoft.com/en-us/library/ee358769.aspx
Try this..
var list = DBContext.EHO_Action
.Where(x => x.YourListColumn != null)
.Select(x => x.YourListColumn).ToList(); // Take the list (FA-00001 ,FA-00059) from db to a list
var maxNo = list.Max(x => Convert.ToInt32(x.Split('-')[1]));
Please change the context and column names according to your Linq context.
If you want to use sql you can do it this way..
var list = DBContext.ExecuteQuery<string>("select yourreqrdcolumn from [ClientRC].[dbo].[EHO_Action]");

Resources