Laravel complex scope querying relation (for review) - php

I wrote this and I'm just wondering if it's the best way to do it.
Orders have many transactions... Transactions belongTo order. I am querying order with ids greater than the last ID sent to fulfilment. Then I want only orders that have approved payment status or are offline payment type.
This code works, just wondering if it's the best way to do this :
/**
* Get all orders pending fulfilment
*
* #param $query
* #param $last_fulfiled
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeToFulfil($query, $last_fulfiled)
{
return $query->where('id', '>', $last_fulfiled)
->where(function($query) {
$query->whereHas('transactions', function ($query) {
$query->where('status', '>=', 5);
})->orWhere('payment_method', '=', 2);
});
}
EDIT:
Here is the SQL syntax :
select * from `orders` where `orders`.`deleted_at` is null and `id` > ? and ((select count(*) from `transactions` where `transactions`.`order_id` = `orders`.`id` and `status` >= ?) >= 1 or `payment_method` = ?)
Thanks !

This is definitely the correct Laravel approach for building this query. This almost always generates a pretty decently optimized query, usually a LEFT JOIN or a subquery or a combination of both.
If you want to see how optimal the resulting query would be, add ->toSql() to the end of that query statement and post the raw query syntax here.

Related

How to do order by the eloquent query without using join relationship in laravel?

How to order laravel eloquent query using parent model?
I mean I have an eloquent query where I want to order the query by its parent without using join relationship?
I used whereHas and order by on it, but did not work.
Here is a sample of my code:
$query = Post::whereHas('users')->orderBy('users.created_at')->get();
If you want to order Post by a column in user you have to do a join in some way unless you sort after you retrieve the result so either:
$query = Post::select('posts.*')
->join('users', 'users.id', 'posts.user_id')
->orderBy('users.created_at')->get();
Note that whereHas is not needed anymore because the join (which is an inner join by default) will only result in posts that have a user.
Alternatively you can do:
$query = Post::has('users')
->with('users')
->get()
->sortBy(function ($post) { return $post->users->created_at; });
The reason is that eloquent relationships are queried in a separate query from the one that gets the parent model so you can't use relationship columns during that query.
I have no clue why you wanted to order Posts based on their User's created_at field. Perhaps, a different angle to the problem is needed - like accessing the Post from User instead.
That being said, an orderBy() can accept a closure as parameter which will create a subquery then, you can pair it with whereRaw() to somewhat circumvent Eloquent and QueryBuilder limitation*.
Post::orderBy(function($q) {
return $q->from('users')
->whereRaw('`users`.id = `posts`.id')
->select('created_at');
})
->get();
It should generate the following query:
select *
from `posts`
order by (
select `created_at`
from `users`
where `users`.id = `posts`.id
) asc
A join might serve you better, but there are many ways to build queries.
*As far as I know, the subquery can't be made to be aware of the parent query fields
You can simply orderBy in your Post model.
public function users(){
return $this->belongsTo(User::class, "user_id")->orderByDesc('created_at');
}
I hope this helps you.
You can try
Post::query()
->has('users')
->orderBy(
User::select('created_at')
->whereColumn('id', 'posts.user_id')
->orderBy('created_at')
)
->get();
The sql generated would be like
select * from `posts`
where exists (select * from `users` where `posts`.`user_id` = `users`.`id`)
order by (select `created_at` from `users` where `id` = `posts`.`user_id` order by `created_at` asc) asc
But I guess join would be a simpler approach for this use case.
Laravel Docs - Eloquent - Subquery Ordering

WhereHas with WhereRaw on one to many relationship (Laravel)

I have an order table and an order_details table in my system.
Relationship between order table and order details table is one to many, means One order has many order details.
Now the problem is i am trying to filter the order with the quantity of items a that are stored in order_details table.
what i doing right know trying to access with whereHas
if ($request->has('quantity') && $request->quantity != null){
$query = $query->whereHas('orderDetails',function ($q) use ($request){
$q->whereRaw('SUM(Quantity) >= '.$request->quantity);
});
}
$orders = $query->orderBy('OrderID','desc')->get();
But it throws an error
General error: 1111 Invalid use of group function (SQL: select * from `orders` where `AddedToCart` = 0 and `PaymentSucceeded` = 1 and exists (select * from `order_details` where `orders`.`OrderID` = `order_details`.`OrderID` and SUM(Quantity) >= 12) order by `OrderID` desc)
I will be vary thankful if i get the solution
To be able to use sum function you need to group by data and as I see you are trying to group them by orderID.
An approach like this might help:
$ordersIDs = DB::table('orderDetails')
->groupBy('OrderID')
->havingRaw('SUM(Quantity)', '>=', 12)
->pluck('orderID')->toArray();
$orders = DB::table('orders')
->whereIn($ordersIDs)
->get();
The above code executes two SQL queries, you can mix them easily to make one.
Hope it helps.

How can I make select in select on laravel eloquent?

I use laravel 5.3
My sql query is like this :
SELECT *
FROM (
SELECT *
FROM products
WHERE `status` = 1 AND `stock` > 0 AND category_id = 5
ORDER BY updated_at DESC
LIMIT 4
) AS product
GROUP BY store_id
I want to change it to be laravel eloquent
But I'm still confused
How can I do it?
In cases when your query is to complex you can laravel RAW query syntax like:
$data = DB::select(DB::raw('your query here'));
It will fire your raw query on the specified table and returns the result set, if any.
Reference
If you have Product model, you can run
$products = Product::where('status', 1)
->where('stock', '>', 0)
->where('category_id', '=', 5)
->groupBy('store_id')
->orderBy('updated_at', 'desc')
->take(4)
->get();
I think this should give you the same result since you pull everything from your derived table.

Relation not loading, when using select

Introduction
Hello. I'm currently building a FacebookFeedParser, and at the moment I'm trying to build a method in a Controller that lists the Facebook pages with the best post/like ratio from today to the user. To do that, I have built the following query in pure SQL
SELECT pa.facebook_name, COUNT(po.id) AS postCount, SUM(likes) AS likes, SUM(likes)/COUNT(po.id) AS likesPerPost
FROM facebook_posts po
INNER JOIN facebook_pages pa ON pa.id = po.facebook_page_id
WHERE CONVERT_TZ(`facebook_created_time`, 'UTC', 'Europe/Berlin') > '2015-07-16 00:00:00'
GROUP by facebook_page_id
ORDER BY SUM(likes)/COUNT(po.id) DESC;
What I now wanted to do, is to transform this query into Laravel/Eloquent.
Current state
I have the following classes in my project
Controllers: PageController, PostController
Models: FacebookPost, FacebookPage, BaseModel
The FacebookPost and FacebookPage model are both defining their relations like this
FacebookPage
/**
* Defines the relation between FacebookPage and FacebookPost
* One page can have multiple posts
*
* #see FacebookPage
* #see FacebookPost
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function posts()
{
return $this->hasMany(FacebookPost::class);
}
FacebookPost
/**
* Defines the association of this object with FacebookPage
* One facebook_post belongs to one facebook_page
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function page()
{
return $this->belongsTo(FacebookPage::class, 'facebook_page_id');
}
In the BaseModel, I have defined a scope, which will be used multiple times in the project
/**
* Query scope to get the database values for today
*
* #param $query
* #return mixed
*/
public function scopeToday($query)
{
return $query->whereRaw('CONVERT_TZ(`'. $this->createDateColumn .'`, "UTC", "'. env('APP_TIMEZONE') .'") > "' . Carbon::today()->toDateTimeString() . '"');
}
And this is the query I've built in order to get those posts, with the filters
$posts = App\Models\FacebookPost::with('page')
->selectRaw('COUNT(id) AS postCount, SUM(likes) AS likes, SUM(likes)/COUNT(id) AS likesPerPost')
->today()
->groupBy('facebook_page_id')
->orderByRaw('SUM(likes)/COUNT(id) DESC')
->get();
Problem
The problem I'm currently having, is, that, when I try to rebuild the above query, I'm not getting all fields I want. As soon as I add an select to the Builder, the relations array, with the index page is null. If I ommit the select method, I'm getting the FacebookPage, but I want to have those specific fields
Now I'm getting an object. I guess this is because I'm using the Eloquent Builder right? Isn't it somehow possible to only get the fields I want to have? The result I'm expecting should look like this (per row)
facebook_name | postCount | likes | likesPerPost
McDonalds 1000 500 0.5
I also tried it like this
$posts = App\Models\FacebookPost::with(['page' => function($query) {
$query->select('facebook_name');
}])
->selectRaw('COUNT(id) AS postCount, SUM(likes) AS likes, SUM(likes)/COUNT(id) AS likesPerPost')
->today()
->groupBy('facebook_page_id')
->orderByRaw('SUM(likes)/COUNT(id) DESC')
->get();
Would I need to use the DB class instead of Eloquent? Or what would be the best solution for this problem?
Alternative solution
$pages = DB::table('facebook_posts')
->select(DB::raw('facebook_pages.facebook_name, COUNT(facebook_posts.id) AS postCount, SUM(likes) AS likes, ROUND(SUM(likes)/COUNT(facebook_posts.id)) AS likesPerPost'))
->join('facebook_pages', 'facebook_posts.facebook_page_id', '=', 'facebook_pages.id')
->whereRaw('CONVERT_TZ(`'. $this->createDateColumn .'`, "UTC", "'. env('APP_TIMEZONE') .'") > "' . Carbon::today()->toDateTimeString() . '"')
->groupBy('facebook_page_id')
->orderByRaw('ROUND(SUM(likes)/COUNT(facebook_posts.id)) DESC')
->get();
This, however, works. Would this be the correct solution for my use case? I'm just asking if it even make sense to use Eloquent here, since I'm not really trying to get an object, but data from multiple sources.
If you want with('page') to work when you specify the list of fields to fetch using select() you need to make sure that the foreign key is in that list, otherwise Laravel is not able to fetch related models. So in your case make sure that page_id is fetched from the database.

Eloquent eager loading do not joins?

I remember the old days "discovering" it in a query using the Eloquent, if I used the with Laravel do aninner join.
Today by chance I checked the queries of a project and ...
[2014-11-20 23:21:16] sql.INFO: select * from `ocurrences` where `ocurrences`.`deleted_at` is null order by RAND() limit 4 {"bindings":[],"time":3.58,"name":"mysql"} []
[2014-11-20 23:21:16] sql.INFO: select * from `users` where `users`.`id` in ('7') {"bindings":["7"],"time":0.49,"name":"mysql"} []
[2014-11-20 23:21:16] sql.INFO: select * from `users` where `users`.`id` = '7' limit 1 {"bindings":["7"],"time":0.51,"name":"mysql"} []
[2014-11-20 23:21:16] sql.INFO: select * from `tags` limit 5 {"bindings":[],"time":0.41,"name":"mysql"} []
In this case, I'm doing a query like this:
/**
* Get random ocurrences for home
* #return mixed
*/
public static function randomForHome()
{
return static::with('user')
->orderByRaw('RAND()')
->limit(4)
->get();
}
What is wrong and/or how do I do joins with Eloquent?
I found the solution in a Laracasts video (thanks Jeffrey!).
I need to use join('users', 'users.id', '=', 'ocurrences.user_id'). It's a bit logical but I thought with do joins too.
Anyway, here's the final solution:
/**
* Get random ocurrences for home
* #return Eloquent
*/
public static function randomForHome()
{
return static::join('users', 'users.id', '=', 'ocurrences.user_id')
->orderByRaw('RAND()')
->limit(4)
->get();
}
Thanks guys.

Categories