Moon
Moon

Reputation: 22565

How to query this with ORM?

I've using Kohana for a couple of weeks. One thing I noticed is that Kohana is missing eager loading (as far as I know). Let's say I have the following tables.

Subjects

id
name

Chapters

id
subject_id
name

Videos

id
chapter_id
name

When a user opens a subject page, I want to display all the chapters and videos. With ORM, I can do

$tutorials = ORM::factory('subject')->where('id','=', 1)->find();

foreach($tutorials as $tutorial) 
{
    $chapters = $tutorial->chapters->find_all();
    foreach($chapters as $chapter) 
    {
        $videos = $chapter->videos->find_all();
    }
}

The above code is not efficient since it makes too many queries.

I thought about using join or database query builder, but both of them do not return a model object as their results. I also looked into with(), but it seems like it only works with one-to-one relationship.

using join on an ORM object returns an OPM object, but it doesn't return the data from the joining tables.

What would be my best option here? I would like to minimize # of queries and also want to get ORM objects a result. Whatever it would be, should return all the columns from tutorials, chapters, and videos.

Upvotes: 1

Views: 1791

Answers (2)

AmazingDreams
AmazingDreams

Reputation: 3204

So I guess you want something like this.

$videos = ORM::factory('Video')
    ->join(array('chapters', 'chapter'), 'LEFT')->on('video.chapter_id', '=', 'chapter.id')
    ->join(array('subjects', 'subject'), 'LEFT')->on('chapter.subject_id', '=', 'subject.id')
    ->where('subject.id', '=', $id)
    ->find_all();

Come to think of it, if the video belongs_to chapter belongs_to subject, try the following using with():

$videos = ORM::factory('Video')
    ->with('chapter:subject')        // These are the names of the relationships. `:` is separator
                                     // equals $video->chapter->subject;
    ->where('subject.id', '=', $id)
    ->find_all();

With things like this it often helps to think 'backwards'. You need the videos on that subject so start with the videos instead of the subject. :)

EDIT: The drawback of the second function is that it is going to preload all the data, it might be shorter to write but heavier on the server. I'd use the first one unless I need to know the subject and chapter anyway.

Upvotes: 1

Vladimir Cherepinskiy
Vladimir Cherepinskiy

Reputation: 337

First of all, your code is excess. ORM method find() returns 1 Model_Subject object. See

$chapters = ORM::factory('subject', 1)->chapters->find_all();
foreach($chapters as $chapter) 
{
    $videos = $chapter->videos->find_all();
}

With DB builder you can make just 2 requests. First get array of all chapters ids:

$chapters = DB::select('id')
   ->from('chapters')
   ->where('subject_id', '=', '1')
   ->execute()
   ->as_array(NULL, 'id');

Second - get all videos by ids as Model_Video object

$videos = DB::select('id')
    ->from('videos')
    ->where('chapter_id', 'IN', $chapters)
    ->as_object('Model_Video') 
    ->execute()
    ->as_array();

Upvotes: 3

Related Questions