I have 5 user titles, defined by booleans: CEO, executive, manager, employee & intern.
I'm building a user search API, and want to turn on/off eloquent queries, returning users with selected titles.
So if I were to search for managers and employees, the query should be
$users = User::where(function($query)
{
$query->orWhere('manager')->orWhere('employee');
})
->where([ADDITIONAL CONSTRAINTS... (like age)])->get();
The furthest I have came is:
$query = User::query();
//the respective titles are turned on by 1 and turned off by 0
if($CEO) {
$query = $query->orWhere('CEO');
}
if($executive) {
$query = $query->orWhere('executive');
}
//And so on for each title
In the end the additional where constraints get added like this:
$users = $query->where([Additional constraints])->get();
When searching for managers and employees, the final query would be:
$users = User::orWhere('manager')->orWhere('employee')
->where([ADDITIONAL CONSTRAINTS... (like age)])->get();
The result of this query is that the additional constraints are not always met, because there are orwhere queries before, which allow for unwanted instances to get selected.
I tried replacing the orWhere's with where's, but then users need to check positive for each selected title to get selected. So if I wanted to search for managers and employees, I might get none, because there isn't any user with both titles.
The goal:
I want to add all these conditional 'title-queries' together.
Put them all in one where(function($query) { $query->[all 'title-queries']; }).
Additional comments:
I know that I could also eliminate every other model instead of searching for wanted models. If I would search for managers and employees, I could set where('CEO', '!=', 1) for each unwanted title. I don't want this, because Users with two titles, like employee and interim would get excluded in some cases.
I know that I could write nested conditional queries for each scenario i.e. (manager & ceo, interim & ceo & executive and on ...), but that would take 25 queries and simply is not easily scalable (exponential more queries) if additional user titles are added.
It has to be an Eloquent solution.
Users can have multiple titles.
I have thought hard about this problem, thanks!
Maybe you can do something like this ? (with use function keyword)
<?php
$filters = ['manager', 'employee'];
$users = User::where(function($query) use($filters) {
foreach( $filters as $filter )
$query = $query->orWhere($filter);
})
->where([ADDITIONAL CONSTRAINTS... (like age)])->get();
Related
Good day. I am building an API, in which I want to return some data. I have three tables
counsels
counsel_cases
analysis_sc
A section of the counsels table is shown below
A section of the counsel_cases is also shown below
Finally, a section of the analysic_sc is shown below.
I want that when a counsel is selected, through the counsel_id, I can fetch the cases belonging to the counsel from the counsel_cases, and then with that information, I want to be able to fetch the number of cases of such a counsel belonging to a legal head (area of practice) on the third table as shown in the design below.
How will that be possible. I have a relationship between counsel and counsel_cases though. Also, I have tried using a foreach loop as shown below, but I am unable to get the unique values of the legal_head.
public function getCounselPracticeAreas(Request $request)
{
$counsel_id = $request->route('counsel_id');
$cases = CounselCase::select('suit_number')->where('counsel_id', $counsel_id)->get();
$data = [];
foreach ($cases as $case) {
$values = AnalysisSc::select('legal_head')->where('suitno', $case->suit_number)->first();
array_push($data, $values);
}
return response()->json([
"message" => "successful",
"data" => $data
]);
}
However, this is the value I get
[![enter image description here][5]][5]
I want to get something like this:
$data : [
"legal_head" : [
"name" : "Criminal Law",
"count" : 2
]
]
Please, is this possible
I know this is quite long. And I hope I explained myself well. Thanks
you need to use SQL Joins to create a relationship between the tables and fetch the data
It looks like you're skipping using relationships in your models. Once you have your models set up, retrieving the data you need will be a lot easier with Laravel. You may want to separate your models/tables a little more and add relationships, like the following:
Models/tables:
Counsel (counsels)
id
name
LegalHead (legal_heads)
id
name
Case (cases)
id
suit_number
year
subject_matter
legal_head_id
case_counsels (This is not a model, just a relationship table)
id
case_id
counsel_id
appearing_for
role
Note: I wasn't sure how your data is structured. You can use this as start and adjust as necessary.
Relationships
The Many-Many relationship is suitable for the case counsels and you'll be able to add the role as a pivot(extra field) for the relationship.
https://laravel.com/docs/8.x/eloquent-relationships#many-to-many
Case
legal_head: belongsTo
counsels: belongsToMany with pivot for role
Counsel
cases: belongsToMany with pivot for role
Retrieving the Objects
When that is set up, you won't have to do as much query work. You can do this to get case data with counsels, legal_head and their role for each case:
$cases = Case::with('counsels','legal_head')->get();
And this to get the Legal Head names with the number of cases:
$legal_heads = LegalHead::withCount('cases')->get();
I'm trying to count some items from the table and join it with the another table so I use the following code
Article::join("article_comments", "article_comments.article_id", "=","articles.id")->
select(["articles.title", "articles.content", "articles.created_at", DB::raw('count(article_comments.id) as commentsCount')])->paginate(10) ;
But I always get only first item
Shouldn't there be a group by if you use aggregates?
Something like:
Article::join("article_comments", "article_comments.article_id", "=","articles.id")->
select(["articles.title", "articles.content", "articles.created_at", DB::raw('count(article_comments.id) as commentsCount')])->
groupBy('articles.id')->
paginate(10);
Also since you're no using your joined table for filtering you might wanna consider eager loading. However in your case you only get articles with comments. When using eager loading you'd get all articles (including the ones without comments).
Something like that should work:
Article::with([
'comments' => function ($query) {
$query
->select('article_id', DB::raw('COUNT(`id`) AS `commentCount`'))
->groupBy('article_id')
}
])
->paginate(10);
Now you should be able to access the count like this:
echo $article->comments->first()->commentCount;
Did not test the solution. You might wanna check if $article->comments->first() exists.
Or if you wanna expand your model a little here are some even better thoughts
I have a collection called User, I also have an array containing two models Post relating to the User model.
The User collection contains a primary key id and each model in my Post collection I have a foreign key user_id.
I am currently executing the following:
foreach ($users as $user) {
foreach ($posts as $post) {
if ($post->user_id == $user->id) {
$user->posts->push($post);
}
}
}
This somewhat works, but not entirely because it pulls in all related posts instead of the recent two posts a user has made.
The array looks like the following:
My User schema looks like; with a hasMany relationship to Post:
You can load the posts associated to a User using with, something like
$user = User::with('posts')->find($id);
But your scenario sounds specifically collecting the latest two Post belonging to a User. To limit your results you can also use scopes.
Something like the following on your Post model would work:
public function scopeLatest($query, $latest = 2)
{
return $query->limit($latest);
}
Then collect these by:
// The user record.
$user = User::find($id);
// Latest 2 posts for this user.
$posts = $user->posts()->latest();
// Latest 5 posts for this user.
$posts = $user->posts()->latest(5);
However, should you with to load the latest 2 posts with the user in a single query - then you could make a new relation:
public function latestPosts()
{
return $this->hasMany(Post::class, 'post_id', 'id')
->orderBy('created_at', 'ASC')
->limit(2);
}
This would work in the following way:
// Load the user with the latest 2 posts.
$user = User::with('latestPosts')->find($userId);
// Access these using; this will be a Collection containing 2 `Post` records.
dd($user->latestPosts);
Basically with Eloquent, when you call $this->latestPosts Eloquent will run latestPosts() and hydrate the related records. Using with this hydration occurs with a single query and the relations are already defined.
The difference between the method latestPosts() and the property $latestPosts is simple.
The method will always return a specific Relation Collection allowing you to chain additional conditions;
So: $user->latestPosts()->get() is the same as $user->latestPosts.
You cannot use query constraints / eager loading to do this. Doing so will only work if you are retrieving the posts for one user. However, if you try to retrieve the posts for multiple users, it will fail because eager loading / query constraints will limit the related results as a whole. To understand, you have to look at the queries Eloquent generates. Lets take a look at an example where you only need one user's posts.
$user = User::with(['posts' => function($query) {
$query->limit(2);
}])->find(1);
In this example, we are getting a user with a primary key of 1. We also also retrieving his/her posts but limiting it so we only retrieve 2 posts. This works, and it will generate 2 queries similar to this:
select * from `users` where `users`.`id` = 1 limit 1
select * from `posts` where `posts`.`user_id` in (1) limit 2
Okay. Now, why doesn't this work if you try to get more than 1 user (or a collection of users)? For example:
$user = User::with(['posts' => function($query) {
$query->limit(2);
}])->get();
In this case, I changed find(1) to get(), and it will generate 2 queries like this:
select * from `users`
select * from `posts` where `posts`.`user_id` in (?, ?, ?, ... ?) limit 2
It's important to take a look at the second query. It's retrieving all the related posts, but at the end, you'll see that it has limit 2. In other words, it's limiting the entire related collection to only 2, which is why query constraints do not work for this.
Achieving this is actually pretty complex, but a fellow member (Jarek Tkaczyk) came up with a solution using MySQL variables, which you can find here: Laravel - Limit each child item efficiently
You can do this a bit simpler with https://laravel.com/docs/5.2/eloquent-relationships#eager-loading constraints.
Example: Users have many Dogs, but only take 2
$user = App\User::with(['dogs' => function ($query) {
$query->limit(2);
}])->find($user_id);
dump($user);
The anonymous constraining function would also have an orderBy in your case
This may be a dupe but I've been trawling for some time looking for a proper answer to this and haven't found one yet.
So essentially all I want to do is join two tables and attach a where condition to the entire collection based on a field from the joined table.
So lets say I have two tables:
users:
-id
-name
-email
-password
-etc
user_addresses:
-address_line1
-address_line2
-town
-city
-etc
For the sake of argument (realising this may not be the best example) - lets assume a user can have multiple address entries. Now, laravel/eloquent gives us a nice way of wrapping up conditions on a collection in the form of scopes, so we'll use one of them to define the filter.
So, if I want to get all the users with an address in smallville, I may create a scope and relationships as follows:
Users.php (model)
class users extends Eloquent{
public function addresses(){
return $this->belongsToMany('Address');
}
public function scopeSmallvilleResidents($query){
return $query->join('user_addresses', function($join) {
$join->on('user.id', '=', 'user_addresses.user_id');
})->where('user_addresses.town', '=', 'Smallville');
}
}
This works but its a bit ugly and it messes up my eloquent objects, since I no longer have a nice dynamic attribute containing users addresses, everything is just crammed into the user object.
I have tried various other things to get this to work, for example using a closure on the relationship looked promising:
//this just filters at the point of attaching the relationship so will display all users but only pull in the address where it matches
User::with(array('Addresses' => function($query){
$query->where('town', '=', 'Smallville');
}));
//This doesnt work at all
User::with('Addresses')->where('user_addresses.town', '=', 'Smallville');
So is there an 'Eloquent' way of applying where clauses to relationships in a way that filters the main collection and keeps my eloquent objects in tact? Or have I like so many others been spoiled by the elegant syntax of Eloquent to the point where I'm asking too much?
Note: I am aware that you can usually get round this by defining relationships in the other direction (e.g. accessing the address table first) but this is not always ideal and not what i am asking.
Thanks in advance for any help.
At this point, there is no means by which you can filter primary model based on a constraint in the related models.
That means, you can't get only Users who have user_address.town = 'Smallwille' in one swipe.
Personally I hope that this will get implemented soon because I can see a lot of people asking for it (including myself here).
The current workaround is messy, but it works:
$products = array();
$categories = Category::where('type', 'fruit')->get();
foreach($categories as $category)
{
$products = array_merge($products, $category->products);
}
return $products;
As stated in the question there is a way to filter the adresses first and then use eager loading to load the related users object. As so:
$addressFilter = Addresses::with('Users')->where('town', $keyword)->first();
$users= $addressFilter->users;
of course bind with belongsTo in the model.
///* And in case anyone reading wants to also use pre-filtered Users data you can pass a closure to the 'with'
$usersFilter = Addresses::with(array('Users' => function($query) use ($keyword){
$query->where('somefield', $keyword);
}))->where('town', $keyword)->first();
$myUsers = $usersFilter->users;
let me at first state that we use php and postgre database. In our project we have decided not to use any ORM due to its overload of sql queries and we are taking the oposite way.
Imagine you have a select from several tables, lets say joined on id columns. For instance:
tables: users(id, name), items(id, name, description), comments(user_id, item_id, text, rating)
So basically you have a table of users, a table of some items and a table of comments which are related to one user and one item.
You create two objects - user and item representing their table row. And then you want to create a comment object. In an ORM it would contain objects user and item and they would load themselves with their queries, but that would be two queries and you re thinking...hm but I can select that data with a single query...but how?
Imagine that you have this select:
SELECT * FROM comments JOIN users ON comments.user_id = users.id JOIN items ON comments.item_id = items.id
(you can also imagine a WHERE clause with specified item id or user id etc.)
So how would you split the result of such a select into this class structure, lets say you want a list of comment objects:
user
item
comment (contains references to user and item object)
So far our theoretical solution was to prefix name of the columns with fixed prefixes :) and then propagating the result into the object structure and each objects takes what it needs from the select. Any other solutions? Lets say more sophisticated?
Thanks for any ideas
PS: obviously I have used a very simple example, but try to imagine that the problem is far larger and the structure far more complex
First of all, you might benefit from looking at the Data Mapper pattern. A simple use-case with would look like this:
$user = new User;
$mapper = new UserMapper( $db );
$user->setName('foobar');
$mapper->fetch( $user );
if ( $user->isBanned() )
{
throw new Exception('get out !');
}
$user->setLastActive( time() );
$mapper->store( $user );
As for the single query with data: that's not the important part. You just ALIAS it as required (oh .. and i hope you are not using the * for selecting rows). The important bit is creating an object graph from selected data. That where you use builders/factories.
//the rest of PDO-related code
$data = $statement->fecth(PDO::FETCH_ASSOC);
$comment = $commentFactory->build($data);
Where $commentFactory is instance of CommentFactory:
class CommentFactory
{
public function build( $params )
{
$author = new User;
$subject = new Item;
$comment = new Comment( $author, $subject );
$author->setId( $params['user_id']);
$author->setName( $params['user_name']);
$subject->setId( $param['item_id']);
$comment->setContent( $param['content']);
return $comment;
}
}
Additionally with setup like this, you can easily change how $comment is made, just by changing what class is the $commentFactory an instance of.