Tricky Eloquent query - php

I've been struggling to figure out a table relationship for the past two days, I'm sure that the solution is simple but it is alluding me.
Four tables/models are involved (including a pivot table):
Skill - skills table. A Skill belongs to a SkillGroup and belongs to many Candidates (candidate_skill pivot table)
Candidate - candidates table. Contains personal information on a candidate, not terribly related to the issue.
SkillGroup - skill_groups table. Each Skill Group has many Skills.
I want to be able to retrieve Skill objects possessed by a Candidate grouped by the SkillGroup. For example:
[
{
"id": 1,
"title": "Information Technology (Skill Group)",
"slug": "information-technology",
"created_at": "2016-05-07 23:58:23",
"updated_at": "2016-05-07 23:58:23",
"skills": [
{
"id": 1,
"title": "Web Development (Skill)",
"slug": "web-development",
"description": "Web developers primarily focus on the back-end of websites",
"created_at": "2016-05-07 23:58:55",
"updated_at": "2016-05-07 23:58:55",
"skill_group_id": 1,
"candidates": [
{
"first_name": "John (Candidate)",
"last_name": "Smith",
"pivot": {
"skill_id": 1,
"candidate_id": 6
}
}
]
}
]
}
]
This is easy to accomplish with the following code, but I want to retrieve only results (SkillGroup -> Skills -> Candidate) for a specific candidate.
\App\SkillGroup::with('skills.candidates')->get();
I have tried the following (and everything else I can think of), the candidate_id does not seem to affect the query - I see skills that are not possessed by the given candidate.
\App\SkillGroup::with(['skills.candidates' => function($query) {
$query->whereCandidateId(6);
}])->get();
Any help would be greatly appreciated, thanks!
EDIT
Thanks to #Giedrius Kiršys, I was able to come up with the following:
\App\SkillGroup::with(['skills.candidates' => function($query) {
$query->wherePivot('candidate_id', 8)->addSelect('candidates.id', 'first_name', 'last_name');
}])->whereHas('skills.candidates', function($q) {
$q->whereCandidateId(8);
})->get();
This only retrieves SkillGroup results with Skills with a Candidate with the given ID.

You want to query by pivot table attribute, but You are querying by candidates.candidate_id attribute.
You can do it like this:
\App\SkillGroup::with(['skills.candidates' => function($query) {
$query->wherePivot('candidate_id', 6);
}])->get();

Related

My question is about mongodb do a query between multiple collection in PHP

I'm trying to use mongodb to do a query link SQL join table in PHP
Two collection are like following
Courses:
{
"CourseID": "CS101",
"Title": "Introduction to Data Science",
"Level": 6
},
{
"CourseID": "CS102",
"Title": "Application Design and Development",
"Level": 6
},
Offer:
{
"DeptID": "CS",
"CourseID": "CS101",
"Year": 2016,
"ClassSize": 40,
"AvailablePlaces": 40
},
I'm trying to make the result like the following:
prefering result here
MongoDB is not a relational database, but you can perform a left outer join by using the $lookup stage.
The $lookup stage lets you specify which collection you want to join with the current collection, and which fields that should match. Here's the official doc.
dbo.collection('courses').aggregate([
{ $lookup:
{
from: 'offer',
localField: 'CourseID',
foreignField: 'CourseID',
as: 'availableplaces'
}
}
])

laravel 5.3 Many-to-Many relationship returns associated Role only when queried

I am just confused on how this thing is working .
I have a M-M relationship between by Users and Roles. If I retrieve my user like the following :-
$user = Auth::User();//->with('roles')->get();
$roleName = $user->roles[0]->name;
return $this->sendResponse('User retrieved successfully',$user);
I get the following response :
{
"success": true,
"message": "User retrieved successfully",
"data": {
"id": 2,
"name": "dummy",
"email": "dummy#dummy.com",
"created_at": "2017-05-06 09:49:50",
"updated_at": "2017-05-06 09:49:50",
"tenant_id": 2,
"roles": [
{
"id": 1,
"created_at": "2017-05-06 06:26:55",
"updated_at": "2017-05-06 06:26:55",
"name": "Admin",
"permissions": null,
"pivot": {
"user_id": 2,
"role_id": 1
}
}
]
}
}
But, if I retrieve my user as :-
$user = Auth::User();//->with('roles')->get();
return $this->sendResponse('User retrieved successfully',$user);
I get the following resut :-
{
"success": true,
"message": "User retrieved successfully",
"data": {
"id": 2,
"name": "Ali",
"email": "ali#and-or.com",
"created_at": "2017-05-06 09:49:50",
"updated_at": "2017-05-06 09:49:50",
"tenant_id": 2
}
}
Why is this happening ? I expected the "first" posted result to the latter query.
Secondly, I did not modify the $user after the query in the first "method" how did it get its Roles attachment ?
I am sure there is an explanation, but I couldn't put my finger on it.
When retrieving a model, the User in this instance, relationships are not automatically also retrieved (Since on the database side this would require a second query while you might not even need the roles in a certain situation).
In your first example, by accessing the roles through $roleName = $user->roles[0]->name;, Laravel does the roles() query automatically, and also adds the roles object to the User (So it can be accessed again at a later point without needing to redo the query). This explains why the roles are 'magically' attached to your User model in the first example.
In your second example this query is not done automatically, so you do not get the roles relation in your response.
If you want to have access to the Users roles, then you could use the with() method like in your comment to eager load the relationship, but keep in mind that this implies doing the second query in order to get this data from the database.
Another option, if you always want the User model to have its Roles attached, would be to add roles to the $appends array of the model:
protected $appends = ['roles'];
This tells Laravel that the roles attribute is one which you always want available on your model, and it then does what is necessary to make this happen (In this case, query the relationship).

How to count relations in laravel

How can I count for example the number of roles for a user?
When I try this:
User::with('roles')->count();
it just counts the number of users.
What I need is to return the number of roles per user. For example:
[
{
"id": 2,
"name": "user",
"roles": 2
},
{
"id": 3,
"name": "user",
"roles": 1
}
]
Eloquent does not support this out-of-the-box.
You can read this great article on how to achieve this:
How to get hasMany relation count efficiently?
If you already have the $user object, you can do the following:
$rolecount = $user->roles()->count();
Or if you are using eager loading you can drop the ( & ) at roles
$rolecount = $user->roles->count();

Laravel Eloquent Relationships

I have a table of Questions and another table that has Answers. A question has many answers, and an answer belongs to a question. These relationships have been defined in the Question and Answer model and work as expected.
However, when I try to get a bunch of questions with the answers, Eloquent returns an empty array.
return Question::with('answers')
->where('category_id', $input['category'])
->take($input['num_questions'])
->orderBy(DB::raw('RAND()'))
->get();
I get the following response...
{
"id": "1",
"category_id": "1",
"question": "Why did the chicken cross the road?",
"feedback": "Why did you ask that?",
"created_at": "2014-04-24 16:57:48",
"updated_at": "2014-04-24 16:57:48",
"answers": []
},
{
"id": "2",
"category_id": "1",
"question": "How awesome is Laravel?",
"feedback": "That's debatable.",
"created_at": "2014-04-24 16:57:48",
"updated_at": "2014-04-24 16:57:48",
"answers": []
}
When printing the raw queries I see the following...
{
"query": "select * from `questions` where `category_id` = ? order by RAND() asc",
"bindings": [
"1"
],
"time": 2.11
},
{
"query": "select `id`, `choice`, `correct` from `answers` where `answers`.`question_id` in (?, ?, ?, ?)",
"bindings": [
"3",
"4",
"1",
"2"
],
"time": 0.92
}
When I run these queries manually, I see answers, but for some reason laravel shows an empty array. Why? What am I doing wrong here?
First remove the ->select() code off your model, then do:
return Question::with(array('answers'=>function($query)
{
$query->select(array('id', 'choice','question_id')); // question_id is mandatory because Laravel need it for mapping
}))
->where('category_id', $input['category'])
->take($input['num_questions'])
->orderBy(DB::raw('RAND()'))
->get();
I was manually selecting what fields I wanted returned in the model. This apparently breaks when you select multiple rows rather than a single row.
return $this->hasMany('Answers')->select(array('id', 'choice'));
Removing that ->select() code off the model fixed it.

How to join and echo 2 different MongoDB collections using PHP? [duplicate]

I'm using the Mongo PHP extension.
My data looks like:
users
{
"_id": "4ca30369fd0e910ecc000006",
"login": "user11",
"pass": "example_pass",
"date": "2010-09-29"
},
{
"_id": "4ca30373fd0e910ecc000007",
"login": "user22",
"pass": "example_pass",
"date": "2010-09-29"
}
news
{
"_id": "4ca305c2fd0e910ecc000003",
"name": "news 333",
"content": "news content 3333",
"user_id": "4ca30373fd0e910ecc000007",
"date": "2010-09-29"
},
{
"_id": "4ca305c2fd0e910ecc00000b",
"name": "news 222",
"content": "news content 2222",
"user_id": "4ca30373fd0e910ecc000007",
"date": "2010-09-29"
},
{
"_id": "4ca305b5fd0e910ecc00000a",
"name": "news 111",
"content": "news content",
"user_id": "4ca30369fd0e910ecc000006",
"date": "2010-09-29"
}
How to run a query similar like this, from PHP?
SELECT n.*, u.*
FROM news AS n
INNER JOIN users AS u ON n.user_id = u.id
MongoDB does not support joins. If you want to map users to the news, you can do the following
1) Do this at the application-layer. Get the list of users, and get the list of news and map them in your application. This method is very expensive if you need this often.
2) If you need to do the previous-step often, you should redesign your schema so that the news articles are stored as embedded documents along with the user documents.
{
"_id": "4ca30373fd0e910ecc000007",
"login": "user22",
"pass": "example_pass",
"date": "2010-09-29"
"news" : [{
"name": "news 222",
"content": "news content 2222",
"date": "2010-09-29"
},
{
"name": "news 222",
"content": "news content 2222",
"date": "2010-09-29"
}]
}
Once you have your data in this format, the query that you are trying to run is implicit. One thing to note, though, is that analytics queries become difficult on such a schema. You will need to use MapReduce to get the most recently added news articles and such queries.
In the end the schema-design and how much denormalization your application can handle depends upon what kind of queries you expect your application to run.
You may find these links useful.
http://www.mongodb.org/display/DOCS/Schema+Design
http://www.blip.tv/file/3704083
I hope that was helpful.
Forget about joins.
do a find on your news. Apply the skip number and limit for paging the results.
$newscollection->find().skip(20).limit(10);
then loop through the collection and grab the user_id in this example you would be limited to 10 items. Now do a query on users for the found user_id items.
// replace 1,2,3,4 with array of userids you found in the news collection.
$usercollection.find( { _id : { $in : [1,2,3,4] } } );
Then when you print out the news it can display user information from the user collection based on the user_id.
You did 2 queries to the database. No messing around with joins and figuring out field names etc. SIMPLE!!!
If you are using the new version of MongoDB (3.2), then you would get something similar with the $lookup operator.
The drawbacks with using this operator are that it is highly inefficient when run over large result sets and it only supports equality for the match where the equality has to be between a single key from each collection. The other limitation is that the right-collection should be an unsharded collection in the same database as the left-collection.
The following aggregation operation on the news collection joins the documents from news with the documents from the users collection using the fields user_id from the news collection and the _id field from the users collection:
db.news.aggregate([
{
"$lookup": {
"from": "users",
"localField": "user_id",
"foreignField": "_id",
"as": "user_docs"
}
}
])
The equivalent PHP example implementation:
<?php
$m = new MongoClient("localhost");
$c = $m->selectDB("test")->selectCollection("news");
$ops = array(
array(
"$lookup" => array(
"from" => "users",
"localField" => "user_id",
"foreignField" => "_id",
"as" => "user_docs"
)
)
);
$results = $c->aggregate($ops);
var_dump($results);
?>
You might be better off embedding the "news" within the users' documents.
You can't do that in mongoDB. And from version 3 Eval() is deprecated, so you shouldn't use stored procedures either.
The only way I know to achieve a server side query involving multiple collections right now it's to use Node.js or similar. But if you are going to try this method, I strongly recommend you to limit the ip addresses allowed to access your machine, for security reasons.
Also, if your collections aren't too big, you can avoid inner joins denormalizing them.

Categories