Doctrine query works with SQLite, not with MySQL - symfony

I've created two entities called Project and ProjectTag using Doctrine (doctrine/orm v2.6.1) and Symfony. I'd like to select all projects that have all tags with the names given in $tags. The following query from my ProjectRepository.php works in my dev environment with SQLite:
public function findByAllTags($tags)
{
$queryBuilder = $this->createQueryBuilder('p');
for ($i = 0; $i < count($tags); $i++) {
$tagSubQueryBuilder = $this->createQueryBuilder("p$i")
->join('p.tags', "tags_joined_$i")
->andWhere("tags_joined_$i.name = ?$i");
$queryBuilder->andWhere(
$queryBuilder->expr()->exists($tagSubQueryBuilder->getDQL())
);
}
$queryBuilder->orderBy('p.timeCreated','DESC');
return $queryBuilder->getQuery()->setParameters($tags)->getResult();
}
This gets converted to
SELECT
p0_.id AS id_0,
p0_.name AS name_1
FROM
project p0_
WHERE
(
EXISTS (
SELECT
p1_.id
FROM
project p1_
INNER JOIN projects_tags p3_ ON p0_.id = p3_.project_id
INNER JOIN project_tag p2_ ON p2_.id = p3_.project_tag_id
WHERE
p2_.name = ?
)
)
ORDER BY
p0_.created DESC
However, in my production environment with MySQL I get the following error:
An exception occurred while executing 'SELECT p0_.id AS id_0, p0_.name AS name_1 FROM project p0_ WHERE (EXISTS (SELECT p1_.id FROM project p1_ INNER JOIN projects_tags p3_ ON p0_.id = p3_.project_id INNER JOIN project_tag p2_ ON p2_.id = p3_.project_tag_id WHERE p2_.name = ?)) ORDER BY p0_.created DESC' with params ["video"]:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'p0_.id' in 'on clause'

In response to your comment, this is an untested approach to your problem without nested queries:
public function findAllByTags($tags)
{
$qb = $this->createQueryBuilder('p');
$qb->join('p.tags', 't');
$qb->where($qb->expr()->in('t.name', ':tags'));
$qb->groupBy('p.id');
$qb->having($qb->expr()->eq('COUNT(t.id)', ':count'));
$qb->setParameter('tags', $tags);
$qb->setParameter('count', count($tags));
return $qb->getQuery()->getResult();
}
This assumes you pass the tag names as an array (or that the Tag class implements a __toString() method that returns the tags name).

Related

Symfony Doctrine DQL how to add SELECT in a SUM of a main SELECT

Actually, i would like to reproduce with Doctrine this regular SQL Query :
SELECT SUM(b.nb_places * (SELECT pricing FROM param p WHERE t.date BETWEEN p.from_date AND p.to_date)) as gains from booking b INNER JOIN tour t ON b.tour_id = t.id;
That compute total gains according prices between two dates.
I wrote this DQL in a Repository :
public function allBooking() {
$query = $this->manager->createQuery(
'SELECT
SUM(b.nbPlaces * SELECT p.pricing FROM \App\Entity\Param p WHERE t.date BETWEEN p.fromDate AND p.toDate)
FROM App\Entity\Booking b JOIN b.tour t'
);
return $query->getResult();
}
But running this query, i got :
[Syntax Error] line 0, col 24: Error: Expected Literal, got 'SELECT' (500 Internal Server Error)
How do i acheive this query with DQL or using QueryBuilder ?
Thx for help
You forgot about putting
SELECT p.pricing FROM \App\Entity\Param p WHERE t.date BETWEEN p.fromDate AND p.toDate
into additional brackets.
It should looks like this:
$query = $this->manager->createQuery(
'SELECT SUM(b.nbPlaces * (SELECT p.pricing FROM \App\Entity\Param p WHERE t.date BETWEEN p.fromDate AND p.toDate))
FROM App\Entity\Booking b JOIN b.tour t'
);

Symfony custom repository query giving error

I am really new at Symfony and I am trying to get used to query builder. At the moment I am trying to join three tables and the following MySQL query gives me the results I need when I run it in PhpMyAdmin
SELECT * FROM pe_users u
LEFT JOIN pe_apply a ON u.id = a.user
LEFT JOIN pe_offer o ON a.id = o.application
However when I move this inside a Symfony Repository method
namespace Ache\AdminBundle\Repository;
use Doctrine\ORM\EntityRepository;
class AdminRepository extends EntityRepository{
public function findAllAppByStatus(){
$query = $this->getEntityManager()->createQuery(
'SELECT * FROM pe_users u
LEFT JOIN pe_apply a ON u.id = a.user
LEFT JOIN pe_offer o ON a.id = o.application
');
try {
return $query->getSingleResult();
} catch (\Doctrine\ORM\NoResultException $e) {
return null;
}
}
}
I get the error
[Syntax Error] line 0, col 7: Error: Expected IdentificationVariable |
ScalarExpression | AggregateExpression | FunctionDeclaration |
PartialObjectExpression | "(" Subselect ")" | CaseExpression, got '*'
What does this error mean? what am I doing wrong?
UPDATE
The three entities I have are as following
UserBundle:User
CoreBundle:Apply
AdminBundle:Offer
User.id links with Apply.user and Offer.application links with Apply.id
You can still use raw sql with Symfony if you are comfortable with that
$conn = $this->getEntityManager()->getConnection();
$sql = "SELECT * FROM pe_users u LEFT JOIN pe_apply a ON u.id = a.user LEFT JOIN pe_offer o ON a.id = o.application WHERE u.id = a.user";
$stmt = $conn->prepare($sql);
$stmt->execute();
return $stmt->fetchAll();
I would do :
public function findAllAppByStatus(){
$qb = $this->createQueryBuilder('u')
->leftJoin('CoreBundle:Apply', 'a', 'WITH', 'a.user = u')
->leftJoin('AdminBundle:Offer', 'o', 'WITH', 'o.application = a')
->setMaxResults(1); // if you return only 1 result, you want to be sure only one (or none) result is fetched
return $qb->getQuery()->getOneOrNullResult();
if you want to return possibly many results, as the 'All' in the method name suggests, get rid of the ->setMaxResults() and use $qb->getQuery()->getResult();
see how the queryBuilder works with objects, not tables. Joins are built on entities and properties, not tables and field names.

Doctrine: ordering By Count on Associated entities

Goal: Ordering my entities on how ofter they are used on associated relations (oneToMany)
The problem: DQL, it somehow handles the FROM keyword is an entity or class.
The Exception thrown: QueryException: [Semantical Error] line 0 col 103 near 'FROM MyBundle:Entity Error: Class 'FROM' is not defined.
Here is a SQL query I wrote to test how to get the data. It works perfectly
SELECT en.id, (COUNT(DISTINCT ag.artist_id) + COUNT(DISTINCT rg.release_id) + COUNT(DISTINCT tg.track_id)) AS total
FROM myapp.entity AS en
LEFT JOIN myapp.other_one AS o1 ON o1.entity_id = en.id
LEFT JOIN myapp.other_two AS o2 ON o2.entity_id = en.id
GROUP BY en.id
ORDER BY total DESC ;
To get the data in symfony to hydrate to objects I try to use Doctrine Query Language in the EntityRepository like this:
/**
* Find Most Common Entities
*
* #return array
*/
public function findMostCommon()
{
$em = $this->getEntityManager();
$qb = $em->createQueryBuilder();
$qb
->select('en, (COUNT(DISTINCT en.other1) + COUNT(DISTINCT en.other2)) AS count')
->from('MarulaBundle:Entity', 'en')
->leftJoin('MyBundle:Other1', 'o1', 'WITH', 'o1.entity = en.id')
->leftJoin('MyBundle:Other2', 'o2', 'WITH', 'o2.entity = en.id')
->groupBy('en')
->orderBy('count', 'DESC')
;
return $qb->getQuery()->getResult();
}
Since it is possible in a SQL query. I was hoping it would work just as fine with DQL.
Has anyone experienced this error before? Is it even possible to achieve this with Doctrine, or is doctrine limited relating this issue?
OOPS: I should not use the keyword "count"
Solution:
->select('en, (COUNT(DISTINCT en.other1) + COUNT(DISTINCT en.other2)) AS HIDDEN orderCount')
note: adding the HIDDEN keyword helps to get only the entities back, which makes the hydration fit right in

Symfony2: Doctrine subquery in the WHERE clause including a LIMIT

I'm trying to convert this SQL into either DQL or whatever the query builder variant would look like.
select *
from project_release r
where (select s.title as status_name
from release_status_log l
left join release_status s
on l.release_status_id = s.id
where l.release_id = r.id
order by l.created_at desc
limit 1
) not in ('Complete', 'Closed')
;
From inside the repository class for the Release entity, I've tried this
return $this->getEntityManager()->createQuery("
select r.*
from MyBundle:Release r
where (select s.title
from MyBundle:ReleaseStatusLog l
join l.status s
where l.release = r
order by l.createdAt desc
limit 1
) IN ('Complete','Closed')
order by r.release_date ASC
limit 10
")->getArrayResult();
Which gives the error
[Syntax Error] line 0, col 265: Error: Expected
Doctrine\ORM\Query\Lexer::T_CLOSE_PARENTHESIS, got 'limit'
Which is referring to the limit 1 in the subquery.
So then I tried this
return $this
->createQueryBuilder('r')
->select('r.*')
->where("(select s.title
from MyBundle:ReleaseStatusLog l
join l.status s
where l.release = r
order by l.created_at desc
limit 1
) $inClause ('Complete', 'Closed')
")
->setMaxResults( $limit )
->orderBy('release_date', 'ASC')
->getQuery()
->getArrayResult()
;
Which gives the same error. How can I execute a subquery limited to 1 row per row in the parent query?
Symfony 2.0.15
Doctrine 2.1.7
PHP 5.3.3
MySQL 5.1.52
I have a solution for this now. I ended up falling back on the native query system with the result set mapping from the entity in questions.
It's not a great solution, but it works and until I see another solution, it's the only option with this type of WHERE clause.
Here's what my finder method looks like now
/**
* Finds Releases by their current status
*
* #param array $statuses White-list of status names
* #param boolean $blackList Treat $statuses as a black-list
* #param integer $limit Limit the number of results returned
* #param string $order Sort order, ASC or DESC
*
* #throws \InvalidArgumentException
*
* #return array <Release>
*/
public function findByCurrentStatus( array $statuses, $blackList=false, $limit=null, $order='ASC' )
{
if ( empty( $statuses ) )
{
throw new \InvalidArgumentException( "Must provide at least one status" );
}
$inClause = $blackList ? 'not in' : 'in';
$rsm = new ResultSetMappingBuilder($this->getEntityManager());
$rsm->addRootEntityFromClassMetadata('MyBundle:Release', 'r');
$SQL = "
select *
from project_release r
where (select s.title as status_name
from release_status_log l
left join release_status s
on l.release_status_id = s.id
where l.release_id = r.id
order by l.created_at desc
limit 1
) $inClause ('" . implode( "','", $statuses ) . "')
order by r.release_date $order
";
if ( $limit )
{
$SQL .= " limit $limit";
}
return $this
->getEntityManager()
->createNativeQuery( $SQL, $rsm )
->getResult()
;
}
I kind of loathed going back to building a query as a string, but oh well. Oh, and for you eagle-eyes, $statuses does not come from user data, so no SQL injection vulnerabilities here ;)
In addition to your Native SQL solution, you can create two queries using DQL within a single repository method.
Some adjustment may be required but you could try this:
public function findCompletedReleases()
{
$em = $this->getEntityManager();
$dqlSubQuery = <<<SQL
SELECT
s.title status_name
FROM
Acme\MyBundle\Entity\ReleaseStatus s,
Acme\MyBundle\Entity\ReleaseStatusLog l,
Acme\MyBundle\Entity\Release r
WHERE
l.release = r.id AND
l.status = s.id
ORDER BY l.createdAt DESC
SQL;
$statusName = $em->createQuery($dqlSubQuery)
->setMaxResults(1)
->getSingleScalarResult();
$dql = <<<SQL
SELECT
r
FROM
Acme\MyBundle\Entity\Release r
WHERE
:status_name IN ('Complete','Closed')
ORDER BY r.release_date ASC
SQL;
$q = $em->createQuery($dql)
->setParameters(array('status_name' => $statusName))
->setMaxResults(10);
return $q->getArrayResult();
}

Why is Doctrine joining this entity onto itself? (Not unique table/alias)

I have an entity named 'tile', with a manyToMany relationship with another entity named 'coins'.
/**
* #ORM\ManyToMany(targetEntity="Coin")
* #ORM\JoinTable(name="coin",
* joinColumns={#ORM\JoinColumn(name="tile_id", referencedColumnName="tile_id")},
* inverseJoinColumns={#ORM\JoinColumn(name="coin_id", referencedColumnName="coin_id")}
* )
**/
protected $coins;
I have a page that lists tiles and all of their associated coins. There can be a lot of coins and tiles on a page and using lazy loading can put 100-300 database queries on a single page. I am trying to avoid this by using a leftJoin.
public function getUserTiles($id)
{
$qb = $this->createQueryBuilder('b')
->select('b', 'z')
->leftJoin('b.coins', 'z')
->leftJoin('z.userInfo', 'u')
->add('where', "b.userId = ".$id." AND b.status = 'COMPLETED'")
->add('orderBy', "b.checkinDate DESC");
return $qb->getQuery()
->getResult();
}
This gives me the following error:
*[2/2] DBALException: An exception occurred while executing '
SELECT t0_.tile_id AS tile_id0, t0_.quilt_id AS quilt_id1, t0_.user_id AS user_id2, t0_.comment AS comment3, t0_.checkout_date AS checkout_date4, t0_.checkin_date AS checkin_date5, t0_.x AS x6, t0_.y AS y7, t0_.status AS status8, t0_.completed_neighbors AS completed_neighbors9, t0_.required_completed_neighbors AS required_completed_neighbors10, t0_.visible AS visible11, t0_.visible_date AS visible_date12, t0_.count_towards_coins AS count_towards_coins13, c1_.coin_id AS coin_id14, c1_.user_id AS user_id15, c1_.tile_id AS tile_id16, c1_.comment AS comment17, c1_.date_given AS date_given18, c1_.status AS status19, c1_.origin AS origin20, t0_.quilt_id AS quilt_id21, t0_.user_id AS user_id22, c1_.tile_id AS tile_id23, c1_.user_id AS user_id24
FROM tile t0_ **LEFT JOIN coin *c1_* ON t0_.tile_id = c1_.tile_id
LEFT JOIN coin *c1_* ON c1_.coin_id = c1_.coin_id**
LEFT JOIN user_info u2_ ON c1_.user_id = u2_.user_id
WHERE t0_.user_id = 14 AND t0_.status = 'COMPLETED'
ORDER BY t0_.checkin_date DESC':
SQLSTATE[42000]: Syntax error or access violation: 1066 Not unique table/alias: 'c1_'*
As you can see, it is trying to join the coin table twice, using the same alias. What am I doing wrong?
Try:
$this->createQueryBuilder('b')
->select('b')
->addSelect('z')
->leftJoin('b.coins', 'z')
->leftJoin('z.userInfo', 'u')
->where("b.userId = :id AND b.status = 'COMPLETED'")
->orderBy('b.checkinDate', 'DESC')
->setParameter('id', $id)
->getQuery('b.checkinDate DESC')
->getSingleResult();
I fixed this problem by changing the ManyToMany relationship to OneToMany as it should have been in the first place.

Resources