Laravel Query Builder avg() method, float number problem - php

Trying to create a 5-star rating system in Laravel.
a star rate is a float number in the range [1 to 5] and it stores successfully in the database.
migration file for rates table in database. ( I've set data type of star column as float)
public function up()
{
Schema::create('rates', function (Blueprint $table) {
$table->id();
$table->morphs('rateble'); //creates rateable_id and rateable_type
$table->foreignId('user_id')->nullable()->constrained()->onUpdate('cascade')->onDelete('set null');
$table->float('star',2, 1); //star number is from 0 to 5
$table->timestamps();
});
}
Then for averaging the rating for a specific product, I am using this query:
public function scopeAverageRate($query, $type, $id)
{
return $query->where('rateble_type', $type)
->where('rateble_id', $id)
->avg('star');
}
The problem is, it seems that the avg() method cast the result to integer! Is there any way to set this function to work with the float numbers?
e.g. assume there is only one rate for a specific product that equals "0.5" stars then the average rating is "0" instead of "0.5" !
How can I solve this problem?
Thank you in advance,

The correct answer is to write raw sql for the average function like:
->selectRaw('CAST(AVG(star) AS DECIMAL(2,1)) AS star_avg')->first()->star_avg;
Please note that star is the name of a column that we would like to average value of it.
for more information: here

Related

Using Laravel 5.5 how do I duplicate a column in a table with a migration?

I would like to duplicate a column on my existing seasons table using a migration in Laravel 5.5.
I can do it using SQL running these two queries:
ALTER TABLE seasons ADD uploader_channel_partner_id BIGINT NULL
then
UPDATE seasons set seasons.uploader_channel_partner_id = seasons.channel_partner_id
I am creating a system where you can transfer the ownership of videos while keeping the original uploader stored.
The up function of my migration currently just has this inside it:
Schema::create('seasons', function (Blueprint $table) {
$table->unsignedBigInteger('uploader_channel_partner_id');
$table->foreign('uploader_channel_partner_id')->references('id')->on('channel_partners');
});
EDIT:
I've made it work with this but it's slow and ugly and I'm sure there is a better way:
public function up()
{
Schema::table('seasons', function (Blueprint $table) {
$table->unsignedBigInteger('uploader_channel_partner_id')->after('channel_partner_id')->nullable();
$table->foreign('uploader_channel_partner_id')->references('id')->on('channel_partners');
});
$seasons = Season::withTrashed()->get();
foreach ($seasons as $key => $season) {
$season->uploader_channel_partner_id = $season->channel_partner_id;
$season->save();
}
}
You can use DB::raw() to force Sql to use another column instead of a string value.
Season::withTrashed()
->update([
'uploader_channel_partner_id' => DB::raw('`channel_partner_id`'),
]);

Eloquent Get higher value of a specific table's column

Using Eloquent in Laravel,
To get the minimum value of a column this code works:
public function getLowestYearBook()
{
return CV_Outputs::min('book_publication_year');
}
But to get the higher value it doesn't work, i'm using 'max' instead of 'min'.
How to get the higher? Thanks!
--------------- Edit:
the problem is I have some rows with "Not defined" text in it, so sorting by desc it returns that row, because letters are "higher" than number.
I fixed it by doing this:
public function getHighestYearBook()
{
return CV_Outputs::all()
->where('book_publication_year', '<>', "Not defined")
->sortByDesc('book_publication_year')
->first()->book_publication_year;
}
You should check if the type/value is the same in all rows, because max should do the job. But try sorting them in descending order which means highest first and then get the first element like this:
CV_Outputs::all()
->where('book_publication_year', '!=', 'Not defined')
->sortByDesc('book_publication_year')
->first();

Laravel scoreboard of more than 1 million users

I'm working in a biggest application ( more than 1 million users ) and I try to get the ranking of each user in the scoreboard section but had this problem: the result is very very slow
This is the architecture of my database:
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
...
});
Schema::create('topics', function (Blueprint $table) {
$table->increments('id');
...
});
The topics table have than 20 row
Schema::create('user_scores', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->integer('topic_id')->unsigned();
$table->unique(['user_id', 'topic_id']);
$table->float('timer');
$table->integer('score');
});
The query to make rank for users
User::where('type',0)->get()->each(function ($user) {
$user->topics= $user->scores->sum('score');
$user->timing= $user->scores->sum('timer');
})->sort(function ($a, $b){
return ($b->topics - $a->topics) == 0
? ($a->timing - $b->timing)
: ($b->topics - $a->topics);
})->values()->each(function($user, $key){
$user->rank = $key +1;
});
Any optimization should I make the get the result quicker? Thanks.
As soon as you call get(), all(), find() or first() on a query builder, you'll ask the Eloquent engine to perform the query and return you the result. So in your case, all the sorting and grouping is performed in memory, which comes with incredibly bad performance.
What you can do is to improve your query:
User::query()
->where('type', 0)
->withCount('scores as topics')
->withCount(['scores as timing' => function ($query) {
$query->selectRaw('SUM(timer)'); // might look weird, but works...
}])
->orderBy('topics', 'desc')
->orderBy('timing', 'desc')
->get()
For the row number (or rank, or however you wanna call it), you might want to search through existing questions and answers. Answering that as well would be too much for this answer, to be honest. Clearly you should not use your approach though, as it will also calculate the row number in memory.
But obviously it is also important what you are doing with the query results. Are you displaying one million rows to the user? If so, the bottleneck will be the browser in the end for sure. You might want to consider using pagination with paginate() instead of get().

Run Update query in a migration in Laravel 5? [duplicate]

I need to add a new column in my laravel Project, no problem for this, I used the Schema::table() to update and it's ok.
Now I need to find out how many records I have on this table and update with some value.
I have the table Warrants:
Schema::create('warrant_grants', function(Blueprint $table) {
$table->increments('id');
$table->integer('warrant_plan_id');
$table->integer('shareholder_id');
});
So I created the new field with a new migration file:
Schema::table('warrant_grants',function ($table) {
$table->string('name',100);
});
Now I need to update this field name in the table with some values, for example if the table has 100 records, then I need to insert in every row the value "Warrant-X" where X is a number starting with 1 to 100.
For example:
Warrant-1, Warrant-2, ....Warrant-100.
I spent hours looking for some way to do this using Seeds but I didn't found. So basically i have two questions:
Can I use Seeds in Laravel 5 to update values or I can just insert them?
Can I create some SQL inside the Seeds (or migrations) to do this update for me?
Based on this link i found the answer: https://stackoverflow.com/a/23506744/4650792
Schema::table('warrant_grants',function ($table){
$table->string('name',100)->after('id')->nullable();
});
$results = DB::table('warrant_grants')->select('id','name')->get();
$i = 1;
foreach ($results as $result){
DB::table('warrant_grants')
->where('id',$result->id)
->update([
"name" => "Warrant-".$i
]);
$i++;
}
Thanks for the help anyway guys.
Other answers are correct. But note that if you have a lot of records, updating all of them with ORM can take time. Use raw SQL queries to do that faster.
Schema::table('warrant_grants',function ($table){
$table->string('name',100)->after('id')->nullable();
});
DB::raw("UPDATE warrant_grants SET name=name+id");
The SQL query is not exact, and you have to make it for your own DB, but you get the point.
Yes, you can perform updates/inserts/whatever in your migrations. For example:
Schema::table('warrant_grants', function($table) {
$table->string('name', 100);
});
$i = 1;
foreach (WarrantGrants::all() as $warrant_grant) {
$warrant_grant->update([
'name' => 'Warrant-' . $i
]);
$i++;
}
Another possible syntax to achieve this:
DB::table('warrant_grants')
->where('id',$result->id)
->update([
"name" => DB::raw("'Warrant-' + `name`")
]);
This allows the update to be done as one batch rather than iterating over results, and retains most of the familiar Eloquent syntax rather than falling back to just using raw SQL.
The string concatenation syntax may need to be changed depending on the SQL variant used.

Laravel Count rows where column value is 1

Hey so i am trying to do an upvote downvote system and I am only using 1 table, the table columns are
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');
$table->integer('voter_id')->unsigned();
$table->foreign('voter_id')->references('id')->on('users');
$table->boolean('vote_type')->default(0);
$table->integer('commentable_id');
$table->string('commentable_type');
$table->string('unique_vote')->unique();
Basically I am trying to count how many votes the comment has but only where the vote_type is == 1 and also the reverse for downvotes where the value is 0
I was thinking about doing this with 2 different tables as it would make counting easier but I also dont want a large database.
i know of {{$comment->votes->count()}} but this returns the total rows regardless of the vote_type value and I am wondering if anyone has a solution or knows of a way while keeping the queries low.
Why you do it like this
public function showCart() {
$votes = Vote::where('vote_type',1)->count();
// do stuff and then return the count with the actual data & view
}
You cant chain like this
$votes = Vote::where('vote_type',1)->where('something',$something)->count();
if you want the result for the logged in user
$votes = Auth::user()->votes->where('vote_type',1)->count();
I hope you get the point here, you dont have to do the count in blade
Too late to answer this question, but in general groupBy on a collection can be a good option for this
$votesInGroups = Vote::all()->groupBy('vote_type');
if you want to refine the data:
$votesInGroups->map(function($group, $key){
// assign keys so that its meaningful
if($key == 1) {
return [
'upvotes' => $group->count();
];
}
// yada yada yada
});
I ended up just creating another relation and then enquing it with the main call. eg
comments class
public function upvotes()
{
return $this->morphMany('App\Models\General\Votes', 'commentable')->whereVoteType(1);
}
public function downvotes()
{
return $this->morphMany('App\Models\General\Votes', 'commentable')->whereVoteType(0);
}
--
public function index($category_slug, $subcategory_slug, $post_slug)
{
return view('pages.forum-post', [
'post' => Post::With('comments','comments.user','comments.votes','comments.upvotes','comments.downvotes','comments.user.UserProfile')->whereSlug($post_slug)->firstOrFail()
]);
}
--
blade
#if ($comment->upvotes)
{{$comment->upvotes->count()}}
#endif
#if ($comment->downvotes)
{{$comment->downvotes->count()}}
#endif
<hr />

Categories