translate raw MySQL into Doctrine 1.2 - doctrine

I'm having a heck of a time translating the following raw MySQL query, which has been tested and works very well, into doctrine 1.2:
SELECT
r.name AS regionname,
s.name AS statename
FROM
job j
LEFT JOIN
community c ON c.id = j.community_id
LEFT JOIN
state s ON s.id = c.state_id
LEFT JOIN
region r ON r.id = s.region_id
WHERE
r.id = 1
This does not work:
$q = Doctrine_Query::create()
->select('r.name AS regionname', 's.name AS statename')
->from('job j')
->leftJoin('community c ON c.id = j.community_id')
->leftJoin('state s ON s.id = c.state_id')
->leftJoin('region r ON r.id = s.region_id')
->where('r.id = 1')
->execute();
Here's my db structure, if it is useful:
job:
columns:
id
community_id
relations:
community: local_key: community_id, foreign_key: id, foreignAlias: Communitys
community:
columns:
id
state_id
relations:
state: local_key: state_id, foreign_key: id, foreignAlias: States
state:
columns:
id
name
region_id
relations:
region: local_key: region_id, foreign_key: id, foreignAlias: Regions
region:
columns:
id
name

I have noticed the same thing as well. Doing joins in Doctrine 1.2 is a little bit different from mysql. To do a join, you have to take advantage of the relationships that Doctrine creates in each component in your case the job class.
You can check the job's base class for these relationships I am talking about. In the setup method, they look like this for example for a community foreign key id in the job table:
$this->hasOne('community as Communitys', array(
'local' => 'community_id',
'foreign' => 'id'));
Back to your question, to do a join you have to reference the alias of the table where the foreign key belongs to. For example if you want to join on the community table with the alias Community:
->leftJoin('j.Community')
instead of
community c ON c.id = j.community_id
Notice that I didn't say join on id because in Doctrine 1.2 that is implied already. You can however choose to join on something else.
Likewise if tables you have previously joined to need to be joined to other tables they have foreign keys to you have to
1.use their alias in the current queryand
2.reference the component as stated in THEIR base class as well.
So you said that you want to join community to states in the current query the way you should do it is
// join job and community first
->from('job j')
->leftJoin('j.Community c')
// join community to state since they are the ones that are related
->leftjoin('c.States s')
From here I hope you can see the pattern. At the end of the day if you look at it the idea of joining is still the same with raw sql, the only difference is the syntax:
SQL syntax:
LEFT JOIN
community c ON c.id = j.community_id
DQL syntax:
->leftJoin('j.Community c')
Anyway to answer your question, here is how the join should somewhat be done:
$q = Doctrine_Query::create()
->select('*')
->from('job j')
->leftJoin('j.Community c')
->leftJoin('c.State s')
->leftJoin('s.Region r')
->where('r.id = 1')
->execute();

Related

Deep nested relationship whereHas and with

I have been stuck on this one for a few days, in part due to the depths of the query. You might as well have the true query in all its current glory too:
$submissions = Submission::with([
'authGroupPriority',
'authGroupPriority.groupData',
'claimData',
'claimData.claimUser',
'claimData.claimForm',
'claimData.claimStatus',
'authGroupPriority.formData',// => function($query){$query->where('forms.id', 1);},
'authGroupPriority.users' => function($query){
$query->where('users.id', Auth::id());
}])->whereHas('authGroupPriority.users', function($query){
$query->where('users.id', Auth::id());
})/*->whereHas('authGroupPriority.formData', function($query){$query->where('forms.id', 1);})*/
->get();
Right so most of the relationships are one to one or one to many relationships here and this is the desired outcome:
Submission
WHERE current user is in list of authGroupPriority.users
WHERE submission.level EQUALS claimData.claimForm.authGroups.level
Thats the basics anyway. I am happy with setting up whereHas and with functions to be the same, just can't work out how to pass in submission level in a whereHas (or with) on the deep nested relationship. Any ideas or am I overcomplicating this somehow?
EDIT:
After spending the afternoon on this query I finally got the one that works (in SQL)
SELECT * from submissions
INNER JOIN(SELECT users.uid, forms.id, forms.name, claims.id AS claimsId FROM claims
INNER JOIN submissions ON submissions.claim = claims.id
INNER JOIN forms ON forms.forms.id = claims.form
INNER JOIN users ON claims.user = users.id) F ON F.claimsId = submissions.claim
INNER JOIN (SELECT auth_group_members.priority FROM auth_group_members
INNER JOIN groupmembership ON auth_group_members.authGroup = groupmembership.groupID
WHERE groupmembership.userID = 1) AG ON AG.priority = submissions.chainPosition
GROUP BY submissions.claim;
Its not pretty but it works. The question is, is this Laravelifiable (new word I think I'm going to claim)? (withs and wherehas's)
EDIT 2:
The SQL above isn't right but its almost right. Just got one more filter to add and thats the submission level = the auth groups level
EDIT 3:
SELECT * FROM submissions
INNER JOIN (SELECT users.uid, forms.id, forms.name, claims.id as claimsId FROM claims
INNER JOIN submissions ON submissions.claim = claims.id
INNER JOIN forms ON forms.forms.id = claims.form
INNER JOIN users ON claims.user = users.id) F ON F.claimsId = submissions.claim
INNER JOIN (SELECT auth_group_members.form, auth_group_members.priority FROM auth_group_members
INNER JOIN groupmembership ON auth_group_members.authGroup = groupmembership.groupID
WHERE groupmembership.userID = 1) AG ON AG.priority = submissions.chainPosition AND AG.form = F.id
GROUP BY submissions.claim

Select from junction table

everybody!
What should I do if I need to make select from junction table?
For example, I develop project and I need to make chats between users. I have two entities: User and Chat, and many-to-many relation between them (accordingly, I have three tables: user, chat, chat_user). I try to get all chats, which user is member, and to get all users from these chats.
I made the following SQL query:
SELECT *
FROM chat c
INNER JOIN chat_user cu ON c.id = cu.chat_id
INNER JOIN user u ON u.id = cu.user_id
WHERE c.id IN (SELECT chat_id
FROM chat_user
WHERE user_id = <idUser>);
But I don't know how to translate in DQL subquery SELECT chat_id FROM chat_user WHERE user_id = <idUser>, because a haven't additional entity for table chat_user.
And I tried to add entity ChatUser and get data in ChatRepository smt. like this:
public function getChatsData($idUser)
{
$subQuery = $this->getEntityManager()
->getRepository(ChatUser::class)
->createQueryBuilder('chus')
->select('chus.chat')
->andWhere('chus.user = :idUser')
->setParameter('idUser', $idUser)
;
$qb = $this->createQueryBuilder('c');
return $qb
->innerJoin('c.chatUsers', 'cu')
->addSelect('cu')
->innerJoin('cu.user', 'u')
->addSelect('u')
->innerJoin('c.messages', 'm')
->addSelect('m')
->andWhere('u.id = :idUser')
->andWhere($qb->expr()->in(
'c.id',
$subQuery->getDQL()
))
->setParameter('idUser', $idUser)
->getQuery()
->getResult()
;
}
but it doesn't work. I get error [Semantical Error] line 0, col 12 near 'chat FROM App\Entity\ChatUser': Error: Invalid PathExpression. Must be a StateFieldPathExpression.
Have Doctrine standard tools for such tasks?

Hibernate, Order by count of subquery on ManyToMany using Predicates

I have a query written using Predicates in Hibernate and I need to add a subquery on a join table to count the number of joins and order by the number of joins where they exist in an array of ids.
The join table is a ManyToMany relation.
I am using flyway to setup the table schema, so while the join table exists in the database, a join model is not needed in Hibernate to join the 2 related models therefore no join model exists.
I don't care about retrieving these related models, I just want the number of joins so that I can order by them.
The following is PostGreSQL, which works. I need to convert the following PSQL into a Predicate based query:
SELECT u.*, COUNT(jui.interest_id) AS juiCount
FROM "user" u
LEFT JOIN (
SELECT ui.user_id, ui.interest_id
FROM user_interest ui
WHERE ui.interest_id IN (?)
) AS jui ON u.id = jui.user_id
GROUP BY u.id
ORDER BY juiCount DESC
Where the ids provided for the IN condition are passed into the subquery. The above query is in PostGreSQL.
Working with what I have so far:
CriteriaBuilder b = em.getCriteriaBuilder();
CriteriaQuery<User> q = b.createQuery(User.class);
Root<User> u = q.from(User.class);
// This doesn't make sense because this is not a join table
// Subquery<Interest> sq = q.subquery(Interest.class)
// Root<Interest> squi = sq.from(Interest.class);
// sq.select(squi);
// sq.where(b.in("interest_id", new LiteralExpression<Long[]>((CriteriaBuilderImpl) b, interestIds)));
q.orderBy(
// b.desc(b.tuple(u, b.count(squi))),
b.asc(u.get(User_.id))
);
q.where(p);
return em
.createQuery(q)
.getResultList();
Everything I have managed to find doesn't quite seem to fit right given that they are not using ManyToMany in the use of q.subquery() in their example.
Anyone can help fill in the blanks on this please?

How can I linq 3 tables

How can I linq 3 tables where I can link a student to a school. Tables are: Students, Depart, School.
studentId(pk), departId(fk) departId(pk), schoolId(fk) schoolId(pk)
Below is linking two tables
#foreach (var student in Model.students.Where(s => s.schoolId == item.schoolId))
what you want exactly ? you can join them ...
var query = (from depart in Model.Depart
join school in Model.School on depart.departId equals school.departId
join student in Model.students on school.schoolId equals student.schoolId
where students.schoolId == item.schoolId
select new
{
depart,
school,
student
});
or...
If you have your associations cdonfigured properly, instead of joins, you could also use the associations:
var query = from school in Model.Schools
from dept in school.Departments
from student in dept.Students
select new { student, dept, school};

Why does LINQ-to-EF render certain "Contains" as two separate JOINs? [duplicate]

I know that there are some questions about this already, most relate to either old issues which were resolved or multiple tables. This question is not covered in any of the other 'left outer join' issues I saw, I get an INNER JOIN and LEFT OUTER JOIN to the same table at the same query.
The table outline:
Users: id (PK)
Name (VARCHAR)
ProfileImageUri (VARCHAR)
Locations: id (PK)
LocationBPNTips: id (PK)
TipText (VARCHAR)
CreatedAt (Datetime)
UserId (int) (FK to User.id, navigation property is called User)
LocationId (int) (FK to Location.id)
(there is more, but it is not relevant :) )
In my scenario, I am performing a query to a referenced table via projection and I get an extra left outer join, this is what I run (the commented parts are irrelevant to the problem, commented out for cleaner SQL, EF does the sorting right (even better than I imagined :) ) ):
LocationBPNTips
.Where(t => t.LocationId == 33)
//.OrderByDescending(t => intList.Contains(t.UserId))
//.ThenByDescending(t => t.CreatedAt)
.Select(tip => new LocationTipOutput
{
CreatedAt = tip.CreatedAt,
Text = tip.TipText,
LocationId = tip.LocationId,
OwnerName = tip.User.Name,
OwnerPhoto = tip.User.ProfileImageUri
}).ToList();
And this is is the generated SQL
SELECT
[Extent1].[LocationId] AS [LocationId],
[Extent1].[CreatedAt] AS [CreatedAt],
[Extent1].[TipText] AS [TipText],
[Extent2].[Name] AS [Name],
[Extent3].[ProfileImageUri] AS [ProfileImageUri]
FROM [dbo].[LocationBPNTips] AS [Extent1]
INNER JOIN [dbo].[Users] AS [Extent2] ON [Extent1].[UserId] = [Extent2].[Id]
LEFT OUTER JOIN [dbo].[Users] AS [Extent3] ON [Extent1].[UserId] = [Extent3].[Id]
WHERE 33 = [Extent1].[LocationId]
As you can see, the LEFT OUTER JOIN is done on the same table of the INNER JOIN
I think the optimal code will be (note, I renamed Extent3 to Extent2 manually, and added the comment. this was not generated by EF!!) - with my current data, this runs about 22% faster (with the sorting, this % should be higher without the sort) as no need for an extra join..
SELECT
[Extent1].[LocationId] AS [LocationId],
[Extent1].[CreatedAt] AS [CreatedAt],
[Extent1].[TipText] AS [TipText],
[Extent2].[Name] AS [Name],
[Extent2].[ProfileImageUri] AS [ProfileImageUri]
FROM [dbo].[LocationBPNTips] AS [Extent1]
INNER JOIN [dbo].[Users] AS [Extent2] ON [Extent1].[UserId] = [Extent2].[Id]
--LEFT OUTER JOIN [dbo].[Users] AS [Extent3] ON [Extent1].[UserId] = [Extent3].[Id]
WHERE 33 = [Extent1].[LocationId]
The different queries I have tried (the projection is into an anonymous type in these):
LocationBPNTips
.Where(t => t.LocationId == 33)
//.OrderByDescending(t => intList.Contains(t.UserId))
//.ThenByDescending(t => t.CreatedAt)
.Select(tip => new
{
CreatedAt = tip.CreatedAt,
Text = tip.TipText,
LocationId = tip.LocationId,
OwnerName = tip.User,
OwnerPhoto = tip.User
}).ToList()
SQL output was messed up, it selected the entire user table twice in the same format as above, inner then left outer. I think that I can see in theory why this happens for this case, because I asked for the data twice - although it could have been done in memory and not by the SQL with an extra join - but in my case I did not ask for data twice, I asked for different columns only once. I did this test to see if the double join is consistent.
I also tried running:
LocationBPNTips
.Where(t => t.LocationId == 33)
.Select(tip => new
{
CreatedAt = tip.CreatedAt,
Text = tip.TipText,
LocationId = tip.LocationId,
OwnerName = tip.User.Name
}).ToList()
This one returned clean, single inner join as expected, but it is not what I am trying to do
So the question is: Is this a bug? am I using the EF incorrectly?
I've seen similar problem already. We can call it bug or feature. Simply EF query generation is far from ideal. ADO.NET team fixes some problems with every major release. I don't have June CTP 2011 currently installed to verify if it also happens in the first preview of the next version.
Edit:
According to this answer similar issue was fixed in June CTP 2011.

Resources