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.
I am trying to retrieve the thumb image path by joining the images table to the listing table. As such, I have the following query in my controller.
$listings = Listing::select('listings.*, images.path as image_path')
->where('listings.ownerid', '=', $ownerid)
->leftJoin('images', 'listings.thumbId', '=', 'images.id')->get();
After testing out the function, the query fails since laravel interprets the query as
select `listings`.`*, images`.`path` as `image_path` from `listings` left join `images` on `listings`.`thumbId` = `images`.`id` where `listings`.`ownerid` = 1)
Notice the asterisk (*) is joined with the ", images" word making it '*, images'.
The query works fine without laravel's odd typo. How does one fix this issue?
You need to do one change in your query. You are passing raw select fields so you need to use selectRaw() instead of select(). Like
$listings = Listing::selectRaw('listings.*, images.path as image_path')
->where('listings.ownerid', '=', $ownerid)
->leftJoin('images', 'listings.thumbId', '=', 'images.id')->get();
check by try above query.
I suggest you to use Laravel Eloquent Relationships feature. Since your code above is more like Query Builder rather than Eloquent. Let's see the example bellow:
You will have 2 Models, 1 for each table (listings, images):
App\Listing Model:
<?php
...
use App\Image;
class Listing extends Eloquent {
...
protected $table = 'listings';
// define Eloquent Relationship of Listing model to Image model
public function image() {
return $this->belongsTo(Image::class, 'thumbId');
}
...
}
App\Image Model:
<?php
...
use App\Listing;
class Image extends Eloquent {
...
protected $table = 'images';
...
// define Eloquent Relationship of Image model to Listing model
public function listings() {
return $this->hasMany(Listing::class, 'thumbId');
}
}
So how to get the data?
// get all listing data
$listings = Listing::all();
// loop through the data
foreach ($listing as $listing) {
dump($listing->id);
// because we have define the relationship, we can access the related data of image
dump($listing->image->path);
// call $this->image will return related Image model
dump($listing->image);
}
You can see Laravel official documentation for more example and explanation.
Hope it helps.
I am trying to setup Eloquent for a new API that we're working on. I am using relations in a model.
Some relations are complex and aren't really suitable for a quick chained Query Builder statement. For example, I am trying to return metrics and some of those metrics are complex. Such as counting the total clicks generated by a user (it's not just a simple COUNT(*)). Here is the code that I have now:
<?php
namespace App\Models\Eloquent;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
class Affiliate extends Model
{
protected $table = 'user';
public function profile()
{
return $this->belongsTo('App\Models\Eloquent\Profile', 'id');
}
public static function clicks()
{
$sql = "SELECT
user_id,
COUNT(*) / SUM(dummy_metric) AS total_clicks
FROM clicks
WHERE something = 'true'
AND another_thing > 100 # dummy filter for example only
GROUP BY user_id";
$rows = DB::select(DB::raw($sql));
// (PSUEDO CODE) THIS IS WHAT I'D LIKE TO DO IDEALY
return $this->belongsTo($rows, user_id);
}
Possible? I'd like to be able to write our own queries without relying on Query Builder all of the time, but I still want to be able to join the relation to Eloquent.
Assuming you have Clicks model defined, find below the eloquent version of your query.
Click::select(DB:raw("user_id, COUNT(*) / SUM(dummy_metric) AS total_clicks"))
->where(/*condition*/)->groupBy("user_id")->get();
Note:
1) Where method accepts an array of conditions, so you can add more than one condition in same method. Reference
Update
I think thius should be your clicks() method:
public function clicks(){
return $this->hasMany('App\Models\Eloquent\Click',/*relevant ids*/);
}
And, now where you want a count of clicks(in controller for example), you can use following query:
$user = User::find("user_id")->with('clicks')->get();
$clicks = $user->clicks()->count();
To make it more efficient, refer to this article on Tweaking Eloquent relations
Update 2:
You can use Accessor function to retrieve total count
Add following 2 methods in User model.(change clicksCount string to anything you need )
public function clicksCount(){
return $this->hasOne('App\Models\Eloquent\Click')
->select(DB:raw("user_id, COUNT(*) count"))
->groupBy("user_id");
}
public function getClicksCountAttribute(){
return $this->clicksCount->count();
}
Now, you can use $user->clicksCount; to get the count directly.
If you are using Laravel 5.4, you can use withCount() method to easily retrieve the count. Counting Related Models
Up until now, I've been doing this to order by a model's relationship:
$users = User::leftJoin('user_stats', 'users.id', '=', 'user_stats.user_id')
->orderBy('user_stats.num_followers')
->get();
Here's the User model:
class User extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'users';
public function stats()
{
return $this->hasOne('App\Models\UserStat');
}
}
Is there a better way to orderBy a model's relationship without having to use any joins? I'm using Laravel 5.2.
You could use Collection instead:
$users = User::with('stats')->get()->sortBy(function($user, $key) {
return $user['stats']['num_followers'];
})->values()->all();
https://laravel.com/docs/5.2/collections#method-sortby
As long as the column you want to order by is on a different table (eg. on a related model), you'll have to join that table in.
The Collection sort:
The proposal by crabbly does not save you the join. It only sorts the collection manually in php, after the data has been fetched using a join.
Edit: In fact, the with('stats') generates no join, but instead a separate SELECT * FROM user_stats WHERE ID IN (x, y, z, …). So well, it actually saves you a join.
Maintain a copy:
A way of truly saving the join would be to have a copy of num_followers directly in the users table. Or maybe store it only there, so you don't have to keep the values in sync.
I am trying to implement soft deleting concept.
Here is my object:
class Post extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'posts';
protected $softDelete = true;
...
Soft delete is on.
Now, if I 'delete' a post, it gets a 'deleted_at' timestamp:
The problem is, when I search or just use all() to display the posts, the soft deleted items appears there. What is wrong?
Sometimes, you will get the soft deleted table entries with get() even with eloquent and protected $softDelete = true;.
So to avoid this problem, use
...->whereNull('deleted_at')->get();
For example, this query will fetch all rows including soft deleted.
DB::table('pages')->select('id','title', 'slug')
->where('is_navigation','=','yes')
->where('parent_id','=',$parent_id)
->orderBy('page_order')
->get();
So the proper method is,
DB::table('pages')->select('id','title', 'slug')
->where('is_navigation','=','yes')
->where('parent_id','=',$parent_id)
->whereNull('deleted_at')
->orderBy('page_order')
->get();
The soft deleting feature works when using Eloquent. If you are querying the results with query builder you will eventually see all the records trashed and not trashed.
It is not clear in the current docs of Laravel 4, but seeing that the concept of soft deleting just appears under Eloquent ORM - Soft Deleting and not under Query Builder, we can only assume that: soft delete only works with Eloquent ORM.
There is a little trick using soft delete tables and queries in laravel:
When we create something like
$objCars = Car::where("color","blue");
The system executes something like that:
SELECT
*
FROM
cars
WHERE
deleted_at IS NULL
AND
"color" = 'blue'
So far, so good. But, when we apply the "orWhere" method, something funny happens
$objCars = Car::where("color","blue")->orWhere("color","red");
The system will execute something like that:
SELECT
*
FROM
cars
WHERE
deleted_at IS NULL
AND
"color" = 'blue'
OR
"color" = 'red'
This new query will return all the car where deleted_at is null and the color is blue OR if the color is red, even if the deleted_at is not null. It is the same behavior of this other query, what show the problem more explicitly:
SELECT
*
FROM
cars
WHERE
(
deleted_at IS NULL
AND
"color" = 'blue'
)
OR
"color" = 'red'
To escape from this problem, you should change the "where" method passing a Closure. Like that:
$objCars = Car::where(
function ( $query ) {
$query->where("color","blue");
$query->orWhere("color","red");
}
);
Then, the system will execute something like that:
SELECT
*
FROM
cars
WHERE
deleted_at IS NULL
AND
(
"color" = 'blue'
OR
"color" = 'red'
)
This last query, searches for all cars where deleted_at is null and where the color can be or red or blue, as we was want it to do.
I had the same problem and nothing here helped me.
My problem was in my construct, I forgot to call the parent constructor:
public function __construct()
{
parent::__construct();
//Rest of my code
}
Hope that help someone!
Laravel 5.2.44 added withoutTrashed() method to SoftDeletingScope. For example you can use something like this:
Post::withoutTrashed()->get();
Tested it in Laravel 5.6, You need to use SoftDeletes Trait in your model
use Illuminate\Database\Eloquent\SoftDeletes;
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Banners extends Model
{
use SoftDeletes;
//no need of this below line
//protected $softDelete = true;
}
and when you query
$banners = Banners::where('status', 1)->get();
it will not return soft deleted data.
me solved join table with eloquent on softdelete - laravel 8
$query = Model_table_1::join('user', 'user.id', '=', 'table_1.user_id')
->join('table_2', 'table_2.id', '=', 'table_1.table2_id')
->join('table_3', 'table_3.id', '=', 'table_1.table3_id')
->join('table_4', 'table_4.id', '=', 'table_3.table4_id')
->select('table_1.id','table_1.name','table_1.created_at',
'user.name','table_2.name2','table_3.name3','table_4.name4')
->get();
use where in datables
$query = Model_table_1::join('user', 'user.id', '=', 'table_1.user_id')
->join('table_2', 'table_2.id', '=', 'table_1.table2_id')
->join('table_3', 'table_3.id', '=', 'table_1.table3_id')
->join('table_4', 'table_4.id', '=', 'table_3.table4_id')
->select('table_1.id','table_1.name','table_1.created_at','user.name','table_2.name2','table_3.name3','table_4.name4')
->where('table_1.user_id', '=', Auth::user()->id)
->get();
use where in view
$query = Model_table_1::join('user', 'user.id', '=', 'table_1.user_id')
->join('table_2', 'table_2.id', '=', 'table_1.table2_id')
->join('table_3', 'table_3.id', '=', 'table_1.table3_id')
->join('table_4', 'table_4.id', '=', 'table_3.table4_id')
->select('table_1.id','table_1.name','table_1.created_at','user.name','table_2.name2','table_3.name3','table_4.name4')
->where('table_1.user_id', '=', Auth::user()->id)
->first();