Laravel - query builder - left join polymorphic relationship, distinct only - php

So I'm using Vue 2.0 and Laravel 5.3 to create an application.
I've implemented my own sortable table with Vue, using the built-in pagination provided by Laravel.
Everything's working perfect - except, I'm trying to left join a "media" polymorphic table, so I can show an image in my table.
To enable sorting, I've had to use the query builder, as you can't sort on a relationship using Eloquent (AFAIK).
I define my relationships like:
$inventories = $inventories->leftjoin('users as inventory_user', 'inventories.user_id', '=', 'inventory_user.id');
$inventories = $inventories->leftjoin('categories as category', 'inventories.category_id', '=', 'category.id');
$inventories = $inventories->leftjoin('inventories as parent', 'inventories.parent_id', '=', 'parent.id');
Which work great. However, how exactly do I left join a polymorphic relationship, without repeating (duplicating) any of the rows?
I've got this:
$inventories = $inventories->leftJoin('media', function($q) {
$q->on('media.model_id', '=', 'inventories.id');
$q->where('media.model_type', '=', 'App\Models\Inventory');
});
Which does show media (if it has a relation in the polymorphic table). However, if for instance I have 5 images (media) for 1 particular inventory item, my query repeats the inventory for however media there are, which in my case it's repeated 5 times.
This is my select query:
$inventories = $inventories->select(DB::raw("
inventories.id as inventory_id,
inventories.name as inventory_name,
inventories.order_column as inventory_order_column,
category.id as category_id,
category.name as category_name,
parent.name as parent_name,
parent.id as parent_id,
inventories.updated_at as inventory_updated_at,
media.name
"));
I think I need to use a groupBy, but I'm not sure where I define it. If I do
$inventories = $inventories->groupBy('inventories.id');
I get
SQLSTATE[42000]: Syntax error or access violation: 1055 'test.inventories.name' isn't in GROUP BY...
Has anyone been in a similar situation or know where to put my groupBy to show 1/distinct inventory items?
EDIT:
I was able to fix by using:
$inventories = $inventories->leftJoin('media', function($q) {
$q->on('media.model_id', '=', 'inventories.id');
$q->where('media.model_type', '=', 'App\Models\Inventory');
$q->orderBy('media.order_column', 'asc');
$q->groupBy('model.model_id');
});
and then setting strict => false in my database.php.

As the error states this issue occurs when name column not being in GROUP BY clause.
To solve this change
'strict' => true,
In mysql configuration under connections in your config/database.php file to
'strict' => false,
Additionally I would highly encourage to use polymorphic relations over left join with eloquent.

I think you'll add it here:
$categories = $categories->select(DB::raw("
inventories.id as inventory_id,
inventories.name as inventory_name,
inventories.order_column as inventory_order_column,
category.id as category_id,
category.name as category_name,
parent.name as parent_name,
parent.id as parent_id,
inventories.updated_at as inventory_updated_at,
media.name
"))->groupBy('inventories.id');

Related

Laravel Eloquent join with IN clause

I'm currently trying to use the Eloquent query builder to create a join with multiple clauses, one of which being an IN clause.
The type of query I would like to create would be
SELECT * FROM trusts t
LEFT JOIN trust_group tg ON tg.trust_id = t.id
AND tg.group_id IN (1,2,4)
I've tried
->leftJoin('trust_group', function($join) {
$join->on('trust_group.trust_id', '=', 'trusts.id');
$join->on('trust_group.group_id', 'IN', [1,2,4]);
})
which results in
and `trust_group`.`group_id` = `IN`
and I've also tried
->leftJoin('trust_group', function($join) {
$join->on('trust_group.trust_id', '=', 'trusts.id');
$join->on(DB::raw('trust_group.group_id IN (1,2,4)'));
})
but this results in a query containing something along the lines of
and trust_group.group_id IN (1,2,4) = ``
(Obviously those group IDs are for example purposes, and would by dynamic)
Can Eloquent support IN clauses on joins?
This is only part of a pretty large query, so would prefer to use the join rather than use a whereIn
As this is kinda deadlock at the moment, I am posting this as an answer until this is officially PRed. Unfortunately joining with an In clause is not yet supported officially. There are some discussions in this closed thread
You can use it as raw query :
<?php
$results = DB::select("
SELECT * FROM trusts t
LEFT JOIN trust_group tg ON tg.trust_id = t.id
AND tg.group_id IN (?)", $groupIds);
Also there is Model::hydrate($array) method if you want to have eloquent collection back from result array.
This is a pretty old post, but for anyone searching, you can now simply use where functions to build more complex joins (tested in Laravel 7) :
->leftJoin('trust_group', function($join) {
$join
->on('trust_group.trust_id', '=', 'trusts.id')
->whereIn('trust_group.group_id', [1, 2, 4])
;
})

How Get Count in Join Query in Laravel

I want to convert the following SQL to Laravel.
SELECT COUNT('uc.*') AS count_down, bs.brand_name
FROM `user_activities` AS uc
JOIN brands AS bs ON uc.brand_id = bs.id
GROUP BY uc.brand_id
ORDER BY count_down DESC
LIMIT 5
But when doing this:
$top_donwload_list = DB::table('user_activities')
->leftJoin('brands', 'brands.id', '=', 'user_activities.brand_id')
->selectRaw('brands.*, brands.brand_name, brands.id, count(user_activities.action_type) as user_activitiesCount')
->groupBy('user_activities.brand_id')
->get();
I get this error:
SQLSTATE[42000]: Syntax error or access violation: 1055 'colorworld.brands.id' isn't in GROUP BY (SQL: select brands.*, brands.brand_name, brands.id, count(user_activities.action_type) as user_activitiesCount from user_activities left join brands on brands.id = user_activities.brand_id group by user_activities.brand_id)
I tried to set 'strict' => true, in database.php but I get the same error in Laravel 5.7.
Update:- database table
If I understood your question correctly, you are trying to find the count of user activities grouped by a brand's name. Also you want the top 5 records ordered by the ones with the most user activities.
So, the following should work:
$top_donwload_list = DB::table('user_activities')
->selectRaw("brands.brand_name, COUNT('user_activities.*') as user_activitiesCount")
->join('brands', 'brands.id', '=', 'user_activities.brand_id')
->groupBy('brands.brand_name')
->orderBy('user_activitiesCount', 'desc')
->take(5)
->get();
You can use Laravel Eloquent. I Assume that the relationship between brand and user_activities is one to many and Here, the "Brand" is the model of the brand entity.
$count = Brand::leftJoin
('user_activities', function($join_brands){
$join_brands-> on
('brands.id', '=','user_activity.brand_id');
)->orderBy('user_activities.count_down','desc ')
->groupBy('user_activities.brand_id')->count();

Laravel 5.4 - Eager Loading Child and Grandchild with Select Both

I am attempting to eager load a two deep relationship (child and grandchild) with selects on both descendants. However, when I add a addSelect() method to the grandchild it returns an empty array. What I have is the following:
$products = Category::with([
'products' => function($q){ $q->addSelect(['product_name', 'product_desc']);},
'products.productgroup' => function($q){ $q->addSelect(['price']);}
])->where('id', 1)->get();
This returns the Category and the product constraints, but the productgroup is returned as an empty array.
If I run the following:
$products = Category::with('products', 'products.productgroup')->where('id', 1)->get();
I get the expected return of all data including the productgroup data. It's only when I add the addSelect() method to the products or products.productgroup that it returns an empty array. Is there something i'm missing here?
I can't find any similar issues on stack or the laravel forums and i'm stumped.
EDIT: Including query from debugbar
The queries that came up in the debugbar are:
30.55ms
select * from `categories` where `id` = '1'
29.38ms
homestead
select `product_name`, `category_product`.`category_id` as`pivot_category_id`, `category_product`.`product_id` as `pivot_product_id` from `products` inner join `category_product` on `products`.`id` = `category_product`.`product_id` where `category_product`.`category_id` in ('1')
730μs
homestead
select `price` from `product_groups` where `product_groups`.`product_id` in ('')
I'm not 100% on how the query builder works under the hood with eager loading. It would appear that the product id isn't getting passed to the product_groups query when a addSelect() is present on either the product query or the productgroup query.
Ok so the answer to this is pretty simple. When selecting columns from the child and grandchild using addSelect(), I was not selecting the product.id or productgroup.product_id. The product.id and productgroup.product_id are obviously required in order to map the Grandchild to the Child node. It should be:
$products = Category::with(['products' => function($q){
$q->with(['productgroups' => function($g){
$g->addSelect(['id', 'price', 'product_id']);
}])->addSelect(['id', 'product_name', 'product_desc']);
}])->where('id', 1)->get();
Hope this helps anyone else who encounters a grandchild node of an eager load returning an empty array.
HT to Eddy The Dove
I used his nested formatting which reproduced the same issue, but was syntactically cleaner than my own way.
Try like this
$products = Category::with(['products' => function($q){
$q->with(['productgroup' => function($a) {
$a->addSelect(['price']);
}])->addSelect(['product_name', 'product_desc']);
})->where('id', 1)->get();
Try this if it works plz:
$product_ids = Category::find(1)->products()->pluck('id');
$prices = ProductGroup::whereIn('product_id', $product_ids)->pluck('price');

query to laravel query builder

i am building a some of news website an i want to know how many sub categories there in a category. I build this query. (it works on my database) now i want i convert it to laravel query builder but i can't get it working.
Raw query:
SELECT news_categories.id,
news_categories.name,
news_categories.description,
count(nc.id)
FROM happyalphen.news_categories
LEFT JOIN news_categories nc ON nc.category_parent = news_categories.id
WHERE news_categories.category_parent = 0
GROUP BY news_categories.id ;
what i have now
DB::table('news_categories')
->selectRaw('news_categories.id')
->join('news_categories subCat', 'news_categories.category_parent', '=', 'subCat.id')
->where('news_categories.category_parent','=',0)
->groupBy('news_categories.id')
->get();
Table layout
i found it (Thanks Jarek Tkacyk)
i was forgot the 'AS' in the join that laravel stop with the query.
DB::table('news_categories')
->selectRaw(' `news_categories`.`id`, `news_categories`.`name`, `news_categories`.`description`, `news_categories`.`color`, `news_categories`.`text_color`, count(`SubCat`.`id`) as SubCatCount ')
->join('news_categories as subCat', 'subCat.category_parent', '=', 'news_categories.id','left')
->where('news_categories.category_parent','=','0')
->groupBy('news_categories.id')
->get();

Laravel 4 select column from another table in subquery

I am attempting to do the equivalent of this:
select p.id, p.title, b.brand,
(select big from images where images.product_id = p.id order by id asc limit 1) as image
from products p
inner join brands b on b.id = p.brand_id
Here is where I am at now, but it of course doesn't work:
public function getProducts($brand)
{
// the fields we want back
$fields = array('p.id', 'p.title', 'p.msrp', 'b.brand', 'p.image');
// if logged in add more fields
if(Auth::check())
{
array_push($fields, 'p.price_dealer');
}
$products = DB::table('products as p')
->join('brands as b', 'b.id', '=', 'p.brand_id')
->select(DB::raw('(select big from images i order by id asc limit 1) AS image'), 'i.id', '=', 'p.id')
->where('b.active', '=', 1)
->where('p.display', '=', 1)
->where('b.brand', '=', $brand)
->select($fields)
->get();
return Response::json(array('products' => $products));
}
I don't really see anything in the docs on how to do this, and I can't seem to piece it together from other posts.
In "regular" SQL, the subquery is treated AS a column, but I am not sure how to string that together here. Thanks for any help on this.
I strongly recommend you to use Eloquent, instead of pure SQL. It's one of the most beautful things in Laravel. Two models and relations and it's done! If you need to use pure SQL like that, put it all in DB::raw. It's easier, simpler and (ironically) less messy!
With the models, you could use relations between the two tables (represented by the models itself) and say (so far I understood) that Brands belongs to Products, and Images belongs to Product. Take a look at Eloquent's documentation on Laravel. Probably will be more clearly.
Once the relations are done, you can only say that you wanna get
$product = Product::where(function ($query) use ($brand){
$brand_id = Brand::where('brand', '=', $brand)->first()->id;
$query->where('brand_id', '=', $brand_id);
})
->image()
->get();
That and a better look at Eloquent's documentation should help you to do the job.
P.S.: I didn't test the code before send it and wrote it by head, but i think it works.

Categories