Laravel datatable taking too much of time to load data - mysql

Here loading orders data using below code but it takes too much time to load and I'm also using serverSide and there are only 320 data's something and TTFB are very high and I also tried "pageLength": 30, "stateSave": true, and using Cloudflare and Nginx server but the result is same please let me know how I fix this issue. I tried most of the solutions but still having issues.
Controller.php
$orders = new Order;
$search = $request->search['value'];
$filter_type = $request->filter_type;
if ($filter_type == "custom") {
$from = date('Y-m-d' . ' 00:00:00', strtotime($request->from_dates));
if ($request->has('to_dates')) {
$to = date('Y-m-d' . ' 23:59:59', strtotime($request->to_dates));
$orders = $orders->whereBetween('created_at', array($from, $to));
}
}
elseif ($filter_type == "daily") {
$orders = $orders->where('created_at', '>=', Carbon\Carbon::today());
}
elseif ($filter_type == "weekly") {
$fromDate = Carbon\Carbon::now()->subDay()->startOfWeek()->toDateString();
$tillDate = Carbon\Carbon::now()->subDay()->endOfWeek()->toDateString();
$orders = $orders->whereBetween(DB::raw('date(created_at)'), [$fromDate, $tillDate]);
}
elseif ($filter_type == "monthly") {
$orders = $orders->whereRaw('MONTH(created_at) = ?', [date('m')]);
}
elseif ($filter_type == "yearly") {
$orders = $orders->whereRaw('YEAR(created_at) = ?', [date('Y')]);
}
$orders = $orders->orderByDesc('id')->select();
$orders = $orders->get();
$datatable = DataTables::of($orders)
->addColumn('id', function ($orders) {
return #$orders->id;
})
->addColumn('payment_type', function ($orders) {
return #$orders->payment_type_text;
})
->addColumn('user_name', function ($orders) {
return #$orders->user->name;
})
->addColumn('store_name', function ($orders) {
return #$orders->store->name;
})
->addColumn('service_type', function ($orders) {
return #$orders->store->service_type1->service_name;
})
->addColumn('total', function ($orders) {
return html_entity_decode(currency_symbol() . (#$orders->total_amount+#$orders->wallet_amount));
})
->addColumn('status_text', function ($orders) {
return #$orders->status_text;
})
->addColumn('action', function ($orders) {
return '<a title="' . trans('admin_messages.view') . '" href="' . route('admin.view_order', $orders->id) . '" ><i class="material-icons">edit</i></a>';
});
$columns = ['id', 'payment_type', 'user_name', 'store_name', 'total', 'status_text'];
$base = new DataTableBase($orders, $datatable, $columns, 'Orders');
return $base->render(null);
Blade.php
#extends('admin/template')
#section('main')
<?php flush(); ?>
<div class="content" ng-controller="statements" ng-cloak>
<div class="card">
<div class="card-header card-header-rose card-header-text">
<div class="card-text">
<h4 class="card-title">{{$form_name}}</h4>
</div>
</div>
<div class="card-body ">
<div class="table-responsive">
<table id="statement_table" class="table table-condensed w-100">
</table>
</div>
</div>
</div>
</div>
</div>
#endsection
#push('scripts')
<link rel="stylesheet" href="{{asset('admin_assets/css/buttons.dataTables.css')}}">
<script src="{{asset('admin_assets/js/dataTables.buttons.js')}}">
</script>
<script src={{url('vendor/datatables/buttons.server-side.js')}}></script>
<script>
var column = [
{data: 'id', name: 'id', title: '{{trans("admin_messages.order_id")}}' },
{data: 'payment_type',name: 'payment_type',title: '{{trans("admin_messages.payment_type")}}',searchable: true},
{data: 'user_name',name: 'user_name',title: '{{trans("admin_messages.user_name")}}'},
{data: 'user_address',name: 'user_address',title: '{{trans("admin_messages.address")}}'},
{data: 'mobile_number',name: 'mobile_number',title: '{{trans("admin_messages.mobile_number")}}'},
{data: 'store_name',name: 'store_name',title: '{{trans("admin_messages.store_name")}}'},
{data: 'service_type',name: 'service_type',title: '{{trans("admin_messages.service_type")}}'},
{data: 'total',name: 'total',title: '{{trans("admin_messages.total")}}'},
{data: 'status_text',name: 'status_text',title: '{{trans("admin_messages.order_status")}}'},
{data: 'created_at',name: 'created_at',title: '{{trans("admin_messages.created_at")}}'},
{data: 'action',name: 'action',title: '{{trans("admin_messages.action")}}',orderable: false,searchable: false}
];
var oTable = $('#statement_table').DataTable({
dom:"lBfrtip",
buttons:["csv","excel","print"],
order:[0, 'desc'],
processing: true,
serverSide: true,
ajax: {
url: ajax_url_list['all_orders'],
data: function (d) {
d.filter_type = $('#filter_by').val();
d.from_dates = $('#from_date').val();
d.to_dates = $('#to_date').val();
}
},
columns: column
});
</script>
#endpush
Create Table
CREATE TABLE `order` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`store_id` int(10) unsigned DEFAULT NULL,
`user_id` int(10) unsigned DEFAULT NULL,
`driver_id` int(11) DEFAULT NULL,
`recipient` tinyint(4) DEFAULT NULL,
`subtotal` decimal(11,2) DEFAULT NULL,
`offer_percentage` decimal(11,2) DEFAULT NULL,
`offer_amount` decimal(11,2) DEFAULT NULL,
`promo_id` int(11) DEFAULT NULL,
`promo_amount` decimal(11,2) DEFAULT NULL,
`delivery_fee` decimal(11,2) DEFAULT NULL,
`booking_fee` decimal(11,2) DEFAULT NULL,
`store_commision_fee` decimal(11,2) DEFAULT NULL,
`driver_commision_fee` decimal(11,2) DEFAULT NULL,
`tax` decimal(11,2) DEFAULT NULL,
`total_amount` decimal(11,2) DEFAULT NULL,
`wallet_amount` decimal(11,2) DEFAULT NULL,
`payment_type` tinyint(4) DEFAULT NULL,
`owe_amount` decimal(11,2) DEFAULT NULL,
`store_owe_amount` decimal(11,2) DEFAULT NULL,
`applied_owe` decimal(11,2) DEFAULT NULL,
`status` tinyint(4) DEFAULT NULL,
`payout_status` tinyint(4) DEFAULT NULL,
`currency_code` varchar(3) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`est_preparation_time` time DEFAULT NULL,
`est_travel_time` time DEFAULT NULL,
`est_delivery_time` time DEFAULT NULL,
`cancelled_by` tinyint(4) DEFAULT NULL,
`cancelled_reason` int(10) unsigned DEFAULT NULL,
`cancelled_message` text COLLATE utf8mb4_unicode_ci,
`delay_min` time DEFAULT NULL,
`delay_message` text COLLATE utf8mb4_unicode_ci,
`schedule_status` tinyint(4) DEFAULT '0',
`payout_is_create` tinyint(4) DEFAULT '0',
`schedule_time` timestamp NULL DEFAULT NULL,
`notes` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`user_notes` text COLLATE utf8mb4_unicode_ci,
`store_notes` text COLLATE utf8mb4_unicode_ci,
`driver_notes` text COLLATE utf8mb4_unicode_ci,
`declined_at` timestamp NULL DEFAULT NULL,
`accepted_at` timestamp NULL DEFAULT NULL,
`cancelled_at` timestamp NULL DEFAULT NULL,
`delivery_at` timestamp NULL DEFAULT NULL,
`completed_at` timestamp NULL DEFAULT NULL,
`delivery_type` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`tips` decimal(11,2) DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `order_store_id_foreign` (`store_id`),
KEY `order_user_id_foreign` (`user_id`),
CONSTRAINT `order_store_id_foreign` FOREIGN KEY (`store_id`) REFERENCES `store` (`id`),
CONSTRAINT `order_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10268 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Route
Route::match(array('GET', 'POST'), '/all_orders','OrderController#all_orders')->name('all_orders');
I tried the below code and getting data faster than before 59.0s to 5.0s now its a good result but I don't know the code is an efficient way or not please guide me guys
$orders = Order::latest();
$search = $request->search['value'];
$filter_type = $request->filter_type;
if ($filter_type == "custom") {
$from = date('Y-m-d' . ' 00:00:00', strtotime($request->from_dates));
if ($request->has('to_dates')) {
$to = date('Y-m-d' . ' 23:59:59', strtotime($request->to_dates));
$orders = $orders->whereBetween('created_at', array($from, $to));
}
}
elseif ($filter_type == "daily") {
$orders = $orders->where('created_at', '>=', Carbon\Carbon::today());
}
elseif ($filter_type == "weekly") {
$fromDate = Carbon\Carbon::now()->subDay()->startOfWeek()->toDateString();
$tillDate = Carbon\Carbon::now()->subDay()->endOfWeek()->toDateString();
$orders = $orders->whereBetween(DB::raw('date(created_at)'), [$fromDate, $tillDate]);
}
elseif ($filter_type == "monthly") {
$orders = $orders->whereRaw('MONTH(created_at) = ?', [date('m')]);
}
elseif ($filter_type == "yearly") {
$orders = $orders->whereRaw('YEAR(created_at) = ?', [date('Y')]);
}
return Datatables::of($orders)
->addColumn('id', function ($orders) {
return #$orders->id;
})
->addColumn('payment_type', function ($orders) {
return #$orders->payment_type_text;
})
->addColumn('user_name', function ($orders) {
return #$orders->user->name;
})
->addColumn('user_address', function ($orders) {
return #$orders->user->user_address->first_address;
})
->addColumn('mobile_number', function ($orders) {
return #$orders->user->mobile_number;
})
->addColumn('store_name', function ($orders) {
return #$orders->store->name;
})
->addColumn('service_type', function ($orders) {
return #$orders->store->service_type1->service_name;
})
->addColumn('total', function ($orders) {
return html_entity_decode(currency_symbol() . (#$orders->total_amount+#$orders->wallet_amount));
})
->addColumn('status_text', function ($orders) {
return #$orders->status_text;
})
->addColumn('created_at', function ($orders) {
return #date("d-m-y h:i A", strtotime($orders->created_at));
})
->addColumn('action', function ($orders) {
return '<a title="' . trans('admin_messages.view') . '" href="' . route('admin.view_order', $orders->id) . '" ><i class="material-icons">edit</i></a>';
})->rawColumns(['id', 'payment_type','user_name','store_name','user_address','mobile_number','service_type','total','status_text','action'])
->make(true);

The "table scans" are killing performance.
A bigger cache may help
A better index may help
Look at how much RAM is unused on the server with MySQL. Increase innodb_buffer_pool_size to include most of that RAM.
(128M is an old default; it is pathetically low for most apps today on most hardware.)
If you are running an old version of MySQL, consider upgrading (for many reasons).
MONTH(created_at) = ? is much less efficient than
WHERE created_at >= '2021-02-01'
AND created_at < '2021-02-01' + INTERVAL 1 MONTH
However, that performance boost assumes some index includes created_at. None exists now.
If I can see the generated SQL for a typical SELECT, I can advise further on a better index.

You are firing off several queries for each order that is found. Fix that by using with and it'll only fire 1 query per table:
$orders = $orders->with(['user', 'store.service_type1'])->get();
https://laravel.com/docs/8.x/eloquent-relationships#eager-loading
If you have 500 orders found you'll be firing off over 1000 queries. This will reduce it to 4!
(Pretty minor extra if you want:) And you could make it 3 if you use a through relationship instead of store.service_type1 (which under the hood does 1 query with a join instead of 2 queries)

Enable query logs and check how much time the query is taking, this will you an idea if the table really needs updating.
I am mentioning some points below that can be done on the table regardless.
I see there is a column driver_id but you have not added any indexing or constraint on it.
If this column is not a foreign key then add basic indexing, Do the same for promo_id column.
Add indexing on column delivery_type.
If you can, create bitmap index on payment_type and payout_status
Finally I see you are running queries on timestamp columns, if possible add indexing for that too.
All this will help reduce the time to get the data from Database.
From code side if you are getting the data from different tables and relations, consider using with([]).
This will help by reducing the number of queries running.
If all of this doesn't help then check how much time the code is taking to process the request.
With all of this atleast you should be able to know if the table is an issue or the code.

You have 2 options for fix your issue
Add caching to fronend and backend so its not taking time. Also you can use mysql query cache also
In laravel uses eloquent orm so it takes time so try to use simple mysql query so it takes less time to execute

What you should be doing is paginate the results, as Im not seeing any pagination on your PHP side, and I imagine you have thousands of records, it will take you a long time to get.
Datatable when serverSide is activated, it sends a bunch of GET params on the query to the backend, amongst these is start and length
so on your controller, you should utilize these params

Code readability and performance
Optimizing your code is VERY important when you use loops and conditional branching.
I'd use switch instead of all those elseif conditionals.
Execution locks
Switching back and forth between PHP code execution, and HTML output will always slow things down. Sometimes by a LOT.
Generate your HTML in string variables instead of printing to buffer/output, and then output the entire page at once when finished.

The main issue is your end query : $orders->get();. You do not provide any pagination/limitation parameter, so this query will query the entire order table, and give the results in a Collection. If there is 10.000 rows matching your filter, they'll be returned in a single Collection.
Check the docs, but I think that Datatables accept an Eloquent instances, not only Collections.
So first, try removing the $orders = $orders->get(); line to directly pass to Datatable the Eloquent query instance.
Second, check the docs again, but I think that Datatable has a built-in way to handle the pagination (reload data and send the correct HTTP parameters from the frontend (next/prev or pagination click, sorting, searching...), apply the pagination to the Eloquent query). If so: try to use it (if it does not work directly out of the box, when passing the Eloquent instance instead of the Collection to the Datatable laravel method). If not, you'll need to implement pagination parameters with your HTTP request and apply them to the eloquent query.
Careful as data table will work (and be slow, cause the HTTP response is huge, and the DOM become huge also) with a 10.000 rows collection returned in one request. The sorting will work, the search also but very poorly as JS only sorting (CPU greedy, slow). It's clearly not what you should do.
In the end, all frontend actions: text searching, column sorting, pagination ... should request your Laravel API with custom GET parameters (for search, sort, pagination...), and your laravel controller should "translate" those GET parameters into Eloquent methods/queries. Also take care about eager-loading as #BenGooding mentioned.
UPDATE after OP question update: OP edited it's question and code snippets. In the first version of the question, OP was executing a ->get() without any pagination or limitation. It was so querying the entire table, and Databable methods was given a huge Collection with all the objects. Then, frontend JS datatable would process hundreds of row again, slowing the page even more.
Using my advice, OP changed his code: instead of querying the entire table, just pass the Eloquent query to the Datatable helper, as this libs can handle query pagination and some filtering itself, if you give it something to work with (not a definitive list of object, but a SQL query to adapt). Following this, OP reduced query time from 50 seconds to 5 seconds. And so it was the main issue with his Datatable management.

Get specific select column which do you want?
datatables()->eloquent($query) instance of DataTables::of($orders) (send to object not passed query result.)
Create separate file of datatable handling like datatable service file.
public function dataTable($query)
{
return datatables()
->eloquent($query)
->editColumn('status', function ($query) {
...
})
->addColumn('action', function ($query) {
...
})
->addIndexColumn()
->rawColumns(['status', 'action']);
}
public function query(Category $model)
{
return $model->newQuery();
}

Related

AWS Aurora DB Combine Data from Multiple SQL Statements

I have three tables with data schema, like:
TABLE user (
user_id BINARY(16) PRIMARY KEY NOT NULL,
created DATETIME NOT NULL,
last_updated DATETIME,
coordinator BINARY(16),
num_updates INT NOT NULL
);
TABLE summary (
user_id BINARY(16) PRIMARY KEY NOT NULL,
calculation_time DATETIME NOT NULL,
calculation_method VARCHAR(25) NOT NULL,
label VARCHAR(50) NOT NULL,
critical_count INT NOT NULL,
median_risk FLOAT(10)
);
TABLE actions(
user_id BINARY(16) PRIMARY KEY NOT NULL,
label VARCHAR(50) NOT NULL,
access_count INT NOT NULL,
median FLOAT(10)
);
The data for all the users (user table) is simply fetched using the lambda handler function in the following manner:
const AWS = require('aws-sdk');
const rdsDataService = new AWS.RDSDataService();
module.exports.hello = async (event, context, callback) => {
const req_id = "5a9dbfca-74d6-471a-af27-31beb4b53bb2";
const sql = 'SELECT * FROM user WHERE user_id=:id';
try {
const params = {
resourceArn: 'arn:aws:rds:us-west-********************',
secretArn: 'arn:aws:secretsmanager:us-west**************',
sql,
database: 'dev_db1',
continueAfterTimeout: true,
includeResultMetadata: true,
parameters: [{ 'name': 'id', 'value': { 'stringValue': `${req_id}` } }]
}
const db_res = await rdsDataService.executeStatement(params).promise();
const convertToJson = (dbresponse) => {
const columns = dbresponse.columnMetadata.map(col => col.name);
const row_data = dbresponse.records.map(row => {
const json_obj = {};
row.map((val, i) => {
json_obj[columns[i]] = Object.values(val)[0];
});
return json_obj;
});
return row_data;
};
const modified_data = convertToJson(db_res);
const response = {
body: {
statusCode: 200,
message: 'Data fetched successfully',
data: modified_data,
}
};
callback(null, response);
} catch (error) {
console.log('Error Received', error);
const error_res = {
body: {
statusCode: error.statusCode,
message: error.message,
data: null
}
}
callback(null, error_res);
}
};
If the same is followed for another table summary or actions, it also works. Now, I need to combine all the columns of these three tables and then return the data (returned rows should match on the basis of req_id).
My working snippet: https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=016ecc94c792611fbaca810605e81a6a
But the final result obtained contains the column user_id in duplicated form i.e. three times inclusion. I don't need the same column to be repeated thrice.
I am a bit new to handling MySQL queries, so unable to figure out the exact reason for the error even when the table exists. The MYSQL version used in Aurora is 5.7.
Any help to resolve the same is appreciated!
Plan A: Explicitly specify the columns you want. Extra benefit: You can get rid of the ids, which tend to be useless to others reading the output.
Plan B: (This option is not always possible.) Instead of JOIN .. ON t1.a = t2.a, say JOIN .. USING(a)
I like to use short aliases. Here's doing all things together:
SELECT u.last_name, u.first_name,
s.risk_score,
t.likes
FROM user AS u
JOIN summary AS s USING(user_id)
LEFT JOIN test AS t USING(user_id)
In general, it is not wise to have a 1:1 relationship (as you have via user_id); you may as well have all the columns in a single table.
try this
SELECT users.*, summary.* from users, summary WHERE users.user_id = summary.user_id
OR
SELECT * from users, summary WHERE users.user_id = summary.user_id

Storing 2 same id of post table with different language into database

I'm building a multi language website. In this case just 2 languages, it's Indonesian and English using Laravel.
I have posts table, it will store id of each post and post_translations table is to store local, title, and description of post.
I got the problem when storing data into database. I don't have any idea how to store post without inc the id except I've added 2 same post with Indonesian and English.
This is the result (wrong)
posts table
id
1
2
post_translations table
id post_id locale title
1 1 en fisrt post
2 2 id post yang pertama
Expexted result
posts table
id
1
post_translations table
id post_id locale title
1 1 en fisrt post
2 1 id post yang pertama
PostController
public function store(Request $request) {
$this->validate($request, [
'title' => 'required',
'slug' => 'required',
'content' => 'required'
]);
$post = new Post;
$post->title = $request->title;
$post->slug = $request->slug;
$post->content = $request->content;
$post->save();
return redirect()->route('post.index');
}
Ok, so here we go (please note that this is not the only way):
install spatie/laravel-translatable with
composer require spatie/laravel-translatable
**Note: for native spatie/laravel-translatable go to version 2 **
create a table with this structure:
CREATE TABLE articles (
id int(10) UNSIGNED NOT NULL,
title text COLLATE utf8_unicode_ci,
slug text COLLATE utf8_unicode_ci,
content text COLLATE utf8_unicode_ci,
created_at timestamp NULL DEFAULT NULL,
updated_at timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Note: beter use a migration. I just exported a table I did earlier to test
Insert the data in the datatabase in json format like this:
INSERT INTO `pages` (`id`, `title`, `slug`, `content`, `created_at`, `updated_at`) VALUES
(1, '{"ro":"Acas\\u0103","en":"Home"}', NULL, '{"ro":"<p><strong>Test title<\\/strong><\\/p>\\r\\n\\r\\n<p>Test content romanian<\\/p>\\r\\n","en":"<p><strong>test title english<\\/strong><\\/p>\\r\\n\\r\\n<p>test content english.<\\/p>\\r\\n"}', '2017-04-03 11:45:56', '2017-04-03 12:15:16');
Now create the blade to edit, update, create show etc. To get the language do something like this in the blade:
{{ $data->title }}
{!! nl2br($data->content) !!}
And in the controller:
add something like this:
/**
* Generate the field by language
*
* #param Model $entry the item selected from the database
*
* #return array
*/
public function getTranslatableFields($fields)
{
$inputs = [];
$languages = $this->getLanguages();
foreach ($languages as $language) {
foreach ($fields as $field) {
$inputs[] = [
'name' => "{$field['name']}[{$language->abbr}]",
'label' => $field['label'] . " ($language->abbr)",
'lang' => $language->abbr,
'type' => array_key_exists('type', $field) ? $field['type'] : 'text'
];
}
}
return $inputs;
}
I added this function in a LangTrait. Since I also use backpack for laravel I did some more things.
For edit I added this method in the trait:
/**
* Show the form for editing the specified resource.
*
* #param int $id the item's identifier
*
* #return Response
*/
public function edit($id)
{
$data['entry'] = Model::find($id);
$data['title'] = trans('lang_file.edit').' '.$this->entity_name; // name of the page
$data['fields'] = $this->getMultiLangFields($data['entry']);
$data['id'] = $id;
return view('crud::edit', $data);
}
/**
* Generate the field by language
*
* #param Model $entry the item selected from the database
*
* #return array
*/
protected function getMultiLangFields($entry)
{
$fields['id'] = ['name' => 'id', 'type' => 'hidden', 'value' => $entry->id];
foreach ($this->crud->update_fields as $key => $field) {
$value = null;
if (array_key_exists('lang', $field)) {
$name = preg_replace('/(\[\w{2}\])$/i', '', $field['name']);
$value = $entry->getTranslation($name, $field['lang']);
}
$fields[$key] = array_merge($field, ['value' => $value]);
}
return $fields;
}
/**
* Get the application active languages
*
* #return \Backpack\LangFileManager\app\Models\Language
*/
protected function getLanguages()
{
return Language::whereActive(1)->orderBy('default', 'desc')->get();
}
Finally in my main controller I did:
use LangTrait; (contains everything above)
In construct I added this:
$this->getTranslatableFields($fields)
where $fields it's the list of fields I need
All methods should be adapted to you html format. As I said I use backpack for laravel and fields are formatted accordingly
And finally for the getLanguage file to work I created a new table and a model in the DB:
Model:
class Language extends Model
{
protected $table = 'languages';
protected $fillable = ['name', 'flag', 'abbr', 'native', 'active', 'default'];
public $timestamps = false;
public static function getActiveLanguagesArray()
{
$active_languages = self::where('active', 1)->get()->toArray();
$localizable_languages_array = [];
if (count($active_languages)) {
foreach ($active_languages as $key => $lang) {
$localizable_languages_array[$lang['abbr']] = $lang;
}
return $localizable_languages_array;
}
return config('laravellocalization.supportedLocales');
}
public static function findByAbbr($abbr = false)
{
return self::where('abbr', $abbr)->first();
}
}
Table:
CREATE TABLE `languages` (
`id` int(10) UNSIGNED NOT NULL,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`app_name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`flag` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`abbr` varchar(3) COLLATE utf8_unicode_ci NOT NULL,
`script` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`native` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`active` tinyint(3) UNSIGNED NOT NULL DEFAULT '1',
`default` tinyint(3) UNSIGNED NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Data in the table:
INSERT INTO `languages` (`id`, `name`, `app_name`, `flag`, `abbr`, `script`, `native`, `active`, `default`, `created_at`, `updated_at`, `deleted_at`) VALUES
(1, 'English', 'english', '', 'en', 'Latn', 'English', 1, 0, NULL, NULL, NULL),
(2, 'Romanian', 'romanian', '', 'ro', 'Latn', 'română', 1, 1, NULL, NULL, NULL);
Since I did this through a package I kind of messed around a little bit with the code.
Now, for the spatie/laravel-translatable package version:
set up the service provider in config/app.php add this in the providers array:
Spatie\Translatable\TranslatableServiceProvider::class,
In the model Articles add use HasTranslations; like this:
use Illuminate\Database\Eloquent\Model;
use Spatie\Translatable\HasTranslations;
class NewsItem extends Model
{
use HasTranslations;
public $translatable = ['name']; // list the columns you want to be translatable (will have json format)
}
save a new entry and use it:
$article = new Article;
$article->setTranslation('name', 'en', 'Updated name in English')
->setTranslation('name', 'nl', 'Naam in het Nederlands');
$article->save();
$article->name; // Returns 'Name in English' given that the current app locale is 'en'
$article->getTranslation('name', 'nl'); // returns 'Naam in het Nederlands'
app()->setLocale('nl');
$article->name; // Returns 'Naam in het Nederlands'
examples from: https://github.com/spatie/laravel-translatable
Database table format is as stated above in the first version
If it does not work out let me know and I'll look at your code.
I think you should change your tables strucuture:
posts : id, slug.
post_translations : id, post_id, locale, title, content
Also add relation to your Post model:
public function translations()
{
return $this->hasMany(PostTranslation::class, 'post_id');
}
And update your controller:
$post = new Post;
$post->slug = $request->slug;
$post->save();
$post->translations()->create([
'locale' => 'en', //or grab it from $request
'title' => $request->title,
'content' => $request->content
])
It will create your post and add translation to it

How to show only per attribute

I'm trying to make a optics shop with PRESTASHOP, but I'm facing a problem. I've create 4 new columns in product and customer table (left eye diopter, right eye diopter, bridge length, leg length), the same in both tables.
What I want to do is when the shop load the products, compare this variables and if they are the same then show the product. This is to try to filter glasses to the client, only showing him the glasses that are compatible with him.
The original query is the next:
$sql = 'SELECT p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) AS quantity'.(Combination::isFeatureActive() ? ', IFNULL(product_attribute_shop.id_product_attribute, 0) AS id_product_attribute,
product_attribute_shop.minimal_quantity AS product_attribute_minimal_quantity' : '').', pl.`description`, pl.`description_short`, pl.`available_now`,
pl.`available_later`, pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`, image_shop.`id_image` id_image,
il.`legend` as legend, m.`name` AS manufacturer_name, cl.`name` AS category_default,
DATEDIFF(product_shop.`date_add`, DATE_SUB("'.date('Y-m-d').' 00:00:00",
INTERVAL '.(int)$nb_days_new_product.' DAY)) > 0 AS new, product_shop.price AS orderprice
FROM `'._DB_PREFIX_.'category_product` cp
LEFT JOIN `'._DB_PREFIX_.'product` p
ON p.`id_product` = cp.`id_product`
'.Shop::addSqlAssociation('product', 'p').
(Combination::isFeatureActive() ? ' LEFT JOIN `'._DB_PREFIX_.'product_attribute_shop` product_attribute_shop
ON (p.`id_product` = product_attribute_shop.`id_product` AND product_attribute_shop.`default_on` = 1 AND product_attribute_shop.id_shop='.(int)$context->shop->id.')':'').'
'.Product::sqlStock('p', 0).'
LEFT JOIN `'._DB_PREFIX_.'category_lang` cl
ON (product_shop.`id_category_default` = cl.`id_category`
AND cl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('cl').')
LEFT JOIN `'._DB_PREFIX_.'product_lang` pl
ON (p.`id_product` = pl.`id_product`
AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').')
LEFT JOIN `'._DB_PREFIX_.'image_shop` image_shop
ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop='.(int)$context->shop->id.')
LEFT JOIN `'._DB_PREFIX_.'image_lang` il
ON (image_shop.`id_image` = il.`id_image`
AND il.`id_lang` = '.(int)$id_lang.')
LEFT JOIN `'._DB_PREFIX_.'manufacturer` m
ON m.`id_manufacturer` = p.`id_manufacturer`
WHERE product_shop.`id_shop` = '.(int)$context->shop->id.'
AND cp.`id_category` = '.(int)$this->id
.($active ? ' AND product_shop.`active` = 1' : '')
.($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '')
.($id_supplier ? ' AND p.id_supplier = '.(int)$id_supplier : '');
I'm trying to modify it, but I don't have very clear how to. And in consecuence, I'm doing wrong things. I've added the next left joins to the query.
LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa ON pa.`id_product` = p.`id_product`
LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON pac.`id_product_attribute` = pa.`id_product_attribute`
LEFT JOIN `'._DB_PREFIX_.'attribute` attr ON attr.`id_attribute` = pac.`id_attribute`
LEFT JOIN `'._DB_PREFIX_.'attribute_lang` attr_lang ON (attr_lang.`id_attribute` = pac.`id_attribute` AND attr_lang.`id_lang` = '.(int)$id_lang.')LEFT JOIN `'._DB_PREFIX_.'attribute_group` attr_group ON attr_group.`id_attribute_group` = attr.`id_attribute_group`
LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` attr_group_lang ON attr_group_lang.`id_attribute_group` = attr.`id_attribute_group`
Thank you for any advice.
EDIT:
The new fields of the products are features inside prestashop (there aren't inside product table) sorry for the mistake.
I put the data model of prestashop for your info.
EDIT 2:
I'm trying now to achieve this by using a module, so my php file of my module has the next code. I've follow the code from CategoryController.php, but I don't know how to remove a product if the conditions are not satisfied.
<?php
if (!defined('_PS_VERSION_'))
exit;
class glassOptics extends Module
{
/* #var boolean error */
protected $_errors = false;
public function __construct()
{
$this->name = 'glassOptics';
$this->tab = 'front_office_features';
$this->version = '1.0';
$this->author = 'MAOL';
$this->need_instance = 0;
parent::__construct();
$this->displayName = $this->l('glassOptics');
$this->description = $this->l('...');
}
public function install()
{
if (!parent::install() OR
!$this->veopticasCustomerDB('add') OR
!$this->veopticasProductDB('add') OR
!$this->registerHook('hookActionProductListOverride')
return false;
return true;
}
public function uninstall()
{
if (!parent::uninstall() OR !$this->veopticasCustomerDB('remove') OR !$this->veopticasProductDB('remove'))
return false;
return true;
}
public function veopticasCustomerDB($method)
{
switch ($method) {
case 'add':
$sql = 'CREATE TABLE IF EXISTS `'._DB_PREFIX_.'customer_optics_data` (
`id_customer` int(10) UNSIGNED NOT NULL,
`left_dioptrics` decimal(20,6) NOT NULL DEFAULT '0.000000',
`right_dioptrics` decimal(20,6) NOT NULL DEFAULT '0.000000',
`bridge` decimal(20,6) NOT NULL DEFAULT '0.000000',
`leg` decimal(20,6) NOT NULL DEFAULT '0.000000',
`glass_width` decimal(20,6) NOT NULL DEFAULT '0.000000',
`glass_height` decimal(20,6) NOT NULL DEFAULT '0.000000'
) ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8';
break;
case 'remove':
$sql = 'DROP TABLE IF EXISTS `'._DB_PREFIX_ . 'customer_optics_data`';
break;
}
if(!Db::getInstance()->Execute($sql))
return false;
return true;
}
public function veopticasProductDB($method)
{
switch ($method) {
case 'add':
$sql = 'CREATE TABLE IF EXISTS `'._DB_PREFIX_.'product_optics_data` (
`id_product` int(10) UNSIGNED NOT NULL,
`left_dioptrics` decimal(20,6) NOT NULL DEFAULT '0.000000',
`right_dioptrics` decimal(20,6) NOT NULL DEFAULT '0.000000',
`bridge` decimal(20,6) NOT NULL DEFAULT '0.000000',
`leg` decimal(20,6) NOT NULL DEFAULT '0.000000',
`glass_width` decimal(20,6) NOT NULL DEFAULT '0.000000',
`glass_height` decimal(20,6) NOT NULL DEFAULT '0.000000'
) ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8';
break;
case 'remove':
$sql = 'DROP TABLE IF EXISTS `'._DB_PREFIX_ . 'product_optics_data`';
break;
}
if(!Db::getInstance()->Execute($sql))
return false;
return true;
}
public function hookActionProductListOverride($params)
{
$customer_settings = glassOptics::getCustomerSettings($this->context->customer);
if ($customer_settings) {
// Inform the hook was executed
$params['hookExecuted'] = true;
// Filter products here, you are now overriding the default
// functionality of CategoryController class.
// You can see blocklayered module for more details.
if ((isset($this->context->controller->display_column_left) && !$this->context->controller->display_column_left)
&& (isset($this->context->controller->display_column_right) && !$this->context->controller->display_column_right))
return false;
global $smarty;
if (!Configuration::getGlobalValue('PS_LAYERED_INDEXED'))
return;
$categories_count = Db::getInstance()->getValue('
SELECT COUNT(*)
FROM '._DB_PREFIX_.'layered_category
WHERE id_category = '.(int)Tools::getValue('id_category', Tools::getValue('id_category_layered', Configuration::get('PS_HOME_CATEGORY'))).'
AND id_shop = '.(int) Context::getContext()->shop->id
);
if ($categories_count == 0)
return;
// List of product to overrride categoryController
$params['catProducts'] = array();
$selected_filters = $this->getSelectedFilters();
$filter_block = $this->getFilterBlock($selected_filters);
$title = '';
if (is_array($filter_block['title_values']))
foreach ($filter_block['title_values'] as $key => $val)
$title .= ' > '.$key.' '.implode('/', $val);
$smarty->assign('categoryNameComplement', $title);
$this->getProducts($selected_filters, $params['catProducts'], $params['nbProducts'], $p, $n, $pages_nb, $start, $stop, $range);
// Need a nofollow on the pagination links?
$smarty->assign('no_follow', $filter_block['no_follow']);
foreach ($params['nbProducts'] as $product) {
$product_settings = glassOptics::getProductSettings($product);
if($product_settings){
$same_bridge = ($product_settings->bridge == $customer_settings->bridge ? true : false);
$same_leg = ($product_settings->leg == $customer_settings->leg ? true : false);
$same_glass_width = ($product_settings->glass_width == $customer_settings->glass_width ? true : false);
$same_glass_heigth = ($product_settings->glass_heigth == $customer_settings->glass_heigth ? true : false);
}
}
}
}
}
I'd recommend taking a different approach and use a module for that. You would create a module that upon installation creates a table_ something like customer_optics_data. Table structure could look something like this:
CREATE TABLE `'._DB_PREFIX_.'customer_optics_data` (
`id_customer` int(10) UNSIGNED NOT NULL,
`left_eye_diopter` int(10) UNSIGNED NOT NULL,
`right_eye_diopter` int(10) UNSIGNED NOT NULL,
`bridge_length` decimal(20,6) NOT NULL DEFAULT '0.000000',
`leg_length` decimal(20,6) NOT NULL DEFAULT '0.000000'
) ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8;
Then your module would hook onto actionProductListOverride hook, and this is where you would perform the check:
public function hookActionProductListOverride($params)
{
$customer_settings = MyDiopterModuleHelperClass::getCustomerSettings($this->context->customer);
if ($customer_settings) {
$params['hookExecuted'] = true;
// Filter products here, you are now overriding the default
// functionality of CategoryController class.
// You can see blocklayered module for more details.
}
}
The module would have a helper class MyDiopterModuleHelperClass that is there to register and obtain data to/from the customer_optics_data table.
This way you're not overriding the core, your updates will still function as normal, the worst thing that can happen is if the hook is suddenly removed from the future versions of PrestaShop, which is unlikely.
The module would also employ the following hooks:
displayCustomerIdentityForm - to display additional fields in My Personal information. This is where your customers would input their information for the module.
actionObjectCustomerAddAfter - this is where you would fetch that data from $_POST and save it in module's table
actionObjectCustomerUpdateAfter - this is where you would update the data if it has been changed by the customer or insert the data if for some reason it's not there.
Optionally, you could also hook the module onto
displayAdminCustomersForm - to display the additional fields in the customers form in your back office.

Codeigniter Model Getting Error: Column count doesn't match value count at row 2

I am parsing currency rates from a rss.xml feed that all works great. I am now trying to insert that data into a database called rates with a table called tblRates. I keep getting this error and do not know why. Here is the function in the model I am using to try to batch insert into the database.
function addIQDRates($Data){
if($this->db->insert_batch('tblRates', $Data, 'Currency'))
{
return $this->db->affected_rows();
}else{
return FALSE;
}
}
Also here is the foreach statement I am using in my controller to sort the data from the xml file and to insert it into the database.
$Data = array();
$Data = array();
$Count = 0;
foreach ($xml->channel->item as $currencyInfo) {
$Data[$Count]['Currency'] = trim(str_replace("/USD", "", $currencyInfo->title)); // UNIQUE
$Data[$Count]['PubDate'] = date('Y-m-d H:i:s', strtotime(trim($currencyInfo->pubDate)));
$Data['CXRate'] = trim(preg_replace("/[^0-9,.]/", "", str_replace("1 United States Dollar = ", "", $currencyInfo->description)));
$Data[$Count]['DateCreated'] = date('Y-m-d H:i:s');
$Count++;
}
$TotalRows = $this->mycron_model->addIQDRates($Data);
Also here is my Create Table statement
CREATE TABLE IF NOT EXISTS `tblRates` (
`RateID` int(11) NOT NULL AUTO_INCREMENT,
`Currency` varchar(50) NOT NULL,
`PubDate` datetime NOT NULL,
`CXRate` int(11) NOT NULL,
`DateCreated` datetime NOT NULL,
PRIMARY KEY (`RateID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
all help greatly appreciated.
I am not sure, you might have written $Data['CXRate'] instead of $Data[$Count]['CXRate'].
So the loop should like like below:
foreach ($xml->channel->item as $currencyInfo) {
$Data[$Count]['Currency'] = trim(str_replace("/USD", "", $currencyInfo->title)); // UNIQUE
$Data[$Count]['PubDate'] = date('Y-m-d H:i:s', strtotime(trim($currencyInfo->pubDate)));
$Data[$Count]['CXRate'] = trim(preg_replace("/[^0-9,.]/", "", str_replace("1 United States Dollar = ", "", $currencyInfo->description)));
$Data[$Count]['DateCreated'] = date('Y-m-d H:i:s');
$Count++;
}

how to get last insert id after insert query in codeigniter active record

I have an insert query (active record style) used to insert the form fields into a MySQL table. I want to get the last auto-incremented id for the insert operation as the return value of my query but I have some problems with it.
Inside the controller:
function add_post(){
$post_data = array(
'id' => '',
'user_id' => '11330',
'content' => $this->input->post('poster_textarea'),
'date_time' => date("Y-m-d H:i:s"),
'status' => '1'
);
return $this->blog_model->add_post($post_data);
}
And inside model:
function add_post($post_data){
$this->db->trans_start();
$this->db->insert('posts',$post_data);
$this->db->trans_complete();
return $this->db->insert_id();
}
I get nothing as the return of the add_post in model
Try this
function add_post($post_data){
$this->db->insert('posts', $post_data);
$insert_id = $this->db->insert_id();
return $insert_id;
}
In case of multiple inserts you could use
$this->db->trans_start();
$this->db->trans_complete();
A transaction isn't needed here, this should suffice:
function add_post($post_data) {
$this->db->insert('posts',$post_data);
return $this->db->insert_id();
}
$id = $this->db->insert_id();
From the documentation:
$this->db->insert_id()
The insert ID number when performing database inserts.
Therefore, you could use something like this:
$lastid = $this->db->insert_id();
Using the mysqli PHP driver, you can't get the insert_id after you commit.
The real solution is this:
function add_post($post_data){
$this->db->trans_begin();
$this->db->insert('posts',$post_data);
$item_id = $this->db->insert_id();
if( $this->db->trans_status() === FALSE )
{
$this->db->trans_rollback();
return( 0 );
}
else
{
$this->db->trans_commit();
return( $item_id );
}
}
Source for code structure: https://codeigniter.com/user_guide/database/transactions.html#running-transactions-manually
It is worth saying that the other answers relate to Codeigniter version 3. The answer in Version 4 (found https://codeigniter.com/user_guide/database/helpers.html) is to use $this->db->insertID()
because you have initiated the Transaction over the data insertion so,
The first check the transaction completed or not. once you start the transaction, it should be committed or rollback according to the status of the transaction;
function add_post($post_data){
$this->db->trans_begin()
$this->db->insert('posts',$post_data);
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE){
$this->db->trans_rollback();
return 0;
}else{
$this->db->trans_commit();
return $this->db->insert_id();
}
}``
in the above, we have committed the data on the successful transaction even you get the timestamp
Just to complete this topic:
If you set up your table with primary key and auto increment you can omit the process of manually incrementing the id.
Check out this example
if (!$CI->db->table_exists(db_prefix() . 'my_table_name')) {
$CI->db->query('CREATE TABLE `' . db_prefix() . "my_table_name` (
`serviceid` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
`hash` varchar(32) NOT NULL,
`url` varchar(120) NOT NULL,
`datecreated` datetime NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT '1'
) ENGINE=InnoDB DEFAULT CHARSET=" . $CI->db->char_set . ';');
Now you can insert rows
$this->db->insert(db_prefix(). 'my_table_name', [
'name' => $data['name'],
'hash' => app_generate_hash(),
'url' => $data['url'],
'datecreated' => date('Y-m-d H:i:s'),
'active' => $data['active']
]);
**Inside Model**
function add_info($data){
$this->db->insert('tbl_user_info',$data);
$last_id = $this->db->insert_id();
return $last_id;
}
**Inside Controller**
public function save_user_record() {
$insertId = $this->welcome_model->save_user_info($data);
echo $insertId->id;
}
You must use $lastId = $this->db->insert_id();