Laravel scoreboard of more than 1 million users - php

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().

Related

From SQL to Laravel 8 Eloquent

I have created a query in SQL and it works quite well.
SELECT learning_content_number,
course,
count(required) as required,
count(overdue) as overdue,
count(status) as status,
count(completion_date) as completion_date
FROM hse_leatros
GROUP BY learning_content_number
Now I want to translate it in Laravel 8 Eloquent. This script works, but I am missing the information about the course.
$courses = Leatro::groupBy('learning_content_number')
->selectRaw('count(required) as required, learning_content_number')
->selectRaw('count(overdue) as overdue, learning_content_number')
->selectRaw('count(status) as status, learning_content_number')
->selectRaw('count(completion_date) as completion_date, learning_content_number')
->get();
How can I enter the in the code that it is transferred with?
My DB-Table structur:
$table->id();
$table->integer('employee_id')->nullable();
$table->string('course')->nullable();
$table->string('required')->nullable();
$table->string('mandatory')->nullable();
$table->string('status')->nullable();
$table->string('due_date')->nullable();
$table->string('completion_date')->nullable();
$table->string('overdue')->nullable();
$table->string('learning_content_number')->nullable();
$table->string('assigned_date')->nullable();
$table->string('assigned_mechanism')->nullable();
$table->string('cost_centre_id')->nullable();
$table->string('hash')->nullable();
$table->timestamps();
$table->softDeletes();
You need just add ->select('learning_content_number', 'course'):
$courses = $db::table('hse_leatros')
->groupBy('learning_content_number', 'course')
->select('learning_content_number', 'course')
->selectRaw('count(required) as required')
->selectRaw('count(overdue) as overdue')
->selectRaw('count(status) as status')
->selectRaw('count(completion_date) as completion_date')
;
echo $courses->toSql();
print_r($courses->get());
Laravel QueryBuilder online

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`'),
]);

Laravel Query Builder avg() method, float number problem

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

How to use hasOne relationship correctly?

I am learning Laravel and I'm trying to create simple online store.
I created tables Items and Amounts. Now I want to display all Items with their amount in stock but for some reason unknown to me, amount of item is not fetched into items.
These are my schemas for tables:
Items:
Schema::create('items', function (Blueprint $table) {
$table->increments('id');
$table->integer('category_id')->unsigned();
$table->string('name', 120)->nullable(false);
$table->float('price',8,2)->unsigned()->nullable(false);
$table->longText('short_specification');
$table->longText('specification');
$table->longText('description');
$table->string('photo', 100);
$table->engine = 'InnoDB';
$table->foreign('category_id')->references('id')->on('categories');
});
Amounts:
Schema::create('amounts', function (Blueprint $table) {
$table->integer('item_id')->unsigned();
$table->integer('amount')->unsigned()->nullable(false);
$table->engine = 'InnoDB';
});
Schema::table('amounts',function($table){
$table->foreign('item_id')->references('id')->on('items');
$table->primary('item_id');
});
These are my models:
Item:
class Item extends Model
{
public $timestamps = false;
function amount()
{
return $this->hasOne('App\Amount','item_id','id');
}
}
Amount:
class Amount extends Model
{
function item()
{
//$this->belongsTo('App\Item');
return $this->belongsTo('App\Item','item_id','id');
}
}
When I do:
$items = DB::table('items')->get();
dd($items);
return view('home')->with('items',$items);
Items are displayed correctly, but amount of item isn't there.
When I do:
#foreach($items as $item)
{{ $item->id }}
{{ $item->amount }}
#endforeach
I get:
Undefined property: stdClass::$amount (View: D:\2.
PROGRAMY\xampp\htdocs\silicon_store\resources\views\home.blade.php)
error.
From what I've seen on the web (I've been trying to fix this for over 3 hours now so I must be doing something totally wrong) it should work properly but it isn't.
With $items = DB::table('items')->get();, you're using the query builder. It won't have the value of the relationship unless you join the amounts table in the query.
$items = DB::table('items')
->leftJoin('amounts', 'items.id', '=', 'amounts.item_id')
->get();
I think you could also use an Eloquent query. In that case each $item would be an instance of the Item model rather than a StdClass object.
$items = App\Item::with('amount')->get();
or you can use kind of this query
$items = App\Item::whereHas('amount')->get()
Here link to understanding whereHas

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.

Categories