Laravel - Select specific columns from joined relationship models using Eloquent - laravel

I'm trying to select specific columns from tables that I have joined using Eloquent.
I have 3 models
- Transaction
- Channel
- Merchant
Transactions links to Channel. It has a hasOne relationship.
Channel links to Merchant. It also has a hasOne relationship.
public function channel() {
return $this->hasOne(Channel::class, 'uuid', 'entityId');
}
public function merchant() {
return $this->hasOne('App\Merchant', 'uuid', 'sender');
}
I'm using eager loading so have the following in the Transaction model:
protected $with = ['channel'];
And Channel has:
protected $with = ['merchant']:
This the query I'm trying to convert into Eloquent but I'm unsure how to select columns when they belong to related models. What I don't get is that if the relationships have been defined, why can't I select columns from the other models without having to reuse joins or the with clause?
SELECT SUM(t.amount) AS amount,
m.name
FROM transactionsV2 t JOIN
channels c
ON t.entityId = c.uuid JOIN
merchants m
ON c.sender = m.uuid
WHERE t.paymentType = 'DB' AND
t.status = 1 AND
t.processing_time >= '2019-01-01' AND
t.processing_time < '2019-01-21'
GROUP BY m.name;

You could do something like protected $with = ['merchant:id,name']; or maybe use raw expressions like selectRaw('SUM(t.amount) AS amount, m.name)

You can try something like this :
Transaction::sum('amount')
->whereStuf(...)
->with(['channel.merchant' => function($query){
$query->select('name')
->groupBy('name');
}])->get();
The channel.merchant allows you to get the nested relation.

Related

Laravel hasOne Eloquent Relationship // Multiple queries instead of one?

I'm testing the speed of my application and I have multiple hasOne relationships.
For example, an Order has one status ( order is pending , order is shipped etc)
Order Model
<?php
namespace App\Models;
use App\Models\OrderStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
protected $primaryKey = 'order_id';
public function status() {
return $this->hasOne( OrderStatus::class , 'id' , 'order_status_id' );
}
}
OrderStatus Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class OrderStatus extends Model
{
protected $primaryKey = 'id';
protected $table = 'orders_status';
}
OrderController Both examples make two queries to the database
public function show ( $order_id ) {
Order::with('status')->where('order_id' , $order_id)->firstOrFail(),
}
public function otherShowExample ( Order $order ) {
$order->load('status');
return view( 'order.show' , [
'order' => $order,
'order_status' => $order->status
] );
}
With Join is just one query to the database
public function showOneQuery ( $order_id ) {
$order = Order::where('order_id' , $order_id)
->select('orders.*' , 'orders_status.orders_status_name' )
->join('orders_status' , 'orders_status.id' , '=' , 'orders.order_status_id')
->firstOrFail();
}
DB::listen for the OrderController#show and OrderController#otherShowExample
select * from `orders` where `order_id` = ? limit 1
select * from `orders_status` where `orders_status`.`id` = ? and `orders_status`.`id` is not null limit 1
In the OrderController#show, when trying to show to the user only one record, with the OrderStatus relationship, using the facade DB::listen I can see that are two queries made.
The question is : Is this the normal behavior of the hasOne relationship ? Is not better using the join() ? I'm doing something wrong ?
Yes. It's normal behavior of HasOne relationship (and any other).
Methods like with and load realize eager loading.
So, first of all, Eloquent will get an item or a collection of items from database. Then it will load relationships.
Yes, It's normal. In Laravel relationship, First laravel fetch a main model object and after that for each relationship it will fire individual query to load them. It's no matter with which type of relation we used and number of records we are processing. When we use a join with Model, it will fire a single query but a result set will be considered as a Order model instance, we can't identify a relationship data in that Order instance and if we try to check $order->status then it will fire a query for that.
In relationship, query will be simple one so it's easy to do indexing in a table but in a join query sometime it's difficult to do indexing to optimize a query and in that case, firing more number of queries will work faster then individual query. For multiple queries laravel will not initialize connection with DB as laravel initialize one time per request so it will not add that amount of extra time to initialize a connection.

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

Eloquent hasMany with foreign key on joint table

Assume this:
class List extends Model
{
public function items(){
return $this->hasMany(Items::class, 'c.class_id', 'class_id')
->rightjoin('items_classes as c', 'c.items_id', '=', 'items.id');
}
}
The problem is that Eloquent prepends items to foreign key field and the final query is:
SELECT * FROM items
RIGHT JOIN items_classes as c ON c.items_id = items.id
// here it is
WHERE items.c.class_id = 10
Even using DB::raw('c.class_id') didn't solve the problem.
If you notice the signature of hasMany relation method :
return $this->hasMany(Model::class, 'foreign_key', 'local_key');
Which means when Laravel will make the query, it will consider second argument foreign_key as a column of table defined in Model::class.
To simplify in your case :
return $this->hasMany(Items::class, 'c.class_id', 'class_id')->...
Leaving the rightjoin aside for a moment, Laravel is considering c.class_id as a foreign key of Item::class table which is indeed items table.
So the resultant query is :
SELECT * FROM items WHERE items.c.class_id = 10
Then when you add the right join, laravel just adds into the main query and makes it :
SELECT * FROM items
RIGHT JOIN items_classes as c ON c.items_id = items.id
WHERE items.c.class_id = 10
Laravel will not refer items_classes in the relation because you are relating List Model to Item::class and not ItemClass::class.
I am not sure about the data you need but see if you can use with like below :
class List extends Model
{
public function items(){
return $this->hasMany(Items::class, 'c.class_id', 'class_id');
}
}
List::with(['items', function($q){
return $q->->rightjoin('items_classes as c', 'c.items_id', '=', 'items.id');
}])->get();
Hope this gives you an idea how you can update your relationships to get desired query. If you add your table structure and data you want, I can update the answer with relationships for you.

Laravel - Eloquent "Has", "With", "WhereHas" - What do they mean?

I've found the concept and meaning behind these methods to be a little confusing, is it possible for somebody to explain to me what the difference between has and with is, in the context of an example (if possible)?
With
with() is for eager loading. That basically means, along the main model, Laravel will preload the relationship(s) you specify. This is especially helpful if you have a collection of models and you want to load a relation for all of them. Because with eager loading you run only one additional DB query instead of one for every model in the collection.
Example:
User > hasMany > Post
$users = User::with('posts')->get();
foreach($users as $user){
$users->posts; // posts is already loaded and no additional DB query is run
}
Has
has() is to filter the selecting model based on a relationship. So it acts very similarly to a normal WHERE condition. If you just use has('relation') that means you only want to get the models that have at least one related model in this relation.
Example:
User > hasMany > Post
$users = User::has('posts')->get();
// only users that have at least one post are contained in the collection
WhereHas
whereHas() works basically the same as has() but allows you to specify additional filters for the related model to check.
Example:
User > hasMany > Post
$users = User::whereHas('posts', function($q){
$q->where('created_at', '>=', '2015-01-01 00:00:00');
})->get();
// only users that have posts from 2015 on forward are returned
The documentation has already explained the usage, so I will use SQL to explain the methods.
Example:
Assuming there is an Order (orders) has many OrderItem (order_items) and you already built the relationship between them:
// App\Models\Order:
public function orderItems() {
return $this->hasMany('App\Models\OrderItem', 'order_id', 'id');
}
These three methods are all based on a relationship.
with
Result: with() return the model object and its related results.
Advantage: It is eager-loading which can prevent the N+1 problem.
When you are using the following Eloquent Builder:
Order::with('orderItems')->get();
Laravel change this code to only two SQL:
// get all orders:
SELECT * FROM orders;
// get the order_items based on the orders' id above
SELECT * FROM order_items WHERE order_items.order_id IN (1,2,3,4...);
And then Laravel merges the results of the second SQL query with the results of the first SQL by foreign key, finally returning the collection results.
So if you selected columns without the foreign_key in a closure, the relationship result will be empty:
Order::with(['orderItems' => function($query) {
// $query->sum('quantity');
$query->select('quantity'); // without `order_id`
}
])->get();
#=> result:
[{ id: 1,
code: '00001',
orderItems: [], // <== is empty
},{
id: 2,
code: '00002',
orderItems: [], // <== is empty
}...
}]
has
Has will return the model's object when its relationship is not empty.
Order::has('orderItems')->get();
Laravel changes this code to one SQL query:
select * from `orders` where exists (
select * from `order_items` where `orders`.`id` = `order_items`.`order_id`
)
whereHas
The methods whereHas and orWhereHas put where conditions on your has queries. These methods allow you to add customized constraints to a relationship constraint.
Order::whereHas('orderItems', function($query) {
$query->where('status', 1);
})->get();
Laravel changes this code to one SQL query:
select * from `orders` where exists (
select *
from `order_items`
where `orders`.`id` = `order_items`.`order_id` and `status` = 1
)

Update many records in a laravel relationship

I have 4 tables that I'm trying to work with in Laravel and I can't figure out how to use eloquent to execute a particular query. I want to update all orders that belong to a user (through product_id) and that have null payout_id.
This raw sql statement works but I'm not sure how to use eloquent for this..perhaps sync?
UPDATE order_items i JOIN products p on (i.product_id = p.id) SET i.payout_id = null where p.user_id = 3
User Model
Product Model
FK: user_id
Order Model
FK: product_id
FK: payout_id
Payout Model
I would really appreciate any help!
First define a function orders in User Model
public function orders()
{
return $this->hasManyThrough('Orders', 'Product','user_id','product_id');
}
User::where('id','=',$userid)->with(array('orders'=>function($query){
$query->where('payout_id','=',0)->update(array('payout_id'=>$updatevalue));
}))->first();
You need to create a model for your tables i and p, see http://laravel.com/docs/eloquent for full information on model creation.
In the model for i you would then create a relationship to p:
public function p()
{
return $this->('p','id','product_id');
}
You can then run your query as follows:
$results = i::with('p')
->where('user_id', '=', 3)
->update(array('payout_id' => null));

Resources