I'm trying to order the results of my query by whether or not they match my original entity on a property. I could do this easily in mySQL with the following query:
SELECT * FROM table
ORDER BY prop = 'value' DESC;
However, in Doctrine, when I attempt the following:
// $qb is an instance of query builder
$qb->select('e')
->from('Entity', 'e')
->orderBy('e.prop = :value', 'DESC')
->setParameter('value', 'value');
// grab values
I get a Doctrine syntax error, 'end of string'. I looked into creating a custom function, but that seems like overkill. I'm fairly new to Doctrine, is there a better way to do this?
Since Doctrine ORM 2.2, you can use the HIDDEN keyword and select additional fields, in this case with a CASE expression:
SELECT
e,
CASE WHEN e.prop = :value THEN 1 ELSE 0 END AS HIDDEN sortCondition
FROM
Entity e
ORDER BY
sortCondition DESC
As I struggeled a while to figure out how to create that query using php syntax here's what I came up with:
$value = 'my-value';
$qb->select('e')
->from('Entity', 'e')
->addSelect('CASE WHEN e.prop = :value THEN 1 ELSE 0 END AS HIDDEN sortCondition')
->setParameter('value', $value)
->addOrderBy('sortCondition', 'DESC');
Related
Is it possible to apply criteria >= condition in Doctrine findBy. I have a query like this:
select * from source where id >= 10
I am looking to apply in following way which will return with reference to Object source.
$this->entityManager->getRepository(Source::class)->findBy(['id' ])
I can use createQueryBuilder but it returns me array result. I need result in same format as above:
$qb = $this->createQueryBuilder('s')
->andWhere('s.id >= :id')
->setParameter('id', 10);
Expected Result:
0 => App\Entity\Source {#845
-id: 10
Can anybody help me?
Thanks in advance.
You could use the repository's matching method.
$criteria = Criteria::create()
->andWhere(Criteria::expr()->gte('id', 10));
$repository->matching($criteria);
Be aware that doctrine can not always hydrate your database data on Entities, when using custom queries. The result is that it only returns array data.
I am trying to put a conditional parameter in my query. if orgID is null i do not want the query to use it as a filter.
AND (:orgID IS NULL OR o.id = :orgID)
I am struggling trying to do this. What am i doing wrong?
$em = $this->getEntityManager();
$query = $em->createQuery('
select count(d.id)
from \***\OrganizationBundle\Entity\Organization o
inner join \***\OrganizationBundle\Entity\Facility f
inner join \***\OrganizationBundle\Entity\Department d
where d.active = true
AND f.active = true
AND o.active = true
AND d.name LIKE :name
AND (:orgID IS NULL OR o.id = :orgID)
');
$query->setParameter(':name', '%' . $name . '%');
$query->setParameter(':orgID', $orgID);
$count = $query->getSingleScalarResult();
Both #Cerad and #xabbuh from comment section are right. Either skip appending the predicate or use QueryBuilder to conditionally make your query string.
In any case, remember that prepared statements placeholders cannot be used for table names and columns.
Also, from performance standpoint, you need not to run a query if you know for a fact that it will return previously known results. That's just waste of resources.
I agree with what others have said - use QueryBuilder as this is the Symfony / Doctrine way.
But if you want to solve it on the MySQL query level, then you will need something like "conditional WHERE clause", for example using CASE expression.
Replace
AND (:orgID IS NULL OR o.id = :orgID)
with
AND
CASE
WHEN :orgID IS NULL THEN TRUE
ELSE o.id = :orgID
END
The first condition explicitly evaluates to TRUE, which is essentially equal to your requirement: "I do not want the query to use it as a filter". (Although technically this is still a filter).
I was wondering what is the best practice for fetching collections by complex queries in Doctrine 2. I have been using the Cirteria matching functionality, but I want to know if it is usable for large scale databases.
$expr = Criteria::expr();
$criteria = Criteria::create();
$criteria->where(
$expr->andX(
$expr->gte('start', $start),
$expr->lte('end', $end)
)
);
$result = (new ArrayCollection($em->getRepository(Entity::class)->findAll())->matching($criteria);
What is the difference in performace with same filter written in DQL.
$qb = $em->createQueryBuilder();
$qb->select('e')
->from('Entity', 'e')
->where('e.start >= :start')
->andWhere('e.end <= :end')
->setParameters(array('start' => $start, 'end' => $end));
return $qb->getQuery()->getArrayResult();
And a native SQL one.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('Entity', 'e');
$query = $this->_em->createNativeQuery('SELECT * FROM Entity WHERE start >= ? AND end <= ?', $rsm);
$query->setParameter(1, $start);
$query->setParameter(2, $end);
$result = $query->getResult();
I really like the criteria one as it is easy to read and easy to maintain. But is it usable? How does the performace suffer when fetching 1 000, 10 000 or even 100 000 records.
The second question is does the Doctrine Criteria fetches all result and then filters them or does it create the specific query first?
The docs says this, but does it apply to my case?
"Collections have a filtering API that allows to slice parts of data from a collection. If the collection has not been loaded from the database yet, the filtering API can work on the SQL level to make optimized access to large collections."
Doctrine 2 Criteria documentation
The problem is that you are using Criteria to match records after calling findAll() in your Repo. You should use criteria direct over your Repo, not to the array collection.
$criteria = new Criteria();
$criteria->where(Criteria::expr()->gte('legajo', $this->legajoDesde));
$criteria->andWhere(Criteria::expr()->lte('legajo', $this->legajoHasta));
$criteria->andWhere(Criteria::expr()->eq('idOrganizacion', $this->idOrganizacion));
$criteria->orderBy(['legajo' => Criteria::ASC]);
$empleados = $empleadoRepository->matching($criteria);
I regularly come across a scenario, where I want to query an entity with a specific value:
$query = $em->createQuery('SELECT e FROM Entity e WHERE e.parent = :parent');
$query->setParameter('parent', $parent);
Often, this value can be NULL, but WHERE e.parent = NULL yields no results, forcing me to hack around like this:
if ($parent === null) {
$query = $em->createQuery('SELECT e FROM Entity e WHERE e.parent = IS NULL');
}
else {
$query = $em->createQuery('SELECT e FROM Entity e WHERE e.parent = :parent');
$query->setParameter('parent', $parent);
}
While I understand the rationale behind NULL != NULL in SQL / DQL, the fact is, the consequence is really annoying in this case.
Is there a cleaner way to perform this query, when the parameter can be null?
It's not possible at the moment. (Tried for myself just several ways).
bindValue() with null only works for INSERT/UPDATE value binding.
I think the limitation is in PDO or SQL Syntax itself and not Doctrine.
You can use the QueryBuilder, so you only need to "duplicate" the WHERE part, instead of the whole query: http://doctrine-dbal.readthedocs.org/en/latest/reference/query-builder.html#where-clause
EDIT: It's possible in native SQL: https://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#operator_equal-to
Sadly doctrine does not have that operator: http://doctrine1-formerly-known-as-doctrine.readthedocs.org/en/latest/en/manual/dql-doctrine-query-language.html#operators-and-operator-precedence
You can use query builder:
$em = \Zend_Registry::get('em');
$qb_1 = $em->createQueryBuilder();
$q_1 = $qb_1->select('usr')
->from( '\Entities\user', 'usr' )
->where( 'usr.deleted_at IS NULL' )
->orWhere( 'usr.status='.self::USER_STATUS_ACTIVE )
->andWhere('usr.account_closed_on is null');
$q_1->getQuery()->getResult();
Was just trying to solve the same issue and I actually found a solution for PostgreSQL.
$sql = 'SELECT * FROM company WHERE COALESCE(:company_id, NULL) ISNULL OR id = :company_id;'
$connection->executeQuery($sql, ['company_id' => $id]);
Based on given $id it returns the particular company or all, if null is passed. The problem seems to be that the parser does not know what you are actually going to do with the argument, so by passing it in to COALESCE function it knows that it is passing it into a function, so problem solved.
So if it started working in the "pure" SQL solution, using it inside the DQL should not be a problem. I didn't try that but Doctrine should have COALESCE support so the DQL should be easy to update.
I want to use DQL to create a query which looks like this in SQL:
select
e.*
from
e
inner join (
select
uuid, max(locale) as locale
from
e
where
locale = 'nl_NL' or
locale = 'nl'
group by
uuid
) as e_ on e.uuid = e_.uuid and e.locale = e_.locale
I tried to use QueryBuilder to generate the query and subquery. I think they do the right thing by them selves but I can't combine them in the join statement. Does anybody now if this is possible with DQL? I can't use native SQL because I want to return real objects and I don't know for which object this query is run (I only know the base class which have the uuid and locale property).
$subQueryBuilder = $this->_em->createQueryBuilder();
$subQueryBuilder
->addSelect('e.uuid, max(e.locale) as locale')
->from($this->_entityName, 'e')
->where($subQueryBuilder->expr()->in('e.locale', $localeCriteria))
->groupBy('e.uuid');
$queryBuilder = $this->_em->createQueryBuilder();
$queryBuilder
->addSelect('e')
->from($this->_entityName, 'e')
->join('('.$subQueryBuilder.') as', 'e_')
->where('e.uuid = e_.uuid')
->andWhere('e.locale = e_.locale');
You cannot put a subquery in the FROM clause of your DQL.
I will assume that your PK is {uuid, locale}, as of discussion with you on IRC. Since you also have two different columns in your query, this can become ugly.
What you can do is putting it into the WHERE clause:
select
e
from
MyEntity e
WHERE
e.uuid IN (
select
e2.uuid
from
MyEntity e2
where
e2.locale IN (:selectedLocales)
group by
e2.uuid
)
AND e.locale IN (
select
max(e3.locale) as locale
from
MyEntity e3
where
e3.locale IN (:selectedLocales)
group by
e3.uuid
)
Please note that I used a comparison against a (non empty) array of locales that you bind to to the :selectedLocales. This is to avoid destroying the query cache if you want to match against additional locales.
I also wouldn't suggest building this with the query builder if there's no real advantage in doing so since it will just make it simpler to break the query cache if you add conditionals dynamically (also, it's 3 query builders involved!)