Trying to avoid Subquery in the Select Statement : Laravel 5.1 - php

I am using Laravel 5.1. below is my Database query
\App\Models\Project\Bids\ProjectBid_Model
::with(['Project' => function($query){
$query->where('WhoCreatedTheProject', 16);
}])
->where('ProjectBidID', 1)
->where('FreelancerAwardedProjectStatusID', \App\ProjectStatus::InProgress)
->first()
What's the Question ?
In the below part, if there exists no record, then the Project Object will be null. This seems a sub query in the select statement. Is there any way to return overall no record if 'WhoCreatedTheProjectvalue does not match in the database ? I meant I am looking forInner Join`.
::with(['Project' => function($query){
$query->where('WhoCreatedTheProject', 16);
}])

You can do this this way (you will have one query):
\App\Models\Project\Bids\ProjectBid_Model
::selectRaw('projectbid_table.*')
->join('projects_table','projectbid_table.project_id','=','project_table.project_id')
->where('WhoCreatedTheProject',16);
->where('ProjectBidID', 1)
->where('FreelancerAwardedProjectStatusID', \App\ProjectStatus::InProgress)
->first();
You need to of course alter table names in join to match your values.
But answering the question, in your example any subquery won't be run. You are getting all the project bids (this is 1st query) and later in separate query you get all the projects for this bids for which WhoCreatedTheProject = 16 (this is 2nd query). So this what you showed is something different what you want to probably achieve.

Related

Laravel Group & Count by Eloquent Relationship

I know this question has be asked before here: Laravel Grouping by Eloquent Relationship but the answers are from 2016 and they seem not to work in 2019. Also my question is more simple, since I only need one relation level.
My Question
A
user has multiple items.
How to find how many items a user has of each item_type with one query?
This is what I tried:
A query like
User::with(['items' => function($q){
$q->groupBy('type');
});
returns this error:
Syntax error or access violation: 1055 Expression #1 of SELECT list
is not in GROUP BY clause and contains nonaggregated column 'items.id'
which is not functionally dependent on columns in GROUP BY clause;
this is incompatible with sql_mode=only_full_group_by
I tried to fix this error with the following query:
User::with(['items' => function($q){
$q->select('type', \DB::raw('count(*) as total'))
->groupBy('type');
});
However, this returns a collection of users where each user's item collection is empty.
Is it somehow possible to group by a relation in the query?
There is an error :
You are using $q as closure argument and inside you are using $query. Also sometimes I have faced issue where I had to pass the foreign key inside the relation query closure to get the results :
<?php
$userWithItems = User::with(['items' => function($q){
$q->select('type', \DB::raw('count(*) as total'), 'user_id')
->groupBy('type');
});
Try it once by removing user_id if it works then it's better. Secondly you can not select non aggregated columns in mysql when you have groupby. The option is disable only_full_group_by in mysql configurations. So mostl likely user_id will also fail unless you disable this configuration

Select rows using query builder in CakePHP based on the same end association but different relation to it

What I want to do is to get all rows related with user_id but in a different way.
First condition is to get all Books that are related with the User via Resources table where user_id is stored (in other words - Books owned by the User). Second condition is to get all Books that are related with the User through the Cities model again which is stored in the Resources table as well (Books that belong to Cities owned by the User).
I tried really a lot of things and I simply cannot make this two conditions work because I use JOIN (tried different combinations of innerJoinWith and leftJoinWith) on the same "end" model (User).
What I've done so far:
$userBooks = $this->Books->find()
->leftJoinWith("Resources.Users")
->leftJoinWith("Cities.Resources.Users")
->where(["Resources.Users" => 1])
->orWhere(["Cities.Resources.Users" => 1])
->all();
This of course does not work, but I hope you get the point about what I'm trying to achieve. The best what I was able to get with trying different approaches is the result of only one JOIN statement what is logical.
Basically, this can be separated into 2 parts which gives expected result (but I do not prefer it because I want it done with one query of course):
$userBooks = $this->Books->find()
->innerJoinWith("Resources.Users", function($q) {
return $q->where(["Users.id" => 1]);
})
->all();
$userBooks2 = $this->Books->find()
->innerJoinWith("Cities.Resources.Users", function($q) {
return $q->where(["Users.id" => 1]);
})
->all();
Also, before this I created an SQL script which works well and result is like expected:
SELECT books.id FROM books, cities, users_resources WHERE
(users_resources.resource_id = books.resource_id AND users_resources.user_id = 1)
OR
(users_resources.resource_id = cities.resource_id AND books.city_id = cities.id AND users_resources.user_id = 1)
This query works and I want to transfer it into ORM styled query in CakePHP to get both Books that are owned by the user and the ones that are connected with the User via Cities. I want somehow to separate these joins to individually filter data like I did in the SQL query.
EDIT
I've tried #ndm solution but the result is the same as where there is only 1 association (User) - I was still able to get data based on only one join statement (second one was ignored). Due to the fact I had to move on, I ended up with
$userBooks = $this->Books->find()
->innerJoinWith("Cities.Resources.Users"‌​)
->where(["Users.id" => $userId])
->union($this->Books->find()
->innerJoinWith("Resour‌​ces.Users")
->where([‌​"Users.id" => $userId])
)
->all();
which outputs correct result but not in very effective way (by union of 2 queries). I would really like to know the best way to approach this as this is a very common case (filtering by related model (user) that is associated with other models).
The ORM (specifically the eager loader) doesn't allow joining the same alias multiple times.
This can be worked around in various ways, the most simple one probaly being creating a separate association with a unique alias. For example in your ResourcesTable, create another association to Users with a different alias, say Users2, like:
$this->belongsToMany('Users2', [
'className' => 'Users'
]);
Then you can use that association in the second leftJoinWith(), and apply the conditions accordingly:
$this->Books
->find()
->leftJoinWith('Resources.Users')
->leftJoinWith('Cities.Resources.Users2')
->where(['Users.id' => 1])
->orWhere(['Users2.id' => 1])
->group('Books.id')
->all();
And don't forget to group your books to avoid duplicate results.
You could also create the joins manually using leftJoin() or join() instead, where you can define the aliases on your own (or don't use any at all) so that there are no conflicts, for more complex queries that can be a tedious task though.
You could also use your two separate queries as subqueries for conditions on Books, or even create a union query from them, which however might perform worse...
See also
Cookbook > Database Access & ORM > Query Builder > Adding Joins
CakePHP Issues > Improve association data fetching

Laravel 5.2 left join one to many only row with column of highest value

I am trying to do a left join using eloquent on a one to many relationship. I would only like to get the row with the highest value in the sort_order column.
So far my query looks like this:
Package::select('packages.*')
->leftJoin('package_routes', 'package_routes.package_id', '=', 'packages.id')
->leftJoin('package_route_items', function($join){
$join->on('package_route_items.package_route_id', '=', 'package_routes.id')
->where(???);
})->...//do more stuff to query here
I'm stuck on the where clause, if I should even be using a where at all.
Since i don't know all the query where conditions i'll try to simplify (you will probably have to adjust column names and syntax
add use DB; at beginning of your controller/model
$data = DB::table('packages')
->select('packages.*, package_route_items.company_id, package_routes.company_id')
->join('package_routes', 'package_routes.package_id', '=', 'packages.id')
->join('package_route_items.package_route_id', '=', 'package_routes.id')
->orderBy('packages.value_column', 'DESC')->first();
That is what i have understand so far from your description. It's not tested but i think it should work with probably minor editing.
Similar question like yours is here, and here (hope it helps you even more than mine answer).

Id Column Overwritten by Join

Using Laravel 4.2 & MySQL.
I have an applications table with an id and a fit_score_id column, and a fit_scores table with an id column. It's a basic "belongs to" relationship.
The following code:
$query = Application::join('fit_scores', 'applications.fit_score_id', '=', 'fit_scores.id');
$collection = $query->get();
...produces a collection of Application models with the id property set to the value of the fit_score_id. What am I doing to cause this?
I should note that it is necessary to do this join rather than simply using eloquent relations, because I'm going to want to order the results by a column on the fit_scores table. I don't believe this is possible using Eloquent without an explicit join.
The best way to solve this is by chaining the join method to a select method as following:
Application::select('*', \DB::raw("applications.id as appid"))
->join('fit_scores', 'applications.fit_score_id', '=', 'fit_scores.id')
->get();
Explained: The solution simply suggest that instead of thinking to prevent the behavior of overwriting the first id with the joined id, we can hook into the primary selection query (before joining) and change the label of the id column into something else (in this case 'appid'). By doing so, we end up with both the id of the parent table being labeled 'appid' and the id of the joined table being labeled 'id' again while they lives together on the final result.
I was able to find a possible solution using this answer:
Laravel 4 - JOIN - Same column name
Basically, since Laravel does not automatically prefix column names with table_name. for joined tables, we need to manually work around it by aliasing any conflicting column names in joins. Adding this select statement to my query did it:
->select(DB::raw("applications.*, fit_scores.*, applications.id as id"))
It depends on what you need but probably you can achieve it using eager loading. In case you need to mix joins and eager loading check this out. http://www.jmilan.net/posts/eager-loading-joins-in-laravel

Laravel query builder, use custom select when calling count()

I have three tables, one defining a many to many relationship between the other two.
table : collections
table : collection_question
table : questions
I am running this query to return the number of questions a collection has
$results = DB::table('collections')
->select(array(
DB::raw('count(collection_question.question_id) as question_count'),
'name',
'description'
))->leftJoin(
'collection_question',
'collections.id',
'=',
'collection_question.collection_id'
)->where('question_count', '>', 1)
->orderBy('question_count', 'asc')
->get();
This works fine as the select query is not tampered with by the query builder.
When I swap out get() for count() however the query builder replaces my select clause with select count(*) as aggregate which is understandable, however I loose the binding to question_count in the process and a SQL exception is thrown.
I've looked through the source code for Illuminate\Database\Query\Builder to try and find a solution but other than manually executing the raw query with a custom count(*) alongside my other select clauses I'm at a bit of a loss.
Can anyone spot a solution to this?
Instead of calling count() on a Builder object, I've resorted to creating my own count expression in addition to any other select clauses on the query rather than replacing them.
// This is in a class that uses Illuminate\Database\Query\Expression the clone
// isn't necessary in most instances but for my case I needed to take a snapshot
// of the query in its current state and then manipulate it further.
$query = clone($query);
$query->addSelect(new Expression("count(*) as aggregate"));
$count = (int) $query->first()->aggregate;

Categories