Self Join in Eloquent - How To Call - laravel

I recently asked a question regarding a self join
SO
I got a great answer but I'm not sure how to call it.
I need to do this query:
SELECT t2.title FROM products t1, products t2
WHERE t1.id = $id
AND t2.color_id = t1.color_id AND
t2.id != $id
I now have this on my products model:
public function parent()
{
return $this->belongsTo(self::class, 'color_id');
}
public function children()
{
return $this->hasMany(self::class, 'color_id');
}
But how do I call this?
Product::with('children')->find(1);
The above gets the product with id 1 but also gets children that have a color_id of 1, I need to get children who have a color_id the same as the id as product 1.
eg.
Products
id | title | color_id
1 dvd 2
When I select row 1 it should select all other products with a color_id of 2.

I believe your relations are not the way they're supposed to be. Usually it's one column (foreign key - color_id in your case) having a value of the other one (usually primary key - id in your case).
What you have is basically a value the records share or a "category". So your products are not "children" but rather siblings (have the same parent color).
Since with method is not build as a JOIN statement but as eager loading (separate query) you can do that manually.
Probably the most straight forward way:
$product1 = Product::find(1);
$children = Product::where('id', '<>', $product1->id)->
where('color_id', $product1->color_id)->
get();
You can add select('title') to the second "builder" to get only title but that would not be your model anymore. Or you can use lists('title') to extract only titles if that's what you need.
UPDATE:
If you decide you need the JOIN after all I'd suggest going with raw query builder and leave the Eloquent out of it:
$res = DB::table('products as t1')->
select('t2.title')->
join('products AS t2', 't2.color_id', '=', 't1.color_id')->
where('t1.id', 1)->
where('td2.id', '<>', 't1.id')->
get();
I believe it should build something similar to what you need.

You can try this way:
// Category.php
public function children()
{
return $this->hasMany(Category::class, 'parent_id');
}
public function parent()
{
return $this->belongsTo(Category::class, 'parent_id');
}
// To access
$category->children; // sub-categories collection
$category->parent; // parent instance
based on : https://laracasts.com/discuss/channels/eloquent/self-join-in-eloquent-model?page=1

You might want to do as follow :
$id = 1;
$product = Product::with('children')->whereHas('children', function($q) use ($id)
{
$q->where('color_id', $id);
})->find($id);

See Advanced Join Clauses and adapt from my example here.
It took me a long time to wrap my head around Laravel joins.
This:
$postIds = DB::table('comments as t1')
->select('t1.*')
->leftJoin('comments as t2', function ($join) {
$join->on('t1.postId', '=', 't2.postId')
->on('t1.created_at', '<', 't2.created_at');
})
->where('t2.id', '=', null)
->orderBy('t1.created_at', 'DESC')
->simplePaginate(20)
->pluck('postId');
seems to be the way to get an array of the postIds that would be revealed by this:
SELECT t1.*
FROM comments a
LEFT OUTER JOIN comments b
ON t1.postId = t2.postId
AND t1.created_at < t2.created_at
WHERE t2.id IS NULL
ORDER BY t1.created_at DESC

SELECT subcat.CategoryName as SubCat cat.CategoryName as Category FROM bn_bas_categories cat, bn_bas_categories subcat WHERE cat.CategoryID = subcat.ParentID;

Related

laravel eloquent with pivot and another table

I have 4 table categories, initiatives, a pivot table for the "Many To Many" relationship category_initiative and initiativegroup table related with initiatives table with initiatives.initiativesgroup_id with one to many relation.
With pure sql I retrive the information I need with:
SELECT categories.id, categories.description, initiatives.id, initiatives.description, initiativegroups.group
FROM categories
LEFT JOIN category_initiative ON categories.id = category_initiative.category_id
LEFT JOIN initiatives ON category_initiative.initiative_id = initiatives.id
LEFT JOIN initiativegroups ON initiatives.initiativegroup_id = initiativegroups.id
WHERE categories.id = '40'
How can I use eloquent model to achieve same results?
Since you have such a specific query touching multiple tables, one possibility is to use query builder. That would preserve the precision of the query, retrieving only the data you specifically need. That would look something like this:
$categories = DB::table('categories')
->select([
'categories.id',
'categories.description',
'initiatives.id',
'initiatives.description',
'initiativegroups.group',
])
->leftJoin('category_initiative', 'categories.id', '=', 'category_initiative.category_id')
->leftJoin('initiatives', 'category_initiative.initiative_id', '=', 'initiatives.id')
->leftJoin('initiativegroups', 'initiatives.initiativegroup_id', '=', 'initiativegroups.id')
->where('categories.id', '=', 40)
->get();
In your models define the relationships:
Category.php model
public function initiatives()
{
return $this->belongsToMany('App\Initiative');
}
Initiative.php model (If has many categories change to belongs to many)
public function category()
{
return $this->belongsTo('App\Category');
}
Then maybe change your initiativegroup -> groups table, and then create a pivot table called group_initiative. Create model for group. Group.php and define the relationship:
public function initiatives()
{
return $this->belongsToMany('App\Initiative');
}
Then you can also add the following relationship definition to the Initiative.php model
public function group()
{
return $this->belongsTo('App\Group');
}
That should get you started.
for the record..
with my original relationship, but changing table name as alex suggest, in my controller:
$inits = Category::with('initiative.group')->find($id_cat);
simple and clean

Laravel Pivot Table, Get Room belonging to users

The structure of my pivot table is
room_id - user_id
I have 2 users that exist in the same room.
How can I get the rooms they both have in common?
It would be nice to create a static class to have something like this.
Room::commonToUsers([1, 5]);
Potentially I could check more users so the logic must not restrict to a certain number of users.
Room::commonToUsers([1, 5, 6, 33, ...]);
I created a Laravel project and make users, 'rooms', 'room_users' tables and their models
and defined a static function in RoomUser Model as below :
public static function commonToUsers($ids)
{
$sql = 'SELECT room_id FROM room_users WHERE user_id IN (' . implode(',', $ids) . ') GROUP BY room_id HAVING COUNT(*) = ' . count($ids);
$roomsIds = DB::select($sql);
$roomsIds = array_map(function ($item){
return $item->room_id;
}, $roomsIds);
return Room::whereIn('id', $roomsIds)->get();
}
in this method, I use self join that the table is joined with itself, A and B are different table aliases for the same table, then I applied the where condition between these two tables (A and B) and work for me.
I hope be useful.
I don't know the names of your relations, but I guess you can do like this :
$usersIds = [1, 5];
$rooms = Room::whereHas('users', function($query) use ($usersIds) {
foreach ($usersIds as $userId) {
$query->where('users.id', $userId);
}
})->get();
It should work. whereHas allows you to query your relation. If you need to have a static method, you can add a method in your model.
There might be a more efficient way but laravel collection does have an intersect method. You could create a static function that retrieves and loop through each object and only retain all intersecting rooms. something like this
public static function commonToUsers($userArr){
$users = User::whereIn('id',$userArr)->get();
$rooms = null;
foreach($users as $user){
if($rooms === null){
$rooms = $user->rooms;
}else{
$rooms = $rooms->intersect($user->rooms);
}
}
return $rooms;
}
This code is untested but it should work.
Room has many users, user has many rooms, so you can find the room which have those two users.
If your pivot table's name is room_users, then you can easily get the common room like this:
public static function commonToUsers($user_ids) {
$room = new Room();
foreach($user_ids as $user_id) {
$room->whereHas('users', function($query) use ($user_id) {
$query->where('room_users.user_id', $user_id);
});
}
return $room->get();
}
This code will convert to raw sql:
select *
from `rooms`
where exists (
select * from `rooms` inner join `room_users` on `rooms`.`id` = `room_users`.`room_id` where `rooms`.`id` = `room_users`.`room_id` and `room_users`.`user_id` = 1
)
and exists
(
select * from `rooms` inner join `room_users` on `rooms`.`id` = `room_users`.`room_id` where `rooms`.`id` = `room_users`.`room_id` and `room_users`.`user_id` = 5
)

How can i make inner join in method instead of controller?

I have a two tables orders and product. Relation is One to One. In my order class i created method product
public function product() {
return $this->hasOne('App\Product', 'key_id', 'key_id');
}
Now in controller i want select rows where id is not null that's why i wanna use InnerJoin
Order::where('order_id', 7)
->join('products', 'products.key_id', 'orders.key_id')
->get();
That is fine but i would like get
Order::where('order_id', 7)->get();
and all join move to method like:
public function product() {
return $this->hasOne('App\Product', 'key_id', 'key_id')
->join('products', 'products.key_id', 'orders.key_id')
}
How can i do this ?
You have defined the relations between both the models, so If you fetch order with the following query:
Order::where('order_id', 7)->with('product')->get();
Remove the join line from product function i.e. ->join('products', 'products.key_id', 'orders.key_id')
After doing this, dump and die the $order, there will be an key pair with product and its values.

Laravel 5.5: How to get top selling items of a given shop?

My tables are like:
shops
[id]
inventories
[id, shop_id]
orders
[id, shop_id]
order_item
[order_id, inventory_id, quantity]
Models:
//Shop
class Shop extends Model
{
public function inventories()
{
return $this->hasMany(Inventory::class);
}
public function orders()
{
return $this->hasMany(Order::class);
}
}
//Inventory
class Inventory extends Model
{
public function shop()
{
return $this->belongsTo(Shop::class);
}
public function orders()
{
return $this->belongsToMany(Order::class, 'order_items')
->withPivot('quantity');
}
}
//Order
class Order extends Model
{
public function shop()
{
return $this->belongsTo(Shop::class);
}
public function inventories()
{
return $this->belongsToMany(Inventory::class, 'order_items')
->withPivot('quantity');
}
}
Now I want 5 top selling inventories of a given shop, What will be the best possible way to do that?
I'm on Laravel 5.5
select s.id,sum(oi.quantity) as total from munna.shops as s
join munna.inventories as iv on s.id=iv.shop_id
join munna.orders as o on iv.shop_id=o.shop_id
join munna.order_items as oi on o.id=oi.order_id
group by s.id
order by total desc limit 5
First, by looking at your tables on order_item, the order_id and inventory_id will bellong to the same shop for sure? I guess yes because if not you would have 2 different shops with same top order. I dont know why you are doing it like this but it's a bit confusing can't figure out why but I would try this:
public function topOrders()
{
$items = DB::table('shops')
->join('orders', 'shops.id', '=', 'orders.shop_id')
->join('inventories', 'shops.id', '=', 'inventories.shop_id')
->join('order_items', 'orders.id', '=', 'order_items.order_id')
->orderBy('quantity', 'desc')
->take(5)
->get();
return $items;
}
What I wrote should select everything from all 3 rows, if you want to select only the items or whatever you want to select you can specify it adding a select clause
Though this was my own question I found the solution on my own and I want to share the solution with the community. I wanted to solve it using Eloquent because I need the model on the view and didn't want to query the model again.
Inventory::where('shop_id', \Auth::user()->shop_id)
->select(
'inventories.*',
\DB::raw('SUM(order_items.quantity) as quantity')
)
->join('order_items', 'inventories.id', 'order_items.inventory_id')
->groupBy('inventory_id')
->get();
I hope this'll help someone with similar issue. Thanks

Laravel Eloquent ORM eager loading. Relation incorrectly returned as null

I have an Eloquent ORM relationship defined as follows:
ProductConfiguration:
public function product()
{
return $this->belongsTo('Excel\Products\Product');
}
public function currency()
{
return $this->belongsTo('Excel\Currencies\Currency');
}
Product
public function productConfigurations()
{
return $this->hasMany('Excel\Products\ProductConfiguration');
}
public function productType()
{
return $this->belongsTo('Excel\Products\ProductType');
}
I expect that if I do the following that I will load all product configurations of a specified product type, with the related products, nested product type details and the product configuration currency
$results = ProductConfiguration::with(
array(
'product' => function($query) use ($product_type) {
$query->where('product_type_id' , $product_type);
},
'product.productType',
'currency'
)
)
->get();
however the returned collection has 'product' set to NULL. the Currency Relationship is there, but the product relationship is not. I can see the outputted SQL queries and the query that selects the products retrieves the correct products if I paste it directly into my sql editor
select * from `products`
where `products`.`id` in ('12', '13')
and `product_type_id` = '1'
Am I correct to think that the results from this query should be included in my collection, or is there some obvious flaw in my thinking?
I think you don't want to achieve that. Now what you get is getting all ProductConfiguration with products that are only of certain_type.
So in case you have some configuration that has other type for product you will get null because you limited results from product to only the one that has certain product type.
I might be wrong, but you probably wanted to get those ProductConfiguration that belongs to Product that is type of certain_type. In this case you should use whereHas:
$results = ProductConfiguration::
with('product', 'product.productType', 'currency')->
whereHas('product', function($q) use ($product_type)
{
$q->where('product_type_id', '=', $product_type);
})->get();
I hate to post this as an answer but since i don't have enough rep to comment so try this first:
$results = ProductConfiguration::with('product')->get();
dd($results->toArray());
See what you get, if you get some data, try this
$results = ProductConfiguartion::with(array('products' => function($query){
$query->where('product_type_id' , $product_type);
})->get();
dd($results);
See what you get, if you get null: your $product_type variable may be something you didnt expect, so try dd($product_type) to make sure its what your expecting.

Resources