Can't convert SQL query to laravel eloquent - php

I have this query that will get how many user votes with each star number
SELECT stars, COUNT(*) AS rate FROM product_user where product_id = 1 GROUP BY(stars)
result of this query
stars | rate
_____________
2 | 3
5 | 4
but I can't convert it to laravel eloquent
this is my try but it gets an error
Product::find($id)->votes()->selectRaw('count(*) as rate, stars')
->groupBy('stars')
->get();
Error message
SQLSTATE[42000]: Syntax error or access violation: 1055 Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'roya.users.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by (SQL: select `users`.`id`, `first_name`, `last_name`, count(*) as rate, stars, `product_user`.`product_id` as `pivot_product_id`, `product_user`.`user_id` as `pivot_user_id`, `product_user`.`stars` as `pivot_stars`, `product_user`.`feedback` as `pivot_feedback`, `product_user`.`created_at` as `pivot_created_at`, `product_user`.`updated_at` as `pivot_updated_at` from `users` inner join `product_user` on `users`.`id` = `product_user`.`user_id` where `product_user`.`product_id` = 1 group by `stars`)
Product Model
class Product extends Model {
public function votes()
{
return $this->belongsToMany(User::class)
->using('App\Review')
->select('users.id', 'first_name', 'last_name')
->withPivot(['stars', 'feedback'])
->withTimeStamps();
}
}

When you use grouped select queries SQL allows only for aggregated functions or columns specifically listed in GROUP BY section to be selected. Your votes() relationship adds extra select columns to your query ('users.id', 'first_name' and 'last_name') and they are causing an error. This happens because selectRaw method doesn't replace previously selected columns, but utilizes addSelect() method to add raw on top of the existing ones.
In your case it's really cumbersome to use Eloquent here when you only need an aggregated count data for specific product votes.
Just add getVotesCountByStars method to your Product model and utilize Laravel's generic query builder via DB facade:
public function getVotesCountByStars()
{
return DB::table('product_user')
->where('product_id', $this->id)
->selectRaw('count(*) as rate, stars')
->groupBy('stars')
->orderBy('stars', 'asc')
->get()
->pluck('rate', 'stars')
->toArray();
}
This way you will see exactly what query is generated and no additional overhead is produced (in my example, an associative array with stars as keys and counts as values will be returned).

What you could do is is add a ratings relationship to your Product model that is a hasMany between Product and Rating:
public function ratings()
{
return $this->hasMany(Rating::class)
->select('stars', 'product_id')
->selectRaw('count(*) as rate')
->groupBy('stars', 'product_id');
}
Then your query would be something like:
$product = Product::with('rating')->find(1);
This would product something like:
{
"id":1,
...
"ratings":[
{
"stars":2,
"product_id":1,
"rate":1
},
{
"stars":3,
"product_id":1,
"rate":2
},
{
"stars":4,
"product_id":1,
"rate":4
},
{
"stars":5,
"product_id":1,
"rate":3
}
]
}

Related

withExists() or withCount() for nested relationship (Many To Many and One To Many) in Laravel Eloquent

I have the following relationships in models:
Product.php
public function skus()
{
return $this->belongsToMany(Sku::class);
}
Sku.php
public function prices()
{
return $this->hasMany(Price::class);
}
I need to get an attribute indicating whether a product has at least one price or not (in the extreme case, just the number of prices).
Product::withExists('sku.prices') or Product::withCount('sku.prices')
I know about this repository https://github.com/staudenmeir/belongs-to-through, but I prefer to use complex query once
UPDATE: I have already written a sql query for this purpose, but I don't know how to do it in Laravel:
SELECT
*,
EXISTS (SELECT
*
FROM prices
INNER JOIN skus
ON prices.sku_id = skus.id
INNER JOIN product_sku
ON skus.id = product_sku.sku_id
WHERE products.id = product_sku.product_id
) AS prices_exists
FROM products
Here you can get at least one record
$skuPrice = Sku::with('prices')
->has('prices', '>=', 1)
->withCount('prices')
->get();

Laravel 5.8 Eloquent query without using DB::raw() -- get rows based on latest value of many to many relationship

My goal is to select all issues that have been marked as false-positive. The issues are connected to Status via a ManyToMany relationship with pivot table IssuesStatus. The current status of an issue is determined by the value of the status column on the Status table.
I've come up with a solution that works but seems somehow suspect. I'm looking for a way to rewrite the query using Eloquent query builder without relying on the DB::raw() method.
public function getFalsePositivesAttribute() {
return Issue::where(DB::raw(
'( select `status` '.
'from `issues-status` '.
'left join `status` on `status`.id = `issues-status`.status_id '.
'where `issues-status`.issue_id = `issues`.id '.
'order by `issues-status`.id desc limit 1 )'
), '=', 'false-positive')->get();
}
Example of desired SQL query:
SELECT
`Issues`.id
FROM
`issues` AS `Issues`
LEFT JOIN
`issues-status` `IssueStatus` on `Issues`.id = `IssueStatus`.issue_id
LEFT JOIN
`status` as `StatusTable` on `IssueStatus`.status_id = `StatusTable`.id
WHERE
`Issues`.report_id = 2
AND
(
SELECT
`status`
FROM
`issues-status` `IssueStatus`
LEFT JOIN
`status` `StatusTable` on `StatusTable`.id = `IssueStatus`.status_id
WHERE
`IssueStatus`.issue_id = `Issues`.id
ORDER BY
`IssueStatus`.id desc
LIMIT 1
) = 'false-positive'
GROUP BY
`Issues`.id
Models:
class Issue extends Model {
...
public function status() {
return $this->belongsToMany( Status::class, 'issues-status')
->withTimestamps()
->withPivot('note');
}
...
}
class Status extends Model {
...
public function issues() {
return $this->hasMany(Issue::class);
}
...
}
Tables:
Issues:
id - identity
Status
id - identity
status - string
IssueStatus
id - identity
issue_id - relation to Issues
status_id - relation to Status
created_at - timestamp
note - text
If i understood correctly, you want to fetch the issue where the latest status is equals to something.
Add a latestStatus function to your Issues model:
public function latestStatus()
{
return $this->hasMany(IssueStatus::class)->latest();
}
Now, swap your status function with this one:
public function status() {
return $this->belongsToMany( Status::class, 'issues-status')
->withTimestamps()
->withPivot('note');
}
Then:
//Get all issues which has the desired status
Issue::whereHas('status', function($query){
$query->where('id', $desiredStatusId);
})
->get()
->reject(function($issue){
//filter the collection by the latest issue status,
// and reject those who does not have the desired latest status;
return $issue->latestStatus()->first()->status_id != $desiredStatusId;
});
Hope it helps.

Laravel Eloquent and Mysql join a table IF another join is null

Three main tables:
products
advertisers
locations
Two pivot tables:
advertisers_locations
products_locations
Relationships:
A product belongs to an advertiser and an advertiser has many locations (Locations it can ship products to)
A product can also have it own set of locations that override the advertiser locations (Some products have delivery restrictions)
What I need to do is:
Select all products
Check if products_locations table for product ID and join it.
If it does not exist then join the advertisers locations table
Is this possible to do in one query and using eloquent? Here's my code - struggling with the conditional:
public function scopeWhereShippableToLocation($query)
{
$location_id = session('location_id');
$query->where(function ($q) use ($location_id) {
$q->join('products_locations', 'products_locations.product_id', '=', 'products.id')
->where('products_locations.location_id', '=', $location_id);
});
$query->orWhere(function ($q) use ($location_id) {
$q->join('advertisers_locations', 'advertisers_locations.advertiser_id', '=', 'products.advertiser_id')
->where('advertisers_locations.location_id', '=', $location_id);
});
//dd($q->toSql());
return $query;
}
This is currently producing a MySQL error:
Column not found: 1054 Unknown column 'products_locations.location_id' in 'where clause' (SQL: select `products`.*,
I think I have a solution for you using eloquent, rather than the query builder. You need to check to see if the relationship exists, if not you need another query. This can be done using the following:
public function scopeWhereShippableToLocation($query)
{
$location_id = session('location_id');
// WhereHas check to see if a relationship exists, IE: The pivot table
// orWhereHas will be checked if the first where does not exist
$query->whereHas('products_locations', function ($q) use ($location_id) {
$q->where('location_id', $location_id);
})->orWhereHas('advertisers_locations', function ($q) use ($location_id) {
$q->where('location_id', $location_id);
});
return $query;
}
This should work providing that your Products, Advertisers and Locations relationship methods are set up.

Making subqueries on eloquent LARAVEL 5.2.29

I am trying to exclude a few records from a select that has a null column in its relationship.
This is the query I hope to achieve:
SELECT
FROM modelas ma WHERE
ma.id NOT IN (SELECT ma_id from modelbs where modelbs.updated_at is null)
Model A:
public function modelb(){
return $this->hasMany('App\Modelb');
}
Model B
public function modela(){
return $this->belongsTo('App\Modela');
}
Now, I want to query all models A that have a model B where a certain column of model B ISN'T null.
Here's how i tried:
Modela::whereHas('modelbs', function ($query) {
$query->whereNotNull('myColumnOfModelB');
})->get();
This gave me exactly the records I wanted to exclude from my select
Well, I was able to find out where I was mistaken. So here is a query that achieves that raw sql:
Modela::whereDoesntHave('modelbs', function ($query) {
$query->whereNull('someModelBcolumn');
})->get()
Raw SQL
SELECT
FROM modelas ma WHERE
ma.id NOT IN (SELECT ma_id from modelbs where modelbs.updated_at is null)
The "whereDoesntHave()" is equivalent to "SELECT FROM modelas ma WHERE ma.id NOT IN" whereas the "$query->whereNull()" is the actual subquery equivalent to "(SELECT ma_id from modelbs where modelbs.updated_at IS NULL)"

How can I get count of records group by a column using with function in laravel Eloquent ORM

I need to get count of a records groupBy using with() function known as eager loading. I have two tables having one-to many relationship.
Properties:
id
name
address
Banner_Invoices:
id
property_id
start_date
end_date
Property Model has following relationship function with Banner_Invoices table:
public function bannerInvoices()
{
return $this->hasMany('BannerInvoice');
}
Now following is my query:
$properties = Property::with(array('country', 'city',
'bannerInvoices' => function($query) {
$query->where('end_date','>',date(date("Y-m-d")))
->groupBy('banner_invoices.status')
->select(DB::raw('COUNT(banner_invoices.id)')) ;
}))->get();
I am not been able to get the count of records groupBy with the status of banner Invoices. It gives me the records of banner_invoices table corresponding to a particular property.

Categories