10

I currently am doing a raw sql query however this is causing issues with relationships and model boot methods.

Is it possible to do the following SQL query but with laravel eloquent models by relationship? Note all db tables have FK's defined, and relationships either HasOne or HasMany relationships.

    $timeBreakDown = DB::select(
        "SELECT
                Entries.`task_id`,
                Entries.`opportunity_id`,
                SUM(Entries.`total_duration`) as 'duration',
                Class.`class` as 'class',
                Subclass.`sub_class` as 'subclass'
            from entries Entries
                INNER JOIN `tasks` Task
                    ON task_id = Task.id
                INNER JOIN `task_class` Class
                    ON Task.`class_id` = Class.`id`
                INNER JOIN `task_subclasses` Subclass
                    ON Task.`subclass_id` = Subclass.`id`
                WHERE Entries.`opportunity_id` = '".$opportunity->id."'
                GROUP BY Entries.`task_id`"
    );

Models are

Entries
Tasks
Class
Subclass

How would I have to structure my models relationships to handle the above sql query?

enter image description here

4
  • May i ask why you prefer joins opposed to eloquent relations ? Commented Dec 11, 2021 at 20:21
  • I dont know how to handle it via relationship to do that query to be a candid answer. @Skywarth Commented Dec 11, 2021 at 20:44
  • Eloquent and joins do not go together. Do you mean query builder? Commented Dec 16, 2021 at 0:18
  • 1
    @kray You can try using this add-on package to use joins as relationship Eloquent Power Joins Commented Dec 27, 2021 at 13:06

8 Answers 8

3

You can write a query in this way:
Please check table names according to your database

DB:: table('table name')->join('tasks','task_id','=','tasks.id')
->join('task_class', 'tasks.subclass_id','=','task_class.id')
->join('task_subclasses','tasks.subclass_id','=', 'task_subclasses.id')
->selectRaw('entries.task_id,
            task_subclasses.opportunity_id,
            SUM(entries.total_duration) as duration,
            task_class.class as class,
            task_subclasses.sub_class as subclass') 
->where(['entries.opportunity_id'=>$opportunity->id])
->groupBy('enteries.task_id')->get();
Sign up to request clarification or add additional context in comments.

Comments

2

Models\Entries.php

<?php

namespace App\Models;


use Illuminate\Database\Eloquent\Model;

class Entries extends Model
{
    public function Tasks(){
        return $this->hasOne(Tasks::class);
    }
    public function Class(){
        return $this->hasMany(Classes::class);
    }
    public function SubClasses(){
        return $this->hasOne(SubClasses::class);
    }
}

Models\Tasks.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Tasks extends Model
{
    public function Entries(){
        return $this->belongsTo(Entries::class, "id", "task_id");
    }
}

Models\Classes.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Classes extends Model
{
    public function Entries(){
        return $this->belongsTo(Entries::class, "class_id", "id");
    }
}

Models\Subclasses.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class SubClasses extends Model
{
    public function Entries(){
        
        return $this->belongsTo(Entries::class, "id", "subclass_id");
        
    }
}

Query:

Entries::with([
        "Tasks",
        "Classes",
        "SubClasses"
    ])
    ->where("opportunity_id", $opportunity->id)
    ->groupBy("task_id")
    ->get();

Comments

0

Yes, You can do it with Eloquent I'll share an example with you I can't read your Mess Query sorry for this but I will suggest you to do this

Entries::with(['Tasks','Class','Subclass'])->get();

from this, you will get all objects from this array

Let just say

The class have a relation with another Model but not Entries table then the Eloquent is something like this

Entries::with(['Tasks','Class.Subclass'])->get();

hope its helpful for you

Comments

0

Might be something like this:

$timeBreakDown = Entries::select('entries.task_id, entries.opportunity_id', DB:raw('SUM(Entries.total_duration) as duration), task_class.class, task_subclasses.sub_class as subclass)
join('tasks', [['tasks.id', 'entries.task_id']])
join('task_class', [['task_class.id', 'entries.class_id']])
join('task_subclasses', [['task_subclasses.id', 'entries.subclass_id']])
->where('entries.opportunity_id', $opportunity->id)
->groupBy('entries.task_id')
->get();

Comments

0

Try this query:

$timeBreakDown = Entries::join('tasks', 'tasks.id', '=', 'entries.task_id')
            ->join('class', 'class.id', '=', 'entries.class_id')
            ->join('subclass', 'subclass.id', '=', 'entries.subclass_id')
            ->select(
                'entries.task_id',
                'entries.opportunity_id',
                \DB::raw('SUM(entries.total_duration) as duration'),
                'class.class',
                'subclass.sub_class as subclass')
            ->where('entries.opportunity_id', $opportunity->id)
            ->groupBy('entries.task_id')
            ->get();

And try dd($timeBreakDown->toSql()); to match with your Raw SQL query.

2 Comments

So when youre doing the join, are you joining by the model name, or the actual table name from the database. If so, wouldnt there be a cleaner way to handle that query instead of having static typed database names in a bunch of queries like this verses have one location to change a table name for maintainability or future changes by doing it within the model.
Looking for something more in the lines of what Mike Lucid answered, however his answer has the relationships mixed up a little bit. I reposted on my OP a screenshot of the db relationship.
0

From the official documentation. You can define relationships using the base database relationship type by adding the tasks method to the Entries model. The tasks method should call the hasOne method and return its result.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Entries extends Model
{
    /**
     * Get the phone associated with the user.
     */
    public function task()
    {
        return $this->hasOne(Tasks::class);
    }
}

In turn, the Tasks model will have an entry method with which we can determine the inverse of the hasOne relationship using the belongsTo method:

<?php
    
  namespace App\Models;
   
  use Illuminate\Database\Eloquent\Model;
    
  class Tasks extends Model
  {
      /**
       * Get the user that owns the phone.
       */
      public function entry()
      {
          return $this->belongsTo(Entries::class);
      }
  }

1 Comment

Look at the OP again. I added a screenshot of the db layout. I dont believe your answer is the solution, its close but I dont think its complete.
0

you need to just setup the relationships like this:

I am assuming that that a Class will have a subClass and a Class will also have Tasks and those Tasks have Entries.

Also do you not have a User model?

Class Model

class Class extends Model
{ 

  protected $with = ['entries', 'subclass', 'task'];

  public function entries()
  {
      return $this->hasManyThrough(\App\Models\Entries::class, \App\Models\Task::class);
  }

  public function subClass()
  {
      return $this->hasOne(\App\Models\subClass::class);
  }

  public function tasks()
  {
    return $this->hasMany(\App\Models\Task::class);
  }
}

Entry Model

class Entry extends Model
{ 

  protected $with = ['task'];

  public function task()
  {
    return $this->belongsTo(Task::class);
  }
}

SubClass Model

class SubClass extends Model
{ 

  protected $with = ['class'];

  public function class()
  {
    return $this->belongsTo(\App\Models\Class::class);
  }
}

Task Model

class Task extends Model
{ 

  protected $with = ['entries', 'class'];

  public function entries()
  {
      return $this->hasMany(\App\Models\Class::class);
  }

  public function class()
  {
    return $this->hasMany(\App\Models\Task::class);
  }
}

With all of that set up you should be good to do something like this fro wherever your at in the stack:

$entry = Entry::findOrFail('id');
$entry->task->class->subClass->name;

or

$class = Class::findOrFail($class->id);
$subclass_name = $class->subclass->name;
$entries = $class->tasks->entries;

3 Comments

Time Entries will have a Task by task_id, A Task is a combination lookup table for a Class and Subclass. So a Task is a Class & Subclass Combo A - B|C B - B|A etc etc. A time entry will have a user which has its own relationship thats not neccessary within this query. I should be able to get it via the relationship.
I added a ss of the db diagram. to the op
Do you mind updating your answer to the correct order of the relationships? @Mike Lucid
0

If you would have posted your models that would have been easier for us. But here is what I got from your raw query above.

$timeBreakDown = Entries::where('opportunity_id',$opportunity->id)->load('Tasks','Class.SubClass')->get();

You should read about Laravel Eloquent and relationships. Just for brief intro the difference between load and with used by Waleed is: Load is used for lazy loading of relationship data while with is used for eager loading.

Eager loading is all the data gets load as soon as the Eloquent queries the data while lazy loading loads the data when it is required.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.