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.
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 a table for students with column remarks which is equal to pass or fail and a column class to determine their group.
Now I want to get a collection using eloquent that goes like:
Select count(remarks) where 'remarks' equal to pass and
count(remarks) where 'remarks' equal to fail
GROUP BY class
What I have tried so far:
Student::where('remarks', 'pass')
->selectRaw('count(remarks) as passRemark')
->where('remarks', 'fail')
->selectRaw('count(remarks) as failRemark')
->groupBy('class')->get();
The above code doesn't seem to work and it returns nothing, it does work when using 1 where clause which is not what I intend to do and I tried adding another where, the query breaks.
Can anyone suggest the best approach for this?
To keep this in Eloquent, I would use the built in Laravel withCount() method and a closure function for each type of count.
Something like this would be my preference (I have now successfully tested this: each Student will have a count for both):
Student::withCount(['remarks as passRemark' => function ($query) {
$query->where('remarks', 'pass');
}, 'remarks as failRemark' => function ($query) {
$query->where('remarks', 'fail');
}])->groupBy('class')->get();
I guess you should try this query, using DB::raw(), just to see whether it works or not, then you can rewrite using the built in methods from the ORM.
SELECT SUM((CASE WHEN remarks = 'pass' THEN 1 ELSE 0 END)) AS passRemark,
SUM((CASE WHEN remarks = 'fail' THEN 1 ELSE 0 END)) AS failRemark
GROUP BY class
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.
I have a problem with Laravel Eloquent to get the subject name with the highest value and second highest value from the 'student_number' where the subject group = 'E1'.
This is what I have tried but it contains an error "max(): When only one parameter is given, it must be an array" and I don't know how to get the 2nd highest value of 'student_number'.
public function electiveGroup(){
$E1 = FinalyearSubject::get()
->where('subject_group','=','E1')
->orWhere('student_number','=',max('student_number'));
return view ('admin.elective')->with(compact('E1'));
}
FinalyearSubject::where('subject_group', 'E1')
->orderBy('student_number', 'DESC')
->limit(2)
->get()
Explanation:
Add filters
Order them by student_number descending
Take the top 2
get() the result
In your example, the moment you are doing FinalyearSubject::get(), the query is already done. get() returns a Collection object (more like an enriched array). Everything you chain afterwards is calculated using Laravel's Collection utilities. ->get() usually should be the last thing in your call so that you can do as much work in SQL as possible.
So, Question is you want maximum of all and 2nd maximum of subject
group = 'E1'.
Max of all
FinalyearSubject::where('subject_group','E1')
->max('student_number');
2nd max of 'E1'
FinalyearSubject::where('subject_group','E1')
->OrderBy('student_number','desc')
->offset(1)
->limit(1)
->get();
I want to do two things in one query: use the limit() function, but also get the total number of records as a record count for the query, as if the limit wasn't used. So, if there are 21 total records that meet the conditions like '%Gregory%', I would like that value returned, even though I'm using a limit(10,0).
Here's my code:
$data['recordcount'] = $this->db->count_all_results('assets');
$this->db->limit(10, 0);
if(isset($data['order'])){
$query = $this->db->select('*')->from('assets')->or_like($array)->order_by($column, $data['order'])->get();
}
The problem is that $data['recordcount'] in the first line returns all records. I want it to return all records that meet the query condition in the 4th line, but without the limit found in the 2nd line.
Normally count_all_results() clears the select statement when run. But by setting the second parameter to FALSE the statement is retained.
I think this will work for you.
$this->db->select('*')->from('assets')->or_like($array);
$data['recordcount'] = $this->db->count_all_results('assets', FALSE);
$this->db->limit(10, 0);
if(isset($data['order']))
{
$this->db->order_by($column, $data['order']);
}
$query = $this->db->get();
You can achieve SQL_CALC_FOUND_ROWS before the asterisk (*) like this:
$this->db->select('SQL_CALC_FOUND_ROWS *')->from('assets')..etc
This will calculate the number of rows that would be returned without the LIMIT condition.
Immediately after that you can select the FOUND_ROWS() like this:
$this->db->select('FOUND_ROWS()')->get();
This will return you the number you are looking for.
For more information refer to this page
enter link description here