Yii2 create large amount of data - yii2

I have to create a large amount of data from an existing table data. The amount is about 5000 to 10000 data a day.
My controller:
$skus = Sku3d::find()->all();
foreach ($skus as $sku) {
$model = new Loghour3d();
$model->sku = $sku->sku;
$model->modeler = $sku->modeler;
$model->team = $sku->team;
$time = new \DateTime('now');
$today = $time->format('Y-m-d');
$model->day = $today;
$model->handover = $sku->handover;
$model->hour = $sku->totalhours;
$model->save();
}
But the hours created is not correct. Example: $sku->totalhours = 12 but $model->hour = 600 and my code is $model->hour = $sku->totalhours;. So I don't know where 600 from.
And also is there a better way to complete this task, because right now it takes about 15-20 minutes.

If you want to speed up processing of large amount of data from database, you should resign from using ActiveRecord in the first place. Operating on arrays and using DAO should be much more efficient than creating ActiveRecord object for each record.
Second - you should use batchInsert() to insert multiple records in one query. Inserting 100 records in one query is faster than doing it in 100 separate queries.
Third - if some value is the same for each record, move it before foreach. There is no point of calculating current date 10000 times if it is always the same.
$skus = Sku3d::find()->asArray()->all();
$time = new \DateTime('now');
$today = $time->format('Y-m-d');
$toInsert = [];
foreach ($skus as $sku) {
$toInsert[] = [
'sku' => $sku['sku'],
'modeler' => $sku['modeler'],
'team' => $sku['team'],
'day' => $today,
'handover' => $sku['handover'],
'hour' => $sku['totalhours'],
];
}
Yii::$app->db->createCommand()
->batchInsert(
Loghour3d::tableName(),
[
'sku',
'modeler',
'team',
'day',
'handover',
'hour',
],
$toInsert
)
->execute();

Related

How do I update multiple columns with their respective values in laravel?

I have a database record that I would like to update base on certain conditions. This condition is: when the pay_day is reached, I want to send the user that owns that record an email and then update the pay_day column to another date in the future using the interval_day column on the users' table. interval_day is just a number selected by the user.
Below is an illustration:
$now = Carbon::now();
User::where('approved', true)
->where('pay_day', '<', $now)
->chunkById(1000, function($users){
foreach ($users as $user) {
$interval = $user->interval;
$payDay = $now()->addDays($interval);
// update the user...
$user->update([
'pay_day' => $payDay,
]);
// if the user was updated, send an email next...
}
});
Now, let's say I have 100 different users with different interval values. I want their respective values in their interval columns to be what would be updated to their pay_day column and NOT the same date for all 100 users.
But when I run the above query it didn't update, neither the pay_day nor send email to the respective users. When I dd($interval & $payDay) it returns nothing.
Please what am I doing wrong? I need your suggestions. Thanks for your time in advance.
use like this
$updates = ([
'pay_day' => $payDay,
//other columns
]);
$x = User::where('approved', true)
->where('pay_day', '<', $now)->->update($updates);
if($x){
//succeed case
}
Not sure if $payDay = $now()->addDays($interval); is a typo or you've actually done that in your code, but $now is a variable not a function. Additionally you need to tell the closure in your chunkById function to use $now:
\App\Models\User::where('approved', true)
->where('pay_day', '<', \Carbon\Carbon::today())
->chunkById(1000, function ($users) {
$users->each(function ($user) {
$success = $user->update([
'pay_day' => \Carbon\Carbon::today()->addDays($user->interval)
]);
if ($success) {
// send email
}
});
});
The above finds all approved Users where their pay_day is before today then processes the results in chunks of 1000 and updates the pay_day for each of them to be today + the interval value.

Mass update in Laravel Eloquent or DB

Is there anyone who knows how to do this without the technique of doing it in a one query string. I mean the popular ways I see on the net is by looping in data(the updates) and generating a single update statement and then fire a query. Is it possible for an Eloquent Approach or DB without looping?
This is posible with Eloquent, it might be necessary to enable mass-assignment, but you will get an error if so.
$post_data = Input::all();
$model = Model::find($id);
$model ->fill($post_data);
$model ->save();
or
$post_data = Input::all();
Model::find($id)->update($post_data);
Yes, you can do that but in that case, you have to make the array of data that is a loop is needed to store the data in the array with respective field_name => value of the table.
The following is the example:
$Array = array(); //This is needed to hold data while looping over $YourData
$YourData - is the array of data you want to store in the respective table.
foreach ($YourData as $YourDatakey => $YourDatavalue ){
$Array = [
'table_column_name' => $YourDatavalue['value_from_array'],
'table_column_name' => $YourDatavalue['value_from_array'],
'table_column_name' => $YourDatavalue['value_from_array'],
...... and so on
];
}
$InsertQuery= YourModelName::create($Array);
PS:
YourModelName model file should have the columns in protected
$fillable = ['column1','column2'....];
You should use App\Models\ModelName; at the top of the file.

Import of 50K+ Records in MySQL Gives General error: 1390 Prepared statement contains too many placeholders

Has anyone ever come across this error: General error: 1390 Prepared statement contains too many placeholders
I just did an import via SequelPro of over 50,000 records and now when I go to view these records in my view (Laravel 4) I get General error: 1390 Prepared statement contains too many placeholders.
The below index() method in my AdminNotesController.php file is what is generating the query and rendering the view.
public function index()
{
$created_at_value = Input::get('created_at_value');
$note_types_value = Input::get('note_types_value');
$contact_names_value = Input::get('contact_names_value');
$user_names_value = Input::get('user_names_value');
$account_managers_value = Input::get('account_managers_value');
if (is_null($created_at_value)) $created_at_value = DB::table('notes')->lists('created_at');
if (is_null($note_types_value)) $note_types_value = DB::table('note_types')->lists('type');
if (is_null($contact_names_value)) $contact_names_value = DB::table('contacts')->select(DB::raw('CONCAT(first_name," ",last_name) as cname'))->lists('cname');
if (is_null($user_names_value)) $user_names_value = DB::table('users')->select(DB::raw('CONCAT(first_name," ",last_name) as uname'))->lists('uname');
// In the view, there is a dropdown box, that allows the user to select the amount of records to show per page. Retrieve that value or set a default.
$perPage = Input::get('perPage', 10);
// This code retrieves the order from the session that has been selected by the user by clicking on a table column title. The value is placed in the session via the getOrder() method and is used later in the Eloquent query and joins.
$order = Session::get('account.order', 'company_name.asc');
$order = explode('.', $order);
$notes_query = Note::leftJoin('note_types', 'note_types.id', '=', 'notes.note_type_id')
->leftJoin('users', 'users.id', '=', 'notes.user_id')
->leftJoin('contacts', 'contacts.id', '=', 'notes.contact_id')
->orderBy($order[0], $order[1])
->select(array('notes.*', DB::raw('notes.id as nid')));
if (!empty($created_at_value)) $notes_query = $notes_query->whereIn('notes.created_at', $created_at_value);
$notes = $notes_query->whereIn('note_types.type', $note_types_value)
->whereIn(DB::raw('CONCAT(contacts.first_name," ",contacts.last_name)'), $contact_names_value)
->whereIn(DB::raw('CONCAT(users.first_name," ",users.last_name)'), $user_names_value)
->paginate($perPage)->appends(array('created_at_value' => Input::get('created_at_value'), 'note_types_value' => Input::get('note_types_value'), 'contact_names_value' => Input::get('contact_names_value'), 'user_names_value' => Input::get('user_names_value')));
$notes_trash = Note::onlyTrashed()
->leftJoin('note_types', 'note_types.id', '=', 'notes.note_type_id')
->leftJoin('users', 'users.id', '=', 'notes.user_id')
->leftJoin('contacts', 'contacts.id', '=', 'notes.contact_id')
->orderBy($order[0], $order[1])
->select(array('notes.*', DB::raw('notes.id as nid')))
->get();
$this->layout->content = View::make('admin.notes.index', array(
'notes' => $notes,
'created_at' => DB::table('notes')->lists('created_at', 'created_at'),
'note_types' => DB::table('note_types')->lists('type', 'type'),
'contacts' => DB::table('contacts')->select(DB::raw('CONCAT(first_name," ",last_name) as cname'))->lists('cname', 'cname'),
'accounts' => Account::lists('company_name', 'company_name'),
'users' => DB::table('users')->select(DB::raw('CONCAT(first_name," ",last_name) as uname'))->lists('uname', 'uname'),
'notes_trash' => $notes_trash,
'perPage' => $perPage
));
}
Any advice would be appreciated. Thanks.
Solved this issue by using array_chunk function.
Here is the solution below:
foreach (array_chunk($data,1000) as $t)
{
DB::table('table_name')->insert($t);
}
There is limit 65,535 (2^16-1) place holders in MariaDB 5.5 which is supposed to have identical behaviour as MySQL 5.5.
Not sure if relevant, I tested it on PHP 5.5.12 using MySQLi / MySQLND.
This error only happens when both of the following conditions are met:
You are using the MySQL Native Driver (mysqlnd) and not the MySQL client library (libmysqlclient)
You are not emulating prepares.
If you change either one of these factors, this error will not occur. However keep in mind that doing both of these is recommended either for performance or security issues, so I would not recommend this solution for anything but more of a one-time or temporary problem you are having. To prevent this error from occurring, the fix is as simple as:
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
While I think #The Disintegrator is correct about the placeholders being limited. I would not run 1 query per record.
I have a query that worked fine until I added one more column and now I have 72k placeholders and I get this error. However, that 72k is made up of 9000 rows with 8 columns. Running this query 1 record at a time would take days. (I'm trying to import AdWords data into a DB and it would literally take more than 24 hours to import a days worth of data if I did it 1 record at a time. I tried that first.)
What I would recommend is something of a hack. First either dynamically determine the max number of placeholders you want to allow - i.e. 60k to be safe. Use this number to determine, based on the number of columns, how many complete records you can import/return at once. Create the full array of data for you query. Use a array_chunk and a foreach loop to grab everything you want in the minimum number of queries. Like this:
$maxRecords = 1000;
$sql = 'SELECT * FROM ...';
$qMarks = array_fill(0, $maxInsert, '(?, ...)');
$tmp = $sql . $implode(', ', $qMarks);
foreach (array_chunk($data, $maxRecords) AS $junk=>$dataArray) {
if (count($dataArray) < $maxRecords)) { break; }
// Do your PDO stuff here using $tmp as you SQL statement with all those placeholders - the ?s
}
// Now insert all the leftovers with basically the same code as above except accounting for
// the fact that you have fewer than $maxRecords now.
Using Laravel model, copy all 11000 records from sqlite database to mysql database in few seconds. Chunk data array to 500 records:
public function handle(): void
{
$smodel = new Src_model();
$smodel->setTable($this->argument('fromtable'));
$smodel->setConnection('default'); // sqlite database
$src = $smodel::all()->toArray();
$dmodel = new Dst_model();
$dmodel->setTable($this->argument('totable'));
$dmodel->timestamps = false;
$stack = $dmodel->getFields();
$fields = array_shift($stack);
$condb = DB::connection('mysql');
$condb->beginTransaction();
$dmodel::query()->truncate();
$dmodel->fillable($stack);
$srcarr=array_chunk($src,500);
$isOK=true;
foreach($srcarr as $item) {
if (!$dmodel->query()->insert($item)) $isOK=false;
}
if ($isOK) {
$this->notify("Przenieśliśmy tabelę z tabeli : {$this->argument('fromtable')} do tabeli: {$this->argument('totable')}", 'Będzie świeża jak nigdy!');
$condb->commit();
}
else $condb->rollBack();
}
You can do it with array_chunk function, like this:
foreach(array_chunk($data, 1000) as $key => $smallerArray) {
foreach ($smallerArray as $index => $value) {
$temp[$index] = $value
}
DB::table('table_name')->insert(temp);
}
My Fix for above issue:
On my side when i got this error I fixed it by reducing the the bulk insertion chunk size from 1000 to 800 and it worked for me.
Actually there were too many fields in my table and most them contains the details descriptions of size like a complete page text. when i go for there bulk insertion the service caused crashed and through the above error.
I think the number of placeholders is limited to 65536 per query (at least in older mysql versions).
I really can't discern what this piece of code is generating. But if it's a gigantic query, There's your problem.
You should generate one query per record to import and put those into a transaction.

YII SQL Query Optimization

I have a huge list of IDs that i need to query through a table to find if those IDs are available in the table, if yes fetch its model.
Since there are few thousands of IDs this process is really slow as I'm using CActiveRecord::find() mothod
ex. $book = Book::model()->find('book_id=:book_id', array(':book_id'=>$product->book_id));
I even indexed all possible keys, still no improvement.
Any suggestions to improve the execution speed?
thanks in advance :)
1)
Make a list of book ids
foreach $product in Product-List
$book_ids[$product->book_id] = $product->book_id;
Now query all Book models ( indexed by book_id )
$books = Book::model()->findAll(array(
'index' => 'book_id',
'condition' => 'book_id IN (' . implode(',', $book_ids). ')',
));
Integrate $books in your code, I believe you are looping through all products.
foreach $product in Product-List
if( isset($books[$product->book_id]) )
$model = $books[$product->book_id]
2) Another way (I am just assuming you have Product model)
in Product model add a relation to Book
public function relations() {
.......
'book'=>array(self::HAS_ONE, 'Book', 'book_id'),
.......
}
While retrieving your product list, add 'with' => array('book') condition, with any of CActiveDataProvider or CActiveRecord ...
//Example
$productList = Product::model()->findAll(array(
'with' => array('book'),
));
foreach( $productList as $product ) {
.......
if( $product->book != null )
$model = $product->book;
......
}
with either way you can reduce SQL queries.
Better if you use schema caching because Yii fetches schema each time we execute a query. It will improve your query performance.
You can enable schema caching by doing some configuration in config/main.php file.
return array(
......
'components'=>array(
......
'cache'=>array(
'class'=>'system.caching.CApcCache', // caching type APC cache
),
'db'=>array(
...........
'schemaCachingDuration'=>3600, // life time of schema caching
),
),
);
One more thing you can fetch specific column of the table that will improve performance also.
You can do it by using CDbCriteria with find method of CActiveRecord.
$criteria = new CDbCriteria;
$criteria->select = 'book_id';
$criteria->condition = 'book_id=:book_id';
$criteria->params = array(':book_id'=>$product->book_id);
$book = Book::model()->find($criteria);
I would suggest you to use any nosql database if you are processing thousands of records if that is suitable.

Codeigniter - How to get records from database where one fields value is more than another?

In my database I have two columns named 'amount_raised' and 'funding_goal'.
I would like to get all records from my database where the 'amount_raised' is equal to or more than the 'funding_goal'.
I am using Codeigniters active record. This is what I have so far:
function get_recently_successful($limit, $offset){
$data = '';
$this->db->order_by('date','desc');
$this->db->where('published', '1');
$this->db->where('amount_raised >=', 'funding_goal');
$query = $this->db->limit($limit, $offset)->get('projects');
foreach ($query->result() as $row) {
$data[] = array(
'id' => $row->id,
'date' => $row->date,
'project_title' => $row->project_title,
);
}
return $data;
}
The code above just returns all values in the database. Not how I specified it with where. How can I make it work??
Try this instead.
$this->db->where('amount_raised >= funding_goal');
Right now you send the value 'funding_goal' through the query, thus making it:
WHERE amount_raised >= 'funding_goal'
You want it compare with a column and not a string:
WHERE amount_raised >= funding_goal
You can always troubleshoot your query by inserting:
echo $this->db->last_query();
After the $query = row.
If you are using multiple condition in array, you can try the below as well:
$this->db->where(array('status' => 'Active', 'category' => $catId));
You can use another condition as well like this:
$this->db->where('amount_raised >= funding_goal');
You can use the last_query() to see the sql query as well to understand it.