Symfony 2: INNER JOIN on non related table with doctrine query builder - php

I'm trying to build a query with the doctrine query builder which joins a non related table like this:
$query = $this->createQueryBuilder('gpr')
->select('gpr, p')
->innerJoin('TPost', 'p')
->where('gpr.contentId = p.contentId')
But this doesn't work. I still get an error:
Error: Identification Variable TPost used in join path expression but was not defined before.
I searched for this error message and everybody answered to use the table alias + attribute like p.someAttribute. But the table I want to join isn't related in the table I start my select from.
As a normal mysql query i would write it like this:
SELECT * FROM t_group_publication_rel gpr
INNER JOIN t_post p
WHERE gpr.content_id = p.content_id
Any ideas what i'm doing wrong?

Today I was working on similar task and remembered that I opened this issue. I don't know since which doctrine version it's working but right now you can easily join the child classes in inheritance mapping. So a query like this is working without any problem:
$query = $this->createQueryBuilder('c')
->select('c')
->leftJoin('MyBundleName:ChildOne', 'co', 'WITH', 'co.id = c.id')
->leftJoin('MyBundleName:ChildTwo', 'ct', 'WITH', 'ct.id = c.id')
->orderBy('c.createdAt', 'DESC')
->where('co.group = :group OR ct.group = :group')
->setParameter('group', $group)
->setMaxResults(20);
I start the query in my parent class which is using inheritance mapping. In my previous post it was a different starting point but the same issue if I remember right.
Because it was a big problem when I started this issue I think it could be also interesting for other people which don't know about it.

Joins between entities without associations were not possible until version 2.4, where you can generate an arbitrary join with the following syntax:
$query = $em->createQuery('SELECT u FROM User u JOIN Blacklist b WITH u.email = b.email');
Reference: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html

Want to improve this post? Add citations from reputable sources by editing the post. Posts with unsourced content may be edited or deleted.
$dql = "SELECT
a, md.fisrtName , md.LastName, mj
FROM MembersBundle:Memberdata md
INNER JOIN MembersBundle:Address a WITH md = a.empID
INNER JOIN MembersBundle:Memberjob mj WITH md = mj.memberData
...
WHERE
a.dateOfChange IS NULL
AND WHERE
md.someField = 'SomeValue'";
return $em->createQuery( $dql )->getResult();

A DQL join only works if an association is defined in your mapping. In your case, I'd say it's much easier to do a native query and use ResultSetMapping to populate your objects.

Related

Why is my result array with a hydrated associated entity shaped differently using query builder vs createQuery?

Doctrine's documentation describes two ways to select a record and its relation, one with using a "fetch join" and one not:
Fetch join
Fetch join of the address:
<?php
$query = $em->createQuery("SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin'");
$users = $query->getResult();
When Doctrine hydrates a query with fetch-join it returns the class in the FROM clause on the root level of the result array. In the previous example an array of User instances is returned and the address of each user is fetched and hydrated into the User#address variable. If you access the address Doctrine does not need to lazy load the association with another query.
The other way
Retrieve a ForumUser and its single associated entity:
<?php
$query = $em->createQuery('SELECT u, a FROM ForumUser u JOIN u.avatar a');
$users = $query->getResult(); // array of ForumUser objects with the avatar association loaded
echo get_class($users[0]->getAvatar());
My question
When I use the query builder it performs like a fetch join and I receive an array with all users and the associations in its root. Like:
$qb = $this->em->createQueryBuilder()
->select('u')
->from(User::class, 'u');
$qb->join(Address::class, 'a', Join::WITH, 'u.a = a');
$qb->addSelect('a');
// Returns
$result = [
'user' => // User
'address' => // Address
];
The User object does also have the Address hydrated, as expected.
However I don't want this, I want it shaped like the second example: an array of just Users, with the Address only accesible via the User object. I could simply filter the array to get the desired shape, but I feel like that's a workaround due to my lack of understanding.
Is it possible to use the query builder to get a result array shaped like the second example above? Or do I have to filter the result array for that? The confusing part for me is that the actual SQL generated by both is identical!
I come from Laravel and Doctrine is brand new to me, so please forgive my naivety on the subject. If I need to clarify anything, just let me know.
Thanks!
I think I found my answer in the related questions section: Symfony2 query builder joins to get objects as a single array
Instead of joining with the fully qualified names of the entities you should join with the association. So the query becomes:
SELECT hs,s,h
FROM \App\SoBundle\Entity\HandsetSubscription hs
JOIN hs.subscription s with s.id = hs.subscription
AND s.mins = 150
AND s.mb = 250
AND s.sms = 150
JOIN hs.handset h with h.id = hs.handset

Laravel 5. Using the USING operator

I tried to find it for a long time, and I can't believe that Laravel doesn't have this functionality.
So, I can write:
select * from a join b where a.id = b.id
or more beautiful:
select * from a join b using(id)
First case is simple for Laravel:
$query->leftJoin('b', 'a.id', '=', 'b.id')
But how to write second case? I expect that it should be simple and short, like:
$query->joinUsing('b', 'id')
But thereis no such method and I can't find it.
PS: it's possible that the answer is very simple, it's just hard to find by word "using", because it's everywhere.
UPDATE
I'm going deeper to source, trying to make scope or pass a function to join, but even inside of this function I can't to anything with this $query. Example:
public function scopeJoinUsing($query, $table, $field) {
sql($query->join(\DB::raw("USING(`{$field}`)")));
// return
// inner join `b` on USING(`id`) ``
// yes, with "on" and with empty quotes
sql($query->addSelect(\DB::raw("USING(`{$field}`)")));
// return
// inner join `b` USING(`id`)
// but in fields place, before "FROM", which is very logic :)
}
So even if forget about scope , I can't do this in DB::raw() , so it's impossible... First time I see that something impossible in Laravel.
So, the answer is - it's impossible.
Laravel doesn't support this functionality, which is really sad.
I fix it in Laravel source code, my PULL REQUEST here - https://github.com/laravel/framework/pull/12773
Nothing is impossible in Laravel.
Until support for join using is added to Laravel core, you can add support for it by installing this Laravel package: https://github.com/nihilsen/laravel-join-using.
It works like this:
use Illuminate\Support\Facades\DB;
use Nihilsen\LaravelJoinUsing\JoinUsingClause;
DB::table('users')->leftJoin(
'clients',
fn (JoinUsingClause $join) => $join->using('email', 'name')
);
// select * from `users` left join `clients` using (`email`, `name`)

Issue in Laravel 5.1 Inner Join Query

Below is my Query in Laravel 5.1
\App\Models\Project\Bids\ProjectBid_Model
::selectRaw('B.*')
->join('tblproject P','B.projectid','=','P.projectid')
->where('P.WhoCreatedTheProject',14)
->first()
and below is the equivalant query
select B.* from `tblprojectbid`
inner join `tblproject P` on `B`.`projectid` = `P`.`projectid`
where `P`.`WhoCreatedTheProject` = 14 limit 1
What's the problem ?
Please check the line 1 in Query: select B.* from tblprojectbid.
What's the question ?
How can I change
select B.* from tblprojectbid
to
select B.* from tblprojectbid B
If you want to use Eloquent I'm afraid there is no easy way to do it.
I use in this case full table name for model for instance
\App\Models\Project\Bids\ProjectBid_Model
::selectRaw('bid_table.*')
->join('tblproject AS P','bid_table.projectid','=','P.projectid')
->where('P.WhoCreatedTheProject',14)
->first()
However it's also possible that you set alias in ProjectBid_Model:
protected $table = 'bid_table AS B';
The con is you will have this table always aliased with B, so in case you have 2 models with same alias (in this case B), you won't be able to change it later just for one table, so I think the better is 1st approach (without using alias)
Here is the final solution.
\App\Models\Project\Bids\ProjectBid_Model
::selectRaw('B.*')
->from('tblprojectbid as B')
->join('tblproject as P','B.projectid','=','P.projectid')
->where('P.WhoCreatedTheProject',14)
->first()
try this.
\DB::table('tblprojectbid as B')
->select()
->join('tblproject as P','B.projectid','=','P.projectid')
->where('P.WhoCreatedTheProject',14)
->first()

Symfony Doctrine, select from subquery with join

Haven't been able to find a solid solution for this, but I have a mySQL query that I want to translate to Doctrine. It is a select from a subquery with joins and I might have read somewhere that joins are not allowed in subqueries in Doctrine.
Here is the SQL:
SELECT part, SUM(qty) as qty FROM (SELECT part, SUM(qty) as qty FROM sub LEFT JOIN main ON main.id = main_id WHERE hold != 1 GROUP BY name, part) AS tbl GROUP BY part
This is what I tried and it is all wrong.
$em = $this->getDoctrine()->getManager();
$q = $em->createQuery('v');
$q2 = $em->createSubQuery()
->select('m.part, sum(s.qty) qty')
->from('Sub s')
->leftJoin('s.main m')
->where('s.hold != 1')
->groupBy('m.part');
$q->select('m.part, sum(qty)', $q2->getDQL());
One of the first error I got was:
FatalErrorException: Error: Call to undefined method Doctrine\ORM\EntityManager::createSubQuery() in ....Controller.php line 238
I am pretty sure it isn't just this that I'm doing wrong but it is the first thing that's coming up. So getManager() apparently doesn't have a createSubQuery() function? What is the right way to do this?
There's no method like createSubQuery(). Take a look at that answer: https://stackoverflow.com/a/10763358

CakePHP: add variables to query in controller

I have a controller action that uses a sql query:
$tag = $this->params['tag'];
$this->set('projects', $this->Project->query('SELECT * FROM projects INNER JOIN projects_tags ON projects.id = projects_tags.project_id INNER JOIN tags on projects_tags.tag_id = tags.id WHERE tags.tag LIKE $tag'));
As you can see at the end I want to use a where clause with the $tag variable but I'm not sure how the syntax would go. As I'm getting the error
Unknown column '$tag' in 'where clause'
Can someone steer me in the right direction?
Ta,
Jonesy
I would strongly advise you to use the Cake ORM instead of raw queries, especially if you're going to plug URL parameters into it. Conditions on HABTM tables can be tricky, but you can build your joins using Cake's ORM syntax as well!
Read the manual, section 3.7.6.9 Joining tables.
Should you want to use Cake's ORM, the following code should provide results equivalent to your raw SQL query:
$this->loadModel('ProjectsTag'); // Load the joining table as pseudo-model
// Define temporary belongsTo relationships between the pseudo-model and the two real models
$this->ProjectsTag->bindModel(array(
'belongsTo' => array('Project','Tag')
));
// Retrieve all the join-table records with matching Tag.tag values
$result_set = $this->ProjectsTag->find('all',array(
'conditions' => array('Tag.tag LIKE' => "%{$tag}%")
));
// Extract the associated Project records from the result-set
$projects = Set::extract('/Project', $result_set);
// Make the set of Project records available to the view
$this->set(compact('projects'));
in php there's a difference between single and double quotes... basically, single quotes dont evaluate the variables... use double quotes instead
And i think that LIKE will need also single quotes.. i'm not really sure
"SELECT * FROM projects INNER JOIN projects_tags ON projects.id = projects_tags.project_id INNER JOIN tags on projects_tags.tag_id = tags.id WHERE tags.tag LIKE '$tag'"
i know.. i know.. people will start talkin' about sql injection.. and the need to scape the caracters... that's another question =)
good luck!
I would at least consider using the cakephp sanitize functions on your tag strings if they are user sourced. See http://book.cakephp.org/view/1183/Data-Sanitization or if using mysql as the db at least consider using http://www.php.net/manual/en/function.mysql-escape-string.php or do something to filter your user input. But the best thing is to make use of the CakePHP orm stuff.
Modify your query as:
$this->set('projects',
$this->Project->query("SELECT * FROM projects
INNER JOIN projects_tags
ON projects.id = projects_tags.project_id
INNER JOIN tags ON projects_tags.tag_id = tags.id
WHERE tags.tag LIKE '" . $tag . "'") //problem was here
);
and it will work.

Categories