Laravel return restaurants with their rate average - laravel

<?php
$query = $this->restaurant->leftJoin('reviews', 'reviews.restaurant_id', '=', 'restaurants.id');
$query = $user_id == null ? $query : $query->where("user_id", $user_id);
$query = $query->select('restaurants.*')
->selectRaw('ROUND(AVG(reviews.rating), 1) as average_rating')
->groupBy('restaurants.id')
->havingRaw('AVG(reviews.rating) >= ?', [$request->from_rating])
->havingRaw('AVG(reviews.rating) <= ?', [$request->to_rating])
->orderByDesc("average_rating")
->paginate(
$this->perPage,
['id', 'name', 'image', 'description', 'filesystem', 'created_at'],
'page',
$request->page
);
return $query;
My query looks like this. I have two tables: Restaurants and Reviews. The Reviews table stores restaurant_id. The problem occurs when we have, for example, 10 restaurants and we have 1 Review only for one of the Restaurants. After running this query, only 1 restaurant is returned, because I use havingRaw which filters Restaurants according to the average rating. So if a Restaurant doesn't have any rating, that Restaurant doesn't get returned.
How do I make that so all Restaurants still return, but their average rating becomes 0?

you can use CASE WHEN to populate mysql with more specific condition. Also you may change the datatype of 'rating' table to int(5), in this case all the empty value will be shown as 0. So that will go as you want according to your question.

Related

Eloquent - Multiple sum on the same relationship

The models I have are : Quote and Payment
A Quote hasMany Payments
A Payment belongsTo a Quote
A Payment has an 'amount' and a boolean 'is_received' fields
I'm trying to make a single query that fetches :
All the Quotes
For each Quote the sum of the received payments amounts
For each Quote the sum of all the payments amount
What I currently have is the following query... it is missing the sum of the received payments, which I have no idea how to implement (also I'm not sure this is a good approach for get the total sum anyway) :
$quotes = Quote::with([
'payments' => function ($query) {
$query->selectRaw('quote_id, SUM(payments.amount) AS payments_total')->groupBy('quote_id');
}
])
->get();
Thanks.
maybe you could use something like this:
Quote::with(['payments' => function ($query) {
$query->selectRaw(
"SUM(amount) AS payments_total," .
"SUM(IF is_received = " . true . " THEN amount ELSE 0 END) AS
received_amount");
}])->get();

Order by highest hasmany

I have two tables Parts has many Offers.
I need to get all Parts sorted by Offers highest price or quantity, where offers price > 0 and offers quantity > 0.
Price and quantity are calculated fields.
How to achieve this?
I tried
Part::
addSelect([
'quantity' => Offer::select('quantity')
->orderBy('quantity', 'desc')
->whereColumn('part_id', 'parts.id')
->limit(1)
])
->orderBy('quantity', 'desc')
->paginate(3)
But this way i cannot filter parts by quantity > 0 and price > 0
Also since quantity is calculated field when i do this
->addSelect([
'qty' => Offer::select(['quantity * 2 as qty'])
->orderBy('qty', 'asc')
->whereColumn('part_id', 'parts.id')
->limit(1)
])
I get Reference 'qty' not supported error
More explanation:
Each Part has many Offers.
Each Offer has many Shippings.
Offer`s
available_quantity = quantity - sum of shipped quantities
I need to show Parts where available_quantity > 0 and sort Parts by available_quantity
You can order it like this:
$shipping = Shipping::groupBy('offer_id')->select(DB::raw('SUM(quantity) AS sum_quantity'), 'offer_id', 'id');
Part::join('offers', 'offers.part_id', '=', 'parts.id')
->join(DB::raw("({$shipping->toSql()}) AS ship"), 'ship.offer_id', '=', 'offers.id')
->select('parts.*',DB::raw('MAX(offers.quantity - ship.sum_quantity) AS avaliable_quantity'))
->groupBy('part.id')
->orderBy('avaliable_quantity', 'desc')
->paginate(3)

Sorting a field based on several values in Laravel

I have an array of city codes
$cities=[9,12,14,2,3,4,5,6,8,10,11,13]
My posts table has a foreign key named city_id
I want sorting posts based on the values of this array
In this way: the first posts of the city 9 then the posts of the city 12 and then posts of city 14 and etc to be loaded
I tried using this method but this is wrong
$posts->orderByRaw('city_id in ? desc',$cities);
Can you help me find the best solution?
The only way i can i think of doing something like that(at least right now) is by doing something like so
$all_posts = [];
$cities=[9,12,14,2,3,4,5,6,8,10,11,13];
foreach ($cities as city) {
$city_posts = Post::whereRaw('city_id = ?', $city)->orderByRaw('created_at DESC');
array_push($all_posts, $city_posts);
}
dd($all_posts);
1st find all the posts relevant to cities and then sort w.r.t given order like
$cities = [9,12,14,2,3,4,5,6,8,10,11,13]; // order you'd like to see with posts
$posts = Post::whereIn('city_id', $cities)->sort(function($a, $b) uses ($cities) {
$pos_a = array_search($a->getAttributes()['city_id'], $cities);
$pos_b = array_search($b->getAttributes()['city_id'], $cities);
return $pos_a - $pos_b;
})->get();
// $posts contains the required order with city_ids
You can use raw query with "CASE something THEN index" this way you tell the query to see something as index so you can assign 0 to the first item in your array.
$sql .= "....";
foreach($cities as $index => $city) {
$sql .= "CASE {$city} THEN {$index}";
}
$sql .= "....";
Thanks to the friends solution,I used this method, and I think it's less complicated than the suggested methods of friends
$posts=$posts->orderByRaw('FIELD(city_id, '.$cities->implode( ', ').') asc')->orderBy('created_at','desc')->get();

Querying related table data with Eloquent

i have a problem trying to get records from a model based on a related table.
I have two tables one called leads and one called recycle_logs, my app will basically send the same record in the leads table like once a month, and when it does so i'll store a new record in recycle_logs.
The problem is that i need to select all leads with a specific campaign_id value and that have a specific status that is different from invalid, so far so good, now the problem is i need to get them only if they don't have any recycleLogs associated with them OR if the last recycleLog associated with that particular lead is older than 30 days ago for instance.
What i currently have looks somewhat like this.
$leads = $this->leadModel->where(Lead::CAMPAIGN_ID, $this->campaignID)
->where(Lead::DUPLICATED, Lead::DUPLICATED_NO)
->where(Lead::LEAD_STATUS, "!=" ,Lead::LEAD_STATUS_INVALID)
->orderBy(Lead::CREATED_AT, 'desc')
->with(
['leadRecyclingLog' => function($query) {
$query->where(LeadRecyclingLog::CREATED_AT, '<', (new Carbon())->subDays($this->configRecyclingDays))
->orWhere(LeadRecyclingLog::ID, null);
}]
)
->get();
What exactly am i doing wrong? It always selects the same number of records regardless of me adding or removing recycleLogs
I've managed to get it done through a raw SQL query which i'll post below in case it helps anyone, i'd still like to know how to do it in Eloquent/Query Builder.
SELECT * FROM `leads` LEFT JOIN `lead_recycling_logs` ON `leads`.`guid` = `lead_recycling_logs`.`original_lead_guid` WHERE `leads`.`campaign_id` = :campaignID AND `leads`.`duplicated` = 0 AND `leads`.`lead_status` != :invalidStatus AND (`lead_recycling_logs`.`id` IS NULL OR `lead_recycling_logs`.`created_at` < :recyclingDate) ORDER BY `leads`.`created_at` DESC
Try this:
$leads = $this->leadModel->where(Lead::CAMPAIGN_ID, $this->campaignID)
->where(Lead::DUPLICATED, Lead::DUPLICATED_NO)
->where(Lead::LEAD_STATUS, "!=" ,Lead::LEAD_STATUS_INVALID)
->orderBy(Lead::CREATED_AT, 'desc')
->where(function($q) {
$q->whereHas('leadRecyclingLog', function($q) {
$q->where(LeadRecyclingLog::CREATED_AT, '<', (new Carbon())->subDays($this->configRecyclingDays));
})
->orWhereHas('leadRecyclingLog', '<', 1); // Where count of the relationship is smaller than 1
})->get();
I assumed the first part of the query is working well (up until the relationship).
What you're looking for is ->whereHas(relationship), not ->with(relationship). ->with(relationship) will attach the associated results to the original model (the query for the original model will not be affected by ->with()). ->whereHas(relationship) filters the original model by the condition.
Got it to work through #devk 's help
$leads = $this->leadModel->where(Lead::CAMPAIGN_ID, $this->campaignID)
->where(Lead::DUPLICATED, Lead::DUPLICATED_NO)
->where(Lead::LEAD_STATUS, "!=" ,Lead::LEAD_STATUS_INVALID)
->orderBy(Lead::CREATED_AT, 'desc')
->where(function($q) {
$q->whereHas('leadRecyclingLog', function($q) {
$q->where(LeadRecyclingLog::CREATED_AT, '<', (new Carbon())->subDays($this->configRecyclingDays));
})
->doesntHave('leadRecyclingLog', 'or');
})->get();

laravel 4 relations - how display a top-5 ranking of records voted by users

I am creating a system of newsfeed, and as you can easily guess, it is beyond my skills.
Please be kind to put me on the right track or provide something I can go on with.
I have several hundred events (model name is Event1, table 'events')
I also have a pivot table in which users can assign any event's importance (values 0,1,2,3)
The relevant columns of the pivot table user_attitudes (Model Userattitude) are
id, item_type, item_id, importance, attitude, creator_id
An example three record are:
456 - event - 678 - 2 - 4
457 - event - 690 - 3 - 15
458 - event - 690 - 1 - 4
459 - participant - 45 - 1 - 4
Plain English: Total aggregated importance of the event #690 is '4', while the event #678 is '2'.
Therefore in my ranking the event #690 should be listed as first.
Just to see the bigger pic: the user #4 also rated participant # 45 as importance = 1.
The table services many models - the above example include two - just to give a better image of what I have.
WHAT I NEED:
I wish to print a ranking of top 5 events (and later other models). I wish to be able to use two methods of calculating the total score:
by counting the actual value (0,1,2,3)
by counting any value above 0 as 1 point.
I want to generate views which filter events by this criteria:
at least one user set the importance to '0' (I need it to flag an event as untrustworthy)
events which has not been rated yet
reverse of the above - events which are rated by at least one user
events listed by number of users who assigned any importance to it
This is easy, but still I have no idea how to make it happen. The same filters as the above #2, but related to a particular user decisions:
list 5 or 10 events (random or newest) which has not yet been rated by the user
maybe something like this would be an answer:
$q->where('creator_id', '=', Auth::user()->id);
Relevant code:
As I don't really grasp the merged relations, I might fail to show everything needed to provide help - ask for more code in comments.
Models:
Event1 (table 'events'):
public function importances()
{
return $this->morphMany('Userattitude', 'item');
}
public function user_importance($user)
{
return $this->morphMany('Userattitude', 'item')->where('creator_id', ($user ? $user->id : NULL))->first();
}
User: (table 'users' - standard user table)
public function importances()
{
return $this->hasMany('Userattitude', 'creator_id');
}
In model Userattitude (different from User, table name 'user_attitudes')
public function events()
{
return $this->morphTo('item')->where('item_type', 'event');
}
public function event()
{
return $this->belongsTo ('Event1', 'item_id');
}
PROBLEMS IN REPLY TO #lucas answer:
PROBLEM 1.
table name 'items' keeps me confused as in my project 'items' are events (model Event1), the participants (model Entity) and other objects.
Can we stick to my naming until I get hold of the knowledge you are providing?
it also contains column named attitudes, which is used for blacklisting particular items.
For instance, an item of type 'entity' (possible participant of multiple events) can be voted by user two-wise:
- by importance set by an user (we are doing this now, values available to use are 0,1,2,3)
- by attitude of an user toward (possible value (-1, 0, 1)
Such solution allows me to compute karma of each item. For instance -1 x 3 = -3 (worst possible karma value), while 1 x 2 = 2 (medium positive karma).
In consequence I am unable to use queries with the users method. It is still too confusing to me, sorry. We diverted too far from my original mental image.
Consider this query:
$events = Event1::has('users', '<', 1)->get();
If in Event1 I declare
public function users()
{
return $this->morphToMany('User', 'item', null, null, 'creator_id');
}
Note: User is the standard users table, where username, password and email are stored
I get this error:
[2014-12-28 05:02:48] production.ERROR: FATAL DATABASE ERROR: 500 = SQLSTATE[42S02]: Base table or view not found: 1146 Table 'niepoz_niepozwalam.items' doesn't exist (SQL: select * from `Events` where (select count(*) from `users` inner join `items` on `users`.`id` = `items`.`creator_id` where `items`.`item_id` = `Events`.`id` and `items`.`item_type` = Event1) >= 1) [] []
if I change the method definition to
public function users()
{
return $this->morphToMany('Userattitude', 'item', null, null, 'creator_id');
}
Note: Userattitude is model (table name is 'user_attitudes') where i store user judgments. This table contains columns 'importance' and 'attitude'.
I get the same error.
If I change the method to
public function users()
{
return $this->morphToMany('User', 'Userattitudes', null, null, 'creator_id');
}
I get this:
[2014-12-28 05:08:28] production.ERROR: FATAL DATABASE ERROR: 500 = SQLSTATE[42S22]: Column not found: 1054 Unknown column 'user_attitudes.Userattitudes_id' in 'where clause' (SQL: select * from Events where (select count(*) from users inner join user_attitudes on users.id = user_attitudes.creator_id where user_attitudes.Userattitudes_id = Events.id and user_attitudes.Userattitudes_type = Event1) >= 1) [] []
Possible solution:
the 'user_attitudes' table alias with name 'items'.
I could create a view with the required name.
I did it, but now the query produces no results.
PROBLEM 2
should I rename creator_id into user_id - or keep both columns and keep duplicated information in them? The creator_id follows conventions and I use it to create records... how to resolve this dillema?
PROBLEM 3.
As far as I understand, if I want to get a USER-RELATED list of top-5 events,
I need to ad another line to the code, which narrows search scope to records created by a particular logged in user:
Auth::user()->id)
The code would look like this:
All with importance 0
$events = Event1::whereHas('users', function($q){
$q->where('importance', 0);
$q->where('creator_id', '=', Auth::user()->id);
})->get();
right?
PROBLEM 5:
Ok, I am now able to output a query like these:
$rank_entities = Entity::leftJoin('user_attitudes', function($q){
$q->on('entity_id', '=', 'entities.id');
$q->where('item_type', '=', 'entity');
})
->selectRaw('entities.*, SUM(user_attitudes.importance) AS importance')
->groupBy('entities.id')
->orderBy('importance', 'desc')
->take(6)
->get();
and in the foreach loop I can display the total importance count with this code:
{{$e->importance or '-'}}
But How I could display count of an alternative query: SUM of values from another column, named attitude, which can be computed in this SEPARATE query:
In other words, in my #foreach loop I need to display both $e->importance and a computed SUM(user_attitudes.attitude) AS karma, which for now can be received with this query:
$rank_entities = Entity::leftJoin('userattitudes', function($q){
$q->on('entity_id', '=', 'entities.id');
$q->where('item_type', '=', 'entity');
})
->selectRaw('entities.*, SUM(userattitudes.karma) AS karma')
->groupBy('entities.id')
->orderBy('karma', 'desc')
->take(5)
->get();
My solution would be to create some extra columns in the 'entities' table:
- karma_negative
- karma_positive
to store/update total amount of votes each time someone is voting.
First, let's talk about the setup. I wasn't entirely sure how and if your's works but I created this on my testing instance and it worked, so I recommend you change yours accordingly:
Database
events
That's a simple one (and you probably already have it like this
id (primary key)
name (or something like that)
etc
users
I'm not sure if in your example that is Userattitude but I don't think so...
id (primary key)
email (?)
etc
items
This is the important one. The pivot table. The name can be different but to keep it simple and follow conventions it should be the plural of the polymorphic relation (in your case item => items)
id (actually not even necessary, but I left it in there)
item_type
item_id
importance
creator_id (consider changing that to user_id. This would simplify the relationship declaration)
Models
I think you have to read the docs again. You had several weird relations declared. Here's how I did it:
Event1
By default Laravel uses the classname (get_class($object)) as value for the ..._type column in the database. To change that you need to define $morphClass in your models.
class Event1 extends Eloquent {
protected $table = 'events';
protected $morphClass = 'event';
public function users()
{
return $this->morphToMany('User', 'item', null, null, 'creator_id');
}
}
User
class User extends Eloquent implements UserInterface, RemindableInterface {
// ... default laravel stuff ...
public function events(){
return $this->morphedByMany('Event1', 'item', null, null, 'creator_id');
}
}
Queries
Alright now we can get started. First one additional information. I used Eloquent relations whenever possible. In all the queries a join() is made it would be slower to use relations because certain things (like counting or calculating the maximum) would have to be done in PHP after the query. And MySQL does a pretty good job (also performance wise) at those things.
Top 5 by total value
$events = Event1::leftJoin('items', function($q){
$q->on('item_id', '=', 'events.id');
$q->where('item_type', '=', 'event');
})
->selectRaw('events.*, SUM(items.importance) AS importance')
->groupBy('events.id')
->orderBy('importance', 'desc')
->take(5)
->get();
Top 5 by number of votes over 0
$events = Event1::leftJoin('items', function($q){
$q->on('item_id', '=', 'events.id');
$q->where('item_type', '=', 'event');
$q->where('importance', '>', 0);
})
->selectRaw('events.*, COUNT(items.id) AS importance')
->groupBy('events.id')
->orderBy('importance', 'desc')
->take(5)
->get();
All with importance 0
$events = Event1::whereHas('users', function($q){
$q->where('importance', 0);
})->get();
All without any votes
$events = Event1::has('users', '<', 1)->get();
All with 1+ votes
$events = Event1::has('users')->get();
All ordered by number of votes
$events = Event1::leftJoin('items', function($q){
$q->on('item_id', '=', 'events.id');
$q->where('item_type', '=', 'event');
})
->selectRaw('events.*, COUNT(items.id) AS count')
->groupBy('events.id')
->orderBy('count', 'desc')
->get();
Newest 5 without votes
If you are using Eloquents timestamps created_at:
$events = Event1::has('users', '<', 1)->latest()->take(5)->get();
If you're not (order by greatest id):
$events = Event1::has('users', '<', 1)->latest('id')->take(5)->get();
Random 5 without votes
$events = Event1::has('users', '<', 1)->orderByRaw('RAND()')->take(5)->get();
I did not add any explanations to the queries on purpose. If you want to know more about something specific or need help, please write a comment
PROBLEM 4: SOLVED! (credit to #lukasgeiter)
If you wish to display a ranking of items and limit the results to a particular tag defined in a pivot table, this is the solution:
$events = Event1 (table name = 'events')
For example, the tag would be war: defined in table
eventtags
Event nature are defined as
id = '1' is name = 'wars'
id = '2' is name = 'conflicts'
pivot table, which assigns multiple tags:
event_eventtags they are defined as id = '4'
Example records for event_eventtags:
id - event_id - eventtag_id
1 - 45 - 1
2 - 45 - 2
Plain English: the Event1 #45 is tagged as war(#1) and conflict(#2)
Now in order to print a list of 10 wars you should define your query in this way:
$events= Entity::join('event_eventtags', function($q){
$q->on('entity_id', '=', 'entities.id');
$q->where('entitycapacitytypes_id', '=', 1);
})->leftJoin('user_attitudes', function($q){
$q->on('item_id', '=', 'entities.id');
$q->where('item_type', '=', 'entity');
})
->selectRaw('entities.*, SUM(user_attitudes.importance) AS importance')
->groupBy('entities.id')
->orderBy('importance', 'desc')
->take(10)
->get();
The user_attitudes is part of voting system described in the original question. You can remove it and sort events by another method.

Resources