Query a table with info from another one in Eloquent - laravel

I have two tables: Invoices and Clients. Each invoice have a client and each client has an expiration time for their invoices.
I want to get each invoice where the invoice date + the expiration time has passed current date.
I actualy did it with DB::select like this.
DB::select('SELECT DISTINCT i.id,(i.invoice_date+INTERVAL \'1 day\' * invoice_expiration) as expiration_date, i.invoice_date, c.invoice_expiration, c.id as client_id, c.name, i.total, i.status, c.rut FROM invoices i, clients c, invoice_cargos ic, cargos ca WHERE i.invoice_date+INTERVAL \'1 day\' * invoice_expiration < current_date AND i.client_id = c.id AND i.status not in (\'PAID\') and i.id = ic.invoice_id and ic.cargo_id = ca."id" AND i.iva = true AND ca.stage = \'APPROVED\' order by c.name, i.invoice_date ;');
But I'm having trouble translating that to eloquent.
The Invoice model has the relationship to the Client one:
Invoice Model:
public function client()
{
return $this->hasOne(Client::class,'id', 'client_id');
}

Laravel tries to guess, by conventions, the names of the columns in the relationships. I don't think id should be there as the second parameter. Also, you can omit client_id it would be easy for Laravel to guess that relationship, so, I'd leave it like this:
public function client()
{
return $this->hasOne(Client::class);
}
Something like this should work:
Invoice::select(DB::raw('DISTINCT i.id, (i.invoice_date + INTERVAL "1 day" * invoice_expiration) as expiration_date, i.invoice_date, c.invoice_expiration, c.id as client_id, c.name, i.total, i.status, c.rut'))
->join('clients as c', 'i.client_id', '=', 'c.id')
->join('invoice_cargos as ic', 'i.id', '=', 'ic.invoice_id')
->join('cargos as ca', 'ic.cargo_id', '=', 'ca.id')
->whereRaw('i.invoice_date + INTERVAL "1 day" * invoice_expiration < current_date')
->whereNotIn('i.status', ['PAID'])
->where('i.iva', true)
->where('ca.stage', 'APPROVED')
->orderBy('c.name')
->orderBy('i.invoice_date')
->get();
Alternatively, you can create columns in the database to store the calculated values so that you don't have to use raw SQL in your Eloquent query. For example, you could add a expiration_date column to the invoices table and update it with the calculated value when creating or updating an invoice. This would allow you to use a simple Eloquent query like this:
Invoice::where('expiration_date', '<', Carbon::now())
->whereNotIn('status', ['PAID'])
->where('iva', true)
->join('invoice_cargos as ic', 'invoices.id', '=', 'ic.invoice_id')
->join('cargos as ca', 'ic.cargo_id', '=', 'ca.id')
->where('ca.stage', 'APPROVED')
->orderBy('clients.name')
->orderBy('invoices.invoice_date')
->get();

Related

Laravel Lumen eloquent left join returns joined table data rather than primary able data

I have two tables company and courses table
The table has the following fields
companies table
id, full_name, email, deleted_at
and courses table
courses table
id, company_id, course_name,deleted_at
Now i would like to retrieve all courses which company is not deleted. So in my controller i have added
public function index(Request $request){
$query = Courses::query();
$query = $query->leftJoin('companies','companies.id','=','courses.company_id');
$query->whereNull('companies.deleted_at');
if($request->get('filter_name')){
$query = $query->where('courses.name', 'like', '%' . $request->get('filter_name') . '%');
}
return response()->json($query->paginate($request->get("perPage")));
}
When i run the above it returns companies data rather than courses. Where am i going wrong or what am i missing out?
If you have use eager loading in both of your model, you can use this kind of approach.
$all_course = Courses::with(['company', function($query) {
return $query->whereNotNull('deleted_at');
}])->get();
As you can see, I query in the Courses model to return all the courses but I added some filter in the company relationship. where in I used whereNotNull to get only the company that's not deleted.

laravel eloquent with and join conflict

I am quite new to Laravel... I have a posts table that has a relation with articles and users tables. In the controller I either get all the posts matching a search criteria or get all posts. In both cases, I learned how to add the related fields to the collection $posts.
// get all posts matching criteria
if (request('search') && request('search') != '') {
$posts = Post::orderBy($orderBySortField,$sortOrder)
->with(['user:id,name','article:id,title'])
->where('post','like','%'.request('search').'%')
->join('users', 'posts.user_id', '=', 'users.id')
->orWhere('users.name','like','%'.request('search').'%')
->join('articles', 'posts.article_id', '=', 'articles.id')
->orWhere('articles.title','like','%'.request('search').'%')
->get();
// get all posts
} else {
$posts = Post::orderBy($orderBySortField,$sortOrder)
->with(['user:id,name','article:id,title'])
->get();
}
// add related fields
foreach ($posts as $post) {
$post->title = '['.$post->article->title.']';
$post->user = '['.$post->user->name.']';
}
When I get all the posts, the related fields are correct in the list displayed in the view page.
However, if I search for the name of a specific user, I get a list where the $posts->user are incorrectly related.
I could figured that by displaying the last queries.
For all the posts:
select * from `posts` order by `posts`.`id` desc
select `id`, `name` from `users` where `users`.`id` in (1, 11)
select `id`, `title` from `articles` where `articles`.`id` in (1)
For the posts written by user 'paul' (user_id = 1):
select * from `posts` inner join `users` on `posts`.`user_id` = `users`.`id` inner join `articles` on `posts`.`article_id` = `articles`.`id` where `post` like ? or `users`.`name` like ? or `articles`.`title` like ? order by `posts`.`id` desc [where ? = %paul% %paul% %paul% ]
select `id`, `name` from `users` where `users`.`id` in (11)
select `id`, `title` from `articles` where `articles`.`id` in (1)
So why is Laravel last queries look for user_id = 11 which is another user?
Am I doing something wrong?
Change the join code the whereHas
// get all posts matching criteria
if (request('search') && request('search') != '') {
$search = request('search');
$posts = Post::orderBy($orderBySortField,$sortOrder)
->with(['user:id,name','article:id,title'])
->where('post','like','%'.$search.'%')
->orWhereHas('user', function($userQuery) use($search){
$userQuery->Where('users.name','like','%'.$search.'%');
})
->orWhereHas('article', function($articleQuery) use($search){
$articleQuery->Where('articles.title','like','%'.$search.'%');
})
->get();
}
If you have define model relation, you don't have to use join in your query, just use with function.
Using the two create duplicate data, that why you getting incorrectly related error.

Eloquent whereHas Relationship with constraint on particular related Entity

Consider having two models User, and Book the last one has a status column that can obtain different string values active, inactive, deleted, so the user can have multiple books and the book belongs to the user.
how could I get only users that have their last book status = 'inactive'?
The SQL Query for the behavior is given below:
SELECT
*
FROM
`users`
WHERE EXISTS
(
SELECT
*
FROM
`books`
WHERE
`books`.`user_id` = `users`.`id` AND `books`.`status` = 'inactive' AND `books`.`id` =(
SELECT
nested.`id`
FROM
`books` AS nested
WHERE
nested.`user_id` = `users`.`id`
ORDER BY
nested.`created_at` DESC
LIMIT 1
)
)
I'm using Laravel 5.6
Create additional relationship in User model that returns wanted result. Basically you need 1-1 relationship for this.
/**
* #return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function inactiveBookStillLatestPerUser()
{
return $this->hasOne(Book::class)->where(['status' => 'inactive', 'id' => function (\Illuminate\Database\Query\Builder $nested) {
$nested->from('books as nested')
->selectRaw('max(id)')
->whereRaw('nested.user_id = books.user_id');
}]);
}
Then in somewhere in code (i.e. controller) you call it with
$users = User::has('inactiveBookStillLatestPerUser')->get();
// or if books are needed too
// $users = User::has('inactiveBookStillLatestPerUser')->with(['inactiveBookStillLatestPerUser'])->get();
I used id latest order [max(id)] in subquery to avoid unwanted result if one user made multiple books batch insert at same point of time and when all those books would have same time of insert so latest per created_at wouldn't be most accurate, maybe. But you can do that similarly, instead:
/**
* #return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function inactiveBookStillLatestPerUser()
{
return $this->hasOne(Book::class)->where(['status' => 'inactive', 'created_at' => function (\Illuminate\Database\Query\Builder $nested) {
$nested->from('books as nested')
->selectRaw('max(created_at)')
->whereRaw('nested.user_id = books.user_id');
}]);
}
Maybe second example is ok, but first example with id would work fine, though.
User::where('your-conditions')
->whereHas('books', function($query) {
$query->where('books.status', '=', 'inactive')
->orderBy('id', 'desc')
->first();
})->get();

Laravel query builder subquery separate tables

How do I write this in Laravel Query Builder:
I have a users table that has one Customer with column 'phone'.
a customers table that belongs to User on 'user_id' with column 'mobile'.
I would like to find any customer id that with a phone number what either 'phone' or 'mobile'. Something like this:
select id
from customers
where mobile = '5555555555' or
user_id = (select id
from users
where phone = '5555555555')
I think this is what I need:
$customers = DB::table('customers')
->whereIn('user_id', function ($query) use ($phoneNumber) {
$query->select('id')
->from('users')
->where('users.phone', $phoneNumber);
})
->orWhere('mobile', $phoneNumber)
->pluck('id');

Self Join in Eloquent - How To Call

I recently asked a question regarding a self join
SO
I got a great answer but I'm not sure how to call it.
I need to do this query:
SELECT t2.title FROM products t1, products t2
WHERE t1.id = $id
AND t2.color_id = t1.color_id AND
t2.id != $id
I now have this on my products model:
public function parent()
{
return $this->belongsTo(self::class, 'color_id');
}
public function children()
{
return $this->hasMany(self::class, 'color_id');
}
But how do I call this?
Product::with('children')->find(1);
The above gets the product with id 1 but also gets children that have a color_id of 1, I need to get children who have a color_id the same as the id as product 1.
eg.
Products
id | title | color_id
1 dvd 2
When I select row 1 it should select all other products with a color_id of 2.
I believe your relations are not the way they're supposed to be. Usually it's one column (foreign key - color_id in your case) having a value of the other one (usually primary key - id in your case).
What you have is basically a value the records share or a "category". So your products are not "children" but rather siblings (have the same parent color).
Since with method is not build as a JOIN statement but as eager loading (separate query) you can do that manually.
Probably the most straight forward way:
$product1 = Product::find(1);
$children = Product::where('id', '<>', $product1->id)->
where('color_id', $product1->color_id)->
get();
You can add select('title') to the second "builder" to get only title but that would not be your model anymore. Or you can use lists('title') to extract only titles if that's what you need.
UPDATE:
If you decide you need the JOIN after all I'd suggest going with raw query builder and leave the Eloquent out of it:
$res = DB::table('products as t1')->
select('t2.title')->
join('products AS t2', 't2.color_id', '=', 't1.color_id')->
where('t1.id', 1)->
where('td2.id', '<>', 't1.id')->
get();
I believe it should build something similar to what you need.
You can try this way:
// Category.php
public function children()
{
return $this->hasMany(Category::class, 'parent_id');
}
public function parent()
{
return $this->belongsTo(Category::class, 'parent_id');
}
// To access
$category->children; // sub-categories collection
$category->parent; // parent instance
based on : https://laracasts.com/discuss/channels/eloquent/self-join-in-eloquent-model?page=1
You might want to do as follow :
$id = 1;
$product = Product::with('children')->whereHas('children', function($q) use ($id)
{
$q->where('color_id', $id);
})->find($id);
See Advanced Join Clauses and adapt from my example here.
It took me a long time to wrap my head around Laravel joins.
This:
$postIds = DB::table('comments as t1')
->select('t1.*')
->leftJoin('comments as t2', function ($join) {
$join->on('t1.postId', '=', 't2.postId')
->on('t1.created_at', '<', 't2.created_at');
})
->where('t2.id', '=', null)
->orderBy('t1.created_at', 'DESC')
->simplePaginate(20)
->pluck('postId');
seems to be the way to get an array of the postIds that would be revealed by this:
SELECT t1.*
FROM comments a
LEFT OUTER JOIN comments b
ON t1.postId = t2.postId
AND t1.created_at < t2.created_at
WHERE t2.id IS NULL
ORDER BY t1.created_at DESC
SELECT subcat.CategoryName as SubCat cat.CategoryName as Category FROM bn_bas_categories cat, bn_bas_categories subcat WHERE cat.CategoryID = subcat.ParentID;

Resources