I tried to run a Laravel query that selects a particular field that appears more than once, and I got the error below.
"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'no_of_service' in 'having clause' (SQL: select count(*) as aggregate from cloudsubscriptions inner join service_package on cloudsubscriptions.package_id = service_package.id group by cloudsubscriptions.msisdn, service_package.title having no_of_service > 1)
$subscribers = Cloudsubscriptions::join("service_package",
"cloudsubscriptions.package_id", "=", "service_package.id")
->select("cloudsubscriptions.msisdn", "cloudsubscriptions.service_name",
"service_package.title",
DB::raw("COUNT(cloudsubscriptions.msisdn) as 'no_of_service'"))
->groupBy("cloudsubscriptions.msisdn", "service_package.title")
->having('no_of_service', '>', 1)
->get();
I expect to see fields that appear more than once.
I'm pretty sure you're using paginate() in your code and not get() as you posted. And when Laravel generates the necessary data for pagination, it overrides the SELECT part of your statement with:
SELECT count(*) as aggregate
This is used to get the total entries count. That is very obvious from the SQL part of your error message:
SQL: select count(*) as aggregate from cloudsubscriptions inner join service_package on cloudsubscriptions.package_id = service_package.id group by cloudsubscriptions.msisdn, service_package.title having no_of_service > 1
That of course overwrites your no_of_service alias definition, which can no longer be found in your HAVING statement when the total count is done by the paginator.
To work around this, you could use the aggregate function directly in your HAVING statement without the alias:
$subscribers = Cloudsubscriptions::join("service_package",
"cloudsubscriptions.package_id", "=", "service_package.id")
->select(
"cloudsubscriptions.msisdn",
"cloudsubscriptions.service_name",
"service_package.title",
DB::raw("COUNT(cloudsubscriptions.msisdn) as 'no_of_service'"))
->groupBy("cloudsubscriptions.msisdn", "service_package.title")
// Use the COUNT aggregate function here as well
->havingRaw('COUNT(cloudsubscriptions.msisdn) > 1')
->get();
It is a little annoying to have to duplicate that logic, but at least you can make use of the Laravel Pagination which is a much bigger gain.
IMPORTANT NOTE! Make sure to use bindings with havingRaw and other raw methods if the value comes from user input.
UPDATE
Since you're using Eloquent as the starting point for your query, your can make use of this great package made by Roy Duineveld, which fixes the issue present in the paginator and allows you to use the alias in your HAVING statement. You can use it by simply including a trait in your model:
use Illuminate\Database\Eloquent\Model;
use JustBetter\PaginationWithHavings\PaginationWithHavings;
class Cloudsubscriptions extends Model
{
use PaginationWithHavings;
}
And now you can use your original query code without any problems.
Related
I have a classrooms table with a "quota" column that has different values. In one class, there are many students. How to display classrooms data with where condition which total students per row < "quota" ? Here's the table : .
Code :
Classroom::with('subject.teacher')->with('students')->whereHas('subject', fn ($query) => $query->where('grade', $grade))->withCount('students')->having('students_count', '<', 'quota');
when I use this code the result is empty
when "having" is removed this is the result :
The desired result only displays 3 classroom
You can use the has and a DB::raw.
// Simplified version
Classroom::has('students', '<=', DB::raw('classrooms.quota'))->get();
The withCount function appends an additional select column to your query. It does not use GROUP BY which is typically what is used in conjunction with having.
You can't actually filter by subqueries that are added in the select. However fortunately Laravel allows you to select rows based on how many related models they have using has. This query is also added as a subquery, but within the where clauses so you can also use column names within it like below:
Classroom::with('subject.teacher')
->with('students')
->whereHas('subject', fn ($query) => $query->where('grade', $grade))
->has('students', '<', \DB::raw('quota'))
->withCount('students');
I have two database tables items and measurement_units - item has measurement unit.
Now the problem is I want to select a particular column from items and some column from measurement_unit. I want to use Eager loading
e.g.
$items_with_mu = Item::with("measurement_unit")->select(["item_name", "item_stock"])->first();
When accessing measurement_unit. It returns null. Without the select function it returns data(measurement_unit).
$items_with_mu->measurement_unit;
can anyone help me and sorry for my English.
Try this
Item::with(['measurement_unit' => function($q) {
$q->select('id','unit_column'); //specified measurement_unit column
}])
->select('id','measurement_unit_id','item_name')
->get();
If your laravel version is >=5.5 then you can write in a single line
Item::with('measurement_unit:id,unit_column')
->select('id','measurement_unit_id','item_name')
->get()
You have to select the primary column of the main model like below.
items_with_mu = Item::with("measurement_unit")->select(["item_name", "item_stock", "primary_key"])->first();
Im trying to paginate a search to bring back pages of 18 items. Previously i had this code working code:
$productsQuery = Product::where('approved', '=', 1)->leftJoin('reviews', 'reviews.products_id', '=', 'products.id')->select('products.*', DB::raw('AVG(ratings) as ratings_average' ))->groupBy('id')->orderBy('ratings_average', 'DESC');
I add on paginate to this as shown in the docs
$productsQuery = Product::where('approved', '=', 1)->leftJoin('reviews', 'reviews.products_id', '=', 'products.id')->select('products.*', DB::raw('AVG(ratings) as ratings_average' ))->groupBy('id')->orderBy('ratings_average', 'DESC')->paginate(18);
And get an error
SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'id' in group statement is ambiguous
Any ideas on how to paginate the statement?
It's not neccesarily the pagination which is causing the issue, the error is caused by your group by.
You should place the table name / alias name prior to the id EG:
$productsQuery = Product::where('approved', '=', 1)->leftJoin('reviews', 'reviews.products_id', '=', 'products.id')->select('products.*', DB::raw('AVG(ratings) as ratings_average' ))->groupBy('product.id OR reviews.id')->orderBy('ratings_average', 'DESC');
Please see the ammended statement above, note that copy pasting it won't work, you'll see to change that groupby argument.
You should also visit the docs on Eloquent, you have approached this in a way where you're fighting laravel. I would suggest getting a tighter grip on the ORM used.
Good luck!
The error is pretty self explanatory here:
Column 'id' in group statement is ambiguous
so you need to make
`groupBy('id')`
more explicit, by telling which id (from which table, as each table used in your query features id column) you want to be used for grouping.
I have Deals and Faq's. I have functional relationships working and I can reference $deal->faqs() and it returns the right faqs.
The problem I am trying to solve comes up as I administer the faqs related to a deal. In my Deal admin view (new / edit) I am getting all the Faq's.
$faqs = \App\Faq::all();
This works great, and I am even able to check if an faq is related to a deal through my checkbox: in the view:
{!! Form::checkbox('faqlist[]', $faq->id, $deal->faqs->contains($faq->id) ? true : false) !!}
So now we have a list of all the faqs and the correct ones are checked.
I have setup an order column on the pivot table (deal_faq). That table consists of:
deal_id
faq_id
timestamps
order
In my form, I have a drag and drop ordering solution (js) built and working. By working I mean, I can drag/drop and a hidden field value is updated to reflect the correct order.
When creating a deal, this is no problem. Get all the faq's, check a few to associate, drag to order, then save.
When editing a deal, I need to load based on the order column in the deal_faq table. This is my issue.
I have tried a few things but always get an error. An example of what I have tried is:
$faqs = \App\Faq::orderBy('deal_faq.order', 'asc')->get();
This returns an error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'deal_faq.order' in 'order clause' (SQL: select * from `faq` order by `deal_faq`.`order` asc)
I think the issue is that I am trying to get all, but order by a field that only exists for the related faqs since the order field is on the deal_faq. Just not sure how to solve.
In essence you need to join the pivot table and then apply the order
$faqs = \App\Faq::join('deal_faq', 'faqs.id', '=', 'deal_faq.faq_id')
->orderBy('deal_faq.order', 'asc')
->get();
You may need to adjust table and column names to match your schema.
Now you can extract this logic into a scope of the Faq model
class Faq extends Model
{
...
public function scopeOrdered($query)
{
return $query->join('deal_faq', 'faqs.id', '=', 'deal_faq.faq_id')
->orderBy('deal_faq.order', 'asc');
}
}
and then use it like this
$faqs = \App\Faq::ordered()->get();
UPDATE:
This works to get the FAQ's in order but it only get the ones that
have been associated. There will be FAQ's that are not associated and
thus not ordered.
In this case you just need to use an outer join - LEFT JOIN. The scope definition would then look like this
public function scopeOrdered($query)
{
return $query->join('deal_faq', 'faqs.id', '=', 'deal_faq.faq_id', 'left')
->orderBy('deal_faq.order', 'asc');
}
or a bit more expressively
public function scopeOrdered($query)
{
return $query->leftJoin('deal_faq', 'faqs.id', '=', 'deal_faq.faq_id')
->orderBy('deal_faq.order', 'asc');
}
Please consider adding a secondary order (i.e. by id, or any other field(s) in faqs table that make sense). This way you'd have a deterministically ordered resultsets each time regardless of whether you have an explicit order defined in a pivot table or not.
I am studying the source code of a package, rtConner/laravel-tagging. In this package there is a trait called TaggableTrait. On line 179, in a method called addTag(), there is this line which I don't understand:
$previousCount = $this->tagged()->where('tag_slug', '=', $tagSlug)->take(1)->count();
What does this line do? In specific, my problem is with ->take(1)->count(); part, are we taking 1 of the entries from the where clause and then count it?
From the Laravel documentation:
take(int $value)
Alias to set the "limit" value of the query.
So basically what you do is constructing a query with Query Builder and you are literally saying:
Select count of all tags, where tag_slug is $tagSlug and return the first row
It is equal to
SELECT COUNT(*) FROM tags WHERE tag_slug = 'blabla' LIMIT 1
Since COUNT() is aggregate function it will always return one row (count of all rows that match the where condition), so ->take(1) is obselote and will give you the same result with or without it.