Get eloquent model and all its relationships with one query in Laravel - php

The problem
Is there a way to get an eloquent model and all its relationships using only one query?
Example scenario:
You have both Post and Comment Eloquent models.
You add their relationships in the model class using hasMany('Comment') and belongsTo('Post') respectively.
Now this is what i've been doing to retrieve both the post AND its comments:
$post = Post::find($id);
$post->comments;
return $post;
This returns a beautiful jsonobject. The problem is, this way, i would be using two queries. That's not so great.
Workaround
Two alternatives come to mind:
Using joinstatements in order to make the query i want. But Eloquent is so much more elegant.
Leveraging the Cache class in order to make even fewer queries in the future (this, i will do anyway later on).
Any ideas?

In eloquent you can do it like this
$post = Post::with('comments')->find($id);
return $post;
Edit:
This will not run the query with join in mysql but its just a single query in Eloquent.

Related

Laravel - Differences between two solutions of a method in a controller

A stack overflow user answered me two ways to return each person's furniture in a 'one to many' relationship. This worked well.
My question is to know the difference in the two ways. The advantages and disadvantages of each way.
IMPORTANT: Laravel Version: 5.8
First solution:
public function showPersonFurnitures($id) {
$person = Person::with('furnitures')->findOrFail($id);
$furnituresOfEachPerson = $person->furnitures; //<-----
return response()->json($furnituresOfEachPerson);
}
Second solution:
public function showPersonFurnitures($id) {
$person = Person::with('furnitures')->findOrFail($id);
$furnituresOfEachPerson = $person->furnitures()->get(); //<----
return response()->json($furnituresOfEachPerson);
}
The given answers are vague and incomplete, so here's a better explanation hopefully:
Situation 1 $person->furnitures(): When you call any of a model's relations as a function, you get an (incomplete) query object for that particular relation. That means whenever you call $person->furnitures(), any additional functions you chain on this result like ->where() are actual SQL operators and directly modify the query. In order to complete the query, you should call ->get() (or a function like ->pluck()) at the end to retrieve the actual data.
Situation 2 $person->furnitures: When you call any of a model's relations as a property, you retrieve the complete relation collection for that model. This means that $person->furnitures lazy-loads the collection if it is not available on the model yet. Any additional functions you chain on this result like ->where() will act on the PHP collection.
By using Person::with('furnitures')... you make sure that the relation is already loaded (Eager Loaded) after the findOrFail() call. You might note that this is not particularly useful with respect to loading 1 model (since you might just as well call $person->furnitures whenever you need it), but the important thing to remember here is that this with() method is extremely useful when you are retrieving collections of a model, like $persons = Person::where('activated', 1)->with('furnitures')->get(). This last query only executes 2 queries: 1 for retrieving persons, and 1 for retrieving all furniture related to these persons.
Overall, the difference for those 2 methods are not huge and you will get the same result, except:
$person->furnitures uses relationships, quite similar to eager loading that it retrieves data based on the One to Many relationship.
$person->furnitures()->get() is implemented from the perspective of collection, which is why it has the syntax of querying a collection.
They are same, but $person->furnitures requires extra steps to understand tha this is relation, and finally call $person->furnitures()->get()

Laravel Eloquent: get unique models between relationships

I have a scenario like this:
I have User model that has an OneToMany relationships with the Post model.
I have a Hashtag model that has an OneToMany relationships with the Post model.
Recap: ONE user has MANY posts, ONE post belongs to ONE hashtag, ONE hashtag has MANY posts.
I would like to fetch only unique users records of an hashtag.
I'm able to do it in non scalable way (fetching all the posts first and then iterate filtering by user id), but I need to maintain scalability for large record numbers.
Edit: I saw a partial solution in Laravel docs.
Laravel eloquent has a method called unique().
With that method I can specify the parameter which should be unique in query.
In my case figured it out with:
$users = $hashtag->posts->unique('user_id');
But I can't paginate query in this way...
Has anyone a solution for that?
You have to paginate your main model caller.
I don't know exactly if this is can reproduce your actual scenario, but let's see this example:
Let's say you have the model hashtags
So let's say the code could be something like this:
$users = DB::table('hashtags AS tags')
->select('tags.*')
->join('[posts AS post','post.id','=','tags.id')
->distinct()
->paginate(5, ['tags.*']);
I don't know if your query it'll be very accurate, but for this, I believe the best approach it'll be you do a raw query, could look like it'll be costly to your database and the operation, but you can work around this approach indexing and partitioning your database.
Remembering always the eloquent sometimes even when we're building join queries could be more even costly to your database.
Since you're worried about scalability so the best thing could be for too could write a view to fetch all the data with proper indexing and partitioning.
Try to use DISTINCT
$users = $hashtag->posts()->selectRaw('DISTINCT(user_id) AS unique_user_id')->paginate(10);
OR
use groupBy()
$users = $hashtag->posts()->groupBy('name')->select('user_id')->paginate(10);

Is it possible to join a MS-SQL to MySQL in Laravel

I apologize if this is a silly question. This is probably well out of standard practice, but I'm looking to be able to join data from a MS-SQL database with that of MySQL in Laravel 5.2.
I'm not sure if it's possible to do something like...
\DB::connection('sqlsrv')->table('mstable')
->leftJoin(\DB::connection('mysql')->table('mysqltable'),
'mysqltable.shared_id',
'=',
'mstable.shared_id');
My thinking is that if Laravel is converting the query early enough into a PHP object, it should be able to. Otherwise, is there a fallback to being able to use the two database types together?
I'm pretty sure that a join won't be possible. What you can do though, is using relations on different databases. Depending on your situation this might be an applicable workaround. I am currently using this approach to query entities from different databases and "chunk" them so that I only keep a couple of thousand entities in the memory. It's still an efficient way to iterate over all entities, because I'm using eager loading, so that Laravel / Eloquent only triggers two requests per chunk: One to get the primary models and the second to get the relation (by default this is done by an IN statement on the relation's table using the keys obtained from the primary models table).
A "simple" way to set this up is to fill the protected $connection property of the Eloquent Models like this:
class Foo extends Model{
protected $connection = "mysql";
public function bar(){
return $this->hasOne(Bar::class);
}
}
class Bar extends Model{
protected $connection = "ms-sql";
}
Foo::with("bar")->get();

Use a join statement and save the results as a relation like "with" does? - Laravel 5.2

I'm trying to get data from multiple relationships. But I don't want to end up with multiple queries. So the with method won't work for me.
I want to use joins to get the data needed, but laravel overwrites keys if they are duplicated. Is there a way to save the results from a join as a relation. Something like this below (join is incorrect I know).
Post::select('post.*', 'category.*')->join('category');
If both have an 'id' field it's overwritten by the other. So I would like to have category.* results as a relation so I can call ->category->id like I can when I use the with method.
Is there any way to do this?
You may use the whereHas and orWhereHas methods
Check this link:
http://laravel.io/forum/04-19-2014-many-to-many-to-many-in-wherehas
And Laravel Documentation:
https://laravel.com/docs/5.1/eloquent-relationships

Laravel - Full Text Search on Multiple Tables

I am using Laravel 4 and the Eloquent model for a project we are working on.
The database conforms to 3NF and everything works great. Also all MySQL tables were switched back from InnoDB to MyISAM since the MySQL version < 5.6 (full text search in InnoDB is only supported from 5.6 and up).
While creating some database search filters I am finding some shortage with using the Eloquent model vs the Query Builder. In specifics, especially when trying to do a full text search on columns from multiple tables (and staying within the Eloquent's object context).
For simplicity, we have the following database structure:
--projects
--id
--name
--status
--...
--users
--id
--...
--roles
--id
--project_id
--user_id
--...
--notes
--id
--project_id
--user_id
--note
--....
The following code (simplified and minimized for the question) currently works fine, but the full text search only works for one table (projects table).
if (Request::isMethod('post'))
{
$filters = array('type_id','status','division','date_of_activation','date_of_closure');
foreach ($filters as $filter) {
$value = Input::get($filter);
if (!empty($value) && $value != -1) {//-1 is the value of 'ALL' option
$projects->where($filter,'=',$value);
}
}
$search = Input::get('search');
if (!empty($search)) {
$projects->whereRAW("MATCH(name,description) AGAINST(? IN BOOLEAN MODE)",array($search));
}
}
// more code here...
// some more filters...
// and at the end I am committing the search by using paginate(10)
return View::make('pages/projects/listView',
array(
"projects" => $projects->paginate(10)
)
);
I need to extend the full text search to include the following columns - projects.name,projects.description and notes.note.
When trying to find how to make it with Eloquent we keep on coming back to Query Builder and running a custom query, which will work fine but then we will face these problems/cons:
Query Builder returns an array while Eloquent returns model objects. Since we are extending each model to include methods, we really don't want to give up the awesomeness of the Eloquent model. And we really don't want to use the Eloquent Project::find($id) on the return results just to get the object again.
We are chaining the 'where' methods to have any number of filters
assigned to it as well as for code re-usability. Seems like mixing
Eloquent and Query Builder statement together will break our chaining.
For the consistency of this project, we want all database queries to
stay in Eloquent connotation.
Reading Laravel's documentation and API, I could not find a method to run raw SQL queries using Eloquent. There is whereRAW() but it is not broad enough. I assume that this is a restriction made by design, but it is still a restriction.
So my questions are:
Is it possible to run a full text search on columns from multiple tables, only in Eloquent. Every piece of information I came across online, mentions using Query Builder.
If not, is it possible to use Query Builder searches and returning Eloquent objects? (without the need to run Project::find($id) on the array results).
And lastly, is it possible to chain Eloquent and Query Builder where methods together, while only committing using get() or paginate(10) at a later point.
I understand that Eloquent and Query Builder are different creatures. But if mixing both was possible, or using Eloquent to run raw SQL queries, I believe that the Eloquent model will become much more robust. Using only Query Builder seems to me a bit like under-using the Laravel framework.
Hope to get some insights about this since it seems as the forums/community of Laravel is still evolving, even though I find it to be an amazing framework!
Thanks and I appreciate any input you may have :)
First of all you can use query scope in your model/s
public function scopeSearch($query, $q)
{
$match = "MATCH(`name`, `description`) AGAINST (?)";
return $query->whereRaw($match, array($q))
->orderByRaw($match.' DESC', array($q));
}
this way you can get "eloquent collection" as return
$projects = Project::search(Input::get('search'))->get();
then, to search also into notes you can make a more complex scope that join notes and search there.
Not sure if this will help but there is a workaround in innoDB(in versions that support fulltext search), maybe it works for you.
Lets use 'notes' as second table
SELECT MATCH(name,description) AGAINST(? IN BOOLEAN MODE),
MATCH(notes.note) AGAINST(? IN BOOLEAN MODE)
FROM ...
.
WHERE
MATCH(name,description) AGAINST(?) OR
MATCH(notes.note) AGAINST(?)

Categories