I've been struggling with this for a while. I've been reading about Eloquent and really trying hard to understand it, but nothing I try seems to work.
This is my MYSQL query that works perfectly:
SELECT jobs.id,
jobs.title,
application_statuses.description,
fee,
candidates.f_name,
candidates.l_name,
clients.name
FROM jobs
JOIN applications ON jobs.id = job_id
JOIN candidates ON candidate_id = candidates.id
JOIN application_statuses ON applications.status_id = application_statuses.id
JOIN clients ON client_id = clients.id
WHERE applications.status_id IN (3,4,5)
ORDER BY applications.status_id desc;
I've created models in Laravel but I can't get the results that I get with this raw query.
If possible could someone point me to somewhere where this sort of thing is explained (ie. multiple joins, and outputting data from all of the tables.), or help me with my predicament if it's not too much work?
I'm trying to do it without DB:RAW
Note. There's not much point me giving you my models and query as it's just a massive mess that I keep deleting and recreating.
Here is my code:
class PivotApplication extends Eloquent {
protected $guarded = ['id'];
protected $table = 'applications';
public function applicationStatus()
{
return $this->belongsTo("ApplicationStatus");
}
public function candidates()
{
return $this->belongsToMany("Candidate", "candidate_id");
}
}
class ApplicationStatus extends Eloquent {
protected $guarded = ['id'];
protected $table = 'application_statuses';
public function pivotApplication()
{
return $this->belongsToMany("PivotApplication");
}
}
class PipelineController extends \BaseController {
function __construct(Job $job, ApplicationStatus $application_status, PivotApplication $pivotApplication)
{
}
public function index()
{
//$appStatuses = ApplicationStatus::whereIn
$applications = PivotApplication::with("applicationStatus")->whereIn("status_id", [3,4,5])
//->applicationStatus()
->get();
//->toSql();
//dd(DB::getQueryLog());
return $applications;
die();
Sometimes an ORM like Eloquent will not achieve what you want from a raw SQL statement. Sometimes you need to approach the solution different from an ORM vs SQL.
Looking at your query - it would probably make sense to have 2-3 different ORM models doing different aspects of your query, and linking them via relationships.
However depending on the functionality - there is not necessarily a problem using a SQL statement for a specific use case in your program.
Related
I am trying to understand how to effectively use Eloquent relationships to have some high level functions in the model.
I have a subscription app with 2 tables, 'users' and 'subscriptions'.
This is a legacy system so I cannot just change things in any way I want.
Table users (model App\User)
id
email
active (0/1)
join_date
address etc
phone
Table subscriptions (model App\Subscription)
id
user_id
box_id (what the person is subscribed to get)
amount
Users are marked active or not active.
I would like to have a static method on the Subscription model that will give me all the active subscriptions. This data is then fed into other parts of the application.
This is derived by joining subscriptions to users and filtering based on the active column.
The query is like this:
SELECT users.*, subscriptions.*
FROM subscriptions
JOIN users ON users.id = subscriptions.user_id
WHERE users.active = 1
Subscription model
class Subscription extends Model
{
public static function allActive()
{
// This works except it doesn't use the eloquent relationship
return static::where('users.active', 1)
->join('users', 'users.id', '=', 'subscriptions.user_id')
->select('users.*','subscriptions.*')
->get();
}
public function user()
{
return $this->belongsTo(User::class);
}
}
User model
class User extends Authenticatable
{
use Notifiable;
public function subscriptions()
{
return $this->hasMany(Subscription::class);
}
}
I would use it like this:
$subscriptions = \App\Subscription::allActive()->toArray();
print_r($subscriptions);
I have 2 questions.
How do I rewrite the allActive function to use the relationship I already defined? Any solution should generate SQL with a JOIN.
In the returned data, how do I separate the columns from the two separate tables so that it is clear which table the data came from?
Given the relationships you have wired up, to get only active subscriptions from the model class you will have to do it this way:
class Subscription extends Model
{
public static function allActive()
{
$activeSubcriptions = Subscription::whereHas('user', function($query){
$query->where('active', 1) //or you could use true in place of 1
})->get();
return $activeSubcriptions;
}
public function user()
{
return $this->belongsTo(User::class);
}
}
Thats working with closures in Laravel, quite an efficient way of writing advanced eloquent queries.
In the callback function you will do pretty much anything with the $query object, its basically working on the User model since you mentioned it as the first parameter of the ->whereHas
Note that that variable has to have EXACTLY the same name used in declaring the relationship
The above i suppose answers your first question, however its highly recommended that you do most of this logic in a controller file
To answer question 2, when you execute that get() it will return Subscription objects array so to access the info based on columns you will have to go like:
$subscriptions = \App\Subscription::allActive();
foreach($subscriptions as $subscription){
$amount = $subscription->amount; //this you access directly since we working with the subscription object
$box_id = $subscription->box_id;
//when accessing user columns
$email = $subscription->user->email; //you will have to access it via the relationship you created
$address = $subscription->user->address;
}
recently I am using PHP Laravel with PostgreSQL and I don't know how to convert this sql script to eloquent.
SELECT v_extensions.extension,
(
SELECT COUNT(v_xml_cdr.uuid) FROM v_xml_cdr WHERE v_xml_cdr.extension_uuid = v_extensions.extension_uuid AND start_stamp >= #startDate AND end_stamp <= #endDate
)
FROM v_extensions ORDER BY count DESC LIMIT 1
I am trying to do something like this, but I don't know how can I write that extension_uuid from XmlCdr is equal to extension from Extension. Also these columns are not linked by foreign key on database.
$call = Extension::select('extension', function(){
XmlCdr::where('extension_uuid', )
})->limit(10)->get();
found it a way how to do it with DB::select
$query = 'SELECT v_extensions.extension, (SELECT COUNT(v_xml_cdr.uuid) FROM v_xml_cdr WHERE v_xml_cdr.extension_uuid = v_extensions.extension_uuid) FROM v_extensions LIMIT 1';
$result = DB::select($query);
First, Eloquent is an ORM. To use it you'll have to create Model(s) which extend the Eloquent model model docs and define the relationships on them
relations docs
Assuming you did it, you should have something like this:
class ExtensionCDR extends \Illuminate\Database\Eloquent\Model
{
protected $primaryKey = "uuid"; // primary key column name
protected $table = "v_xml_cdr"; // by default Eloquent assumes your table has the same name as the class - more or less
// add relations like
public function extensions()
{
return $this->hasMany(Extensions::class); //further parameters may needed - see docs
}
}
class Extensions extends \Illuminate\Database\Eloquent\Model
{
protected $table = "v_extensions";
// add relations
}
With this you can do this (anywhere basically):
(I'm not sure about which table has the relevant timestamps)
// this is the case where the timestamps are on the related model
$result = ExtensionCDR::whereHas('extensions', function ($query) use($start, $end) {
return $query->whereDate('start_stamp', '>', $start)
->whereDate('start_stamp', '<', $end);
})->get();
I understand that this won't be a perfect (copy/pasta) answer, but I hope it will steer you in the right direction;
In case I misunderstood you completely, just use the Database module with raw queries like so: raw things
Is it possible to have a hasMany relationship on two columns?
My table has two columns, user_id and related_user_id.
I want my relation to match either of the columns.
In my model I have
public function userRelations()
{
return $this->hasMany('App\UserRelation');
}
Which runs the query: select * from user_relations where user_relations.user_id in ('17', '18').
The query I need to run is:
select * from user_relations where user_relations.user_id = 17 OR user_relations.related_user_id = 17
EDIT:
I'm using eager loading and I think this will affect how it will have to work.
$cause = Cause::with('donations.user.userRelations')->where('active', '=', 1)->first();
I don't think it's possible to do exactly what you are asking.
I think you should treat them as separate relationships and then create a new method on the model to retrieve a collection of both.
public function userRelations() {
return $this->hasMany('App\UserRelation');
}
public function relatedUserRelations() {
return $this->hasMany('App\UserRelation', 'related_user_id');
}
public function allUserRelations() {
return $this->userRelations->merge($this->relatedUserRelations);
}
This way you still get the benefit of eager loading and relationship caching on the model.
$cause = Cause::with('donations.user.userRelations',
'donations.user.relatedUserRelations')
->where('active', 1)->first();
$userRelations = $cause->donations[0]->user->allUserRelations();
Compoships adds support for multi-columns relationships in Laravel 5's Eloquent.
It allows you to specify relationships using the following syntax:
public function b()
{
return $this->hasMany('B', ['key1', 'key2'], ['key1', 'key2']);
}
where both columns have to match.
I'd prefer doing it this way:
public function userRelations()
{
return UserRelation::where(function($q) {
/**
* #var Builder $q
*/
$q->where('user_id',$this->id)
->orWhere('related_user_id',$this->id);
});
}
public function getUserRelationsAttribute()
{
return $this->userRelations()->get();
}
If anyone landed here like me due to google:
As neither merge() (as suggested above) nor push() (as suggested here) allow eager loading (and other nice relation features), the discussion is still ongoing and was continued in a more recent thread, see here: Laravel Eloquent Inner Join on Self Referencing Table
I proposed a solution there, any further ideas and contributions welcome.
You can handle that things with this smart and easy way .
$cause = Cause::with(['userRelations' => function($q) use($related_user_id) {
$q->where('related_user_id', $related_user_id);
}])->where('active', '=', 1)->first();
I'm trying to get the most popular hackathons which requires ordering by the respective hackathon's partipants->count(). Sorry if that's a little difficult to understand.
I have a database with the following format:
hackathons
id
name
...
hackathon_user
hackathon_id
user_id
users
id
name
The Hackathon model is:
class Hackathon extends \Eloquent {
protected $fillable = ['name', 'begins', 'ends', 'description'];
protected $table = 'hackathons';
public function owner()
{
return $this->belongsToMany('User', 'hackathon_owner');
}
public function participants()
{
return $this->belongsToMany('User');
}
public function type()
{
return $this->belongsToMany('Type');
}
}
And HackathonParticipant is defined as:
class HackathonParticipant extends \Eloquent {
protected $fillable = ['hackathon_id', 'user_id'];
protected $table = 'hackathon_user';
public function user()
{
return $this->belongsTo('User', 'user_id');
}
public function hackathon()
{
return $this->belongsTo('Hackathon', 'hackathon_id');
}
}
I've tried Hackathon::orderBy(HackathonParticipant::find($this->id)->count(), 'DESC')->take(5)->get()); but I feel like I made a big mistake (possibly the $this->id), because it doesn't work at all.
How would I go about trying to get the most popular hackathons which is based on the highest number of related hackathonParticipants?
This works for me in Laravel 5.3, using your example:
Hackathon::withCount('participants')->orderBy('participants_count', 'desc')->paginate(10);
This way it is ordered on the query and the pagination works nicely.
Edit: If using Laravel 5.2 or greater, use kJamesy's answer. It will likely perform a bit better because it's not going to need to load up all the participants and hackathons into memory, just the paginated hackathons and the count of participants for those hackathons.
You should be able to use the Collection's sortBy() and count() methods to do this fairly easily.
$hackathons = Hackathon::with('participants')->get()->sortBy(function($hackathon)
{
return $hackathon->participants->count();
});
Another approach can be by using withCount() method.
Hackathon::withCount('participants')
->orderBy('participants_count', 'desc')
->paginate(50);
Ref: https://laravel.com/docs/5.5/eloquent-relationships#querying-relations
I had similar issue and using sortBy() is not suitable because of pagination, exactly as Sabrina Gelbart commented in previous solution.
So I used db raw, here's simplified query:
Tag::select(
array(
'*',
DB::raw('(SELECT count(*) FROM link_tag WHERE tag_id = id) as count_links'))
)->with('links')->orderBy('count_links','desc')->paginate(5);
You can also use join operator. As Sabrina said, you can not use sortby at the db level.
$hackathons = Hackathon::leftJoin('hackathon_user','hackathon.id','=','hackathon_user.hackathon_id')
->selectRaw('hackathon.*, count(hackathon_user.hackathon_id) AS `count`')
->groupBy('hackathon.id')
->orderBy('count','DESC')
->paginate(5);
But this code takes all records from database. So you should paginate manually.
$hackathons = Hackathon::leftJoin('hackathon_user','hackathon.id','=','hackathon_user.hackathon_id')
->selectRaw('hackathon.*, count(hackathon_user.hackathon_id) AS `count`')
->groupBy('hackathon.id')
->orderBy('count','DESC')
->skip(0)->take(5)->get();
Referred from : https://stackoverflow.com/a/26384024/2186887
I needed to sum multiple counts and then use it to set order. Following query worked for me in Laravel 8.
$posts = Post::withCount('comments','likes')->orderBy(\DB::raw('comments_count + likes_count'),'DESC')->get();
You can use below code
Hackathon::withCount('participants')->orderByDesc("participants_count")->paginate(15)
Or if you even want ASC/DESC with single method
Hackathon::withCount('participants')->orderBy("participants_count", 'asc')->paginate(15)
I have 4 mysql tables, as the following:
Makes:
id - make_name
Models:
id - model_name - make_id
Trims:
id - trim_name - model_id
Forsale_Cars:
id - trim_id - year - price
in the Makes table I have ~700 records, so my question is, how can get a list of Makes which only have a child trim in the forsale table?
I have 20 records in the forsale table, I want to get the list of Makes for these cars.
I am using Laravel, so if anybody has achieved that previously using eloquent it will be great
Eloquent way:
// Makes that have forsale nested relation
Make::whereHas('models', function ($q) {
$q->whereHas('trims', function ($q) {
$q->has('forsales');
});
})->get(); // returns Eloquent Collection
Models with correct relations (hasMany can be replaced with hasOne if that's actual relation somewhere):
// Make model
public function models()
{
return $this->hasMany('CarModel');
}
// CarModel (as you can't use Model name)
public function trims()
{
return $this->hasMany('Trim');
}
public function make()
{
return $this->belongsTo('Make');
}
// Trim model
public function forsales()
{
return $this->hasMany('Forsale');
}
public function carModel()
{
return $this->belongsTo('CarModel');
}
// Forsale model
public function trim()
{
return $this->belongsTo('Trim');
}
You could use this query for the first question you asked. Not sure if the this completely answers your question.
SELECT * FROM Makes m left join Foresale_Cars fc on (m.id = fc.id) left join Trims t on (fc.trim_id = t.id) where t.trim_name = "child trim"
I assume you have all your models set up, e.g.:
class Makes extends Eloquent {
protected $table = 'makes';
public function models() {
return $this->hasMany('Model', 'makes_id');
}
}
and so on.. (you have to do this with all your models, of course)
Now, if you want to get all the cars for sale you'd simply chain some foreach loops:
foreach( $makes->models as $model ) {
foreach( $model->trims as $trim ) {
{{ $trim->forsale_cars }}
...
}
}
Edit: Yes you can use raw queries, of course, but using models and the power of eloquent is much more elegant and useful...
For more information on this topic: http://laravel.com/docs/eloquent#relationships