I am trying to display database table to laravel datatable using below code but laravel datatable taking long time to load data.
I have approx 700000 records in database. How to decrease time of load data?
Code
web.php
Route::get('home', 'HomeController#index')->name('home');
HomeController.php
public function index()
{
$campaigns = TonicData::select('campaign')->distinct()->get();
if (request()->ajax()) {
$data = \DB::table('tonic_data')
->whereNotNull('subid4')
->where('subid4', '!=', '')
->select('subid4')
->groupBy('subid4')
->selectRaw('sum(view) as sum_of_views, sum(term_view) as sum_of_term_views,
sum(add_click) as sum_of_add_click, sum(revenue_usd) as sum_of_revenue,
(sum(revenue_usd)/sum(view)*1000) as rpm')
->when((request()->has('selectedCampaign') && request()->get('selectedCampaign') != ''), function ($query) {
$query->whereIn('campaign', request()->get('selectedCampaign'));
})
->when((request()->has('selectedDateRange') && request()->get('selectedDateRange') != ''), function ($query) {
$query->whereBetween('day_date', [request()->get('selectedDateRange')['fromDate'], request()->get('selectedDateRange')['toDate']]);
});
return DataTables::of($data)
->addIndexColumn()
->make();
}
return view('dashboard', compact('campaigns'));
}
dashboard.blade.php
<script type="text/javascript">
$(document).ready(function() {
$('#datatable').dataTable({
responsive: true,
processing: true,
serverSide: true,
ajax: '{{ route('home') }}',
columns: [
{data: 'DT_RowIndex', orderable: false, searchable: false},
{data: 'subid4', name: 'subid4'},
{data: 'sum_of_views', name: 'sum_of_views', searchable: false},
{data: 'sum_of_term_views', name: 'sum_of_term_views', searchable: false},
{data: 'sum_of_add_click', name: 'sum_of_add_click', searchable: false},
{data: 'sum_of_revenue', name: 'sum_of_revenue', searchable: false},
{data: 'rpm', name: 'rpm', searchable: false}
]
});
});
</script>
sql code
query 1:
select count(*) as aggregate
from ( SELECT `subid4`,
sum(view) as sum_of_views,
sum(term_view) as sum_of_term_views,
sum(add_click) as sum_of_add_click,
sum(revenue_usd) as sum_of_revenue,
(sum(revenue_usd)/sum(view)*1000) as rpm
from `tonic_data`
where `subid4` is not null
and `subid4` != ?
group by `subid4`
) count_row_table
query 2:
select `subid4`, sum(view) as sum_of_views, sum(term_view) as sum_of_term_views,
sum(add_click) as sum_of_add_click, sum(revenue_usd) as sum_of_revenue,
(sum(revenue_usd)/sum(view)*1000) as rpm
from `tonic_data`
where `subid4` is not null
and `subid4` != ?
group by `subid4`
limit 10 offset 0
Thanks in advance!
To summarize the discussion, as your query try to get all 700k records at once, no wonder it takes a long time.
Al though you are showing only a limited number of records while rendering the page, technically query gets all records on page load itself.
Action Required
Minimize the below section:
->selectRaw('sum(view) as sum_of_views, sum(term_view) as sum_of_term_views,
sum(add_click) as sum_of_add_click, sum(revenue_usd) as sum_of_revenue,
(sum(revenue_usd)/sum(view)*1000) as rpm')
Instead of sum up all these values on the fly, maintain a separate table and store all these values when any record gets altered in the tonic_data. There are callback functions as per this answer.
Instead of datatables use laravel pagination and sorting which will perform on server side that controls loading the huge data on page load.
try to implement date based or other possible filters.
Looks like Laravel is running the same query twice -- once to get a row count, once to get the first 10 rows.
Because of the GROUP BY, the LIMIT 10 has little effect on the speed. This is because it must gather lots of rows, then do the grouping, and only finally deliver 10 rows.
The first query (to get the count) could be simplified and greatly sped up by changing to
select count(DISTINCT subid4) as aggregate
from `tonic_data`
where `subid4` is not null
and `subid4` != ?
and having
INDEX(subid4)
Or forego of the count (if Laravel lets you do that).
Please provide SHOW CREATE TABLE tonic_data; I may have further tips.
First of all I suggest looking into your query performance and try to optimize your query.
Secondly jquery datatables plugin has a feature (named as pipeline) to cache number of pages to reduce ajax calls. So this way let's say on first request if you have cached 10 pages, then it won't make another request to server until the 11th page is accessed and this goes on.
So on client side your ajax will be updated to
<script type="text/javascript">
$(document).ready(function() {
$('#datatable').dataTable({
responsive: true,
processing: true,
serverSide: true,
ajax: $.fn.dataTable.pipeline({
url: '{{ route('home') }}',
pages: 20 // number of pages
})
});
});
</script>
For more help on pipeline, please refer https://datatables.net/examples/server_side/pipeline.html
Related
I'm currently displaying a table returning player rankings and highlighting the current player row. What I would like to achieve is let's say display on 5 rows and center the current player at the center.
If the current player ranking is 100th, I want to display player ranks 98 to 102. Currently it will display rank 1 to 5.
I can't find a want to "center my results" on the current player ranking.
My current table is displayed with this code:
$.ajax({
url: "myController/getDataTable",
method: 'post',
dataType: 'json',
success: function (values) {
var table = $('#myTableLeaderboard').DataTable({
paging: true,
pageLength: 5,
sort: true,
searching: true,
data: values['data'],
ordering: true,
aoColumns: [
{ "data": "ranking" },
{ "data": "cash" },
{ "data": "staff_count" }
// more columns...
],
"createdRow": function ( row, data, index ) {
$currentUserId = $('#currentUserId').attr('value');
if ( data.id_player == $currentUserId ) {
$('td', row).addClass('bold');
}
}
});
$('#myTableLeaderboard_filter').switchClass( 'dataTables_filter', 'dataTables_filter_show' );
}
});
My server side PHP function returns two arrays, one containing the data for the table and one variable containing the current player rank:
{"data":
[{"cash":"606, "id_player":"61","staff_count":null,"ranking":1},
{"cash":"30","id_player":"63","staff_count":null,"ranking":2},
...more players....],
"current_id_player":"67",
"current_player_ranking":3
}
I looked into the Custom filtering - range search page but I'm not sure that's what I should look into and how to approach it.
Finally, paging is enabled so if the player navigates to another page, the results won't be "centered" on him anymore.
I found the solution with adding:
displayStart: values.current_player_ranking-2,
And completed with jumpToData plugin
$('#myTableLeaderboard thead').on('click', 'th', function () {
table.page.jumpToData( $currentUserId, 9 );
} );
table.page.jumpToData( $currentUserId, 9 );
I have a table in database with 73k records.
So I want to show it in only one table on view using Datatables.
This is table should be:
Datatables in client side
I already have a table with datatables but It's limited item.
The first column is not in database,
"Show Image" column is special feature, when manager hover over this text, the image shown
I want to use datatable in server side to load all data with changing the appearance.
So I make a controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use Yajra\Datatables\Datatables;
use App\Models\GS1;
class DataTablesController extends Controller
{
private $sa24Repository;
// function __construct(SA24Repository $sa24Repository)
// {
// $this->sa24Repository = $sa24Repository;
// }
public function getIndex() {;
return Datatables::of(Product::query())->make(true);
}
}
?>
with a simple view:
Products table
It work well but when I type in search input, It search all column.
And I have to add this code to using filter:
<script>
$('.testTable').dataTable();
$.fn.dataTable.ext.errMode = 'throw';
$('.testTable').dataTable({
destroy: true,
processing: true,
serverSide: true,
ajax: '/datatables',
columns: [
{data: 'gtin'},
{data: 'brand_name'},
{data: 'description_short'}
]
});
</script>
If I remove this codes:
columns: [
{data: 'gtin'},
{data: 'brand_name'},
{data: 'description_short'}
]
When I search Browser logged:
Uncaught Error: DataTables warning: table id=DataTables_Table_0 - Requested unknown parameter '0' for row 0, column 0. For more information about this error, please see http://datatables.net/tn/4
at Ae (all.js?v=1484881746:6)
at k (all.js?v=1484881746:5)
at j (all.js?v=1484881746:5)
at M (all.js?v=1484881746:5)
at U (all.js?v=1484881746:6)
at all.js?v=1484881746:5
at d (all.js?v=1484881746:5)
at Object.success (all.js?v=1484881746:5)
at d (all.js?v=1484881746:2)
at Object.fireWith [as resolveWith] (all.js?v=1484881746:2)
and I can't search anything
So could you help me to using datatable to search any columns I want to,
and I don't have to add this snippet in script:
columns: [
{data: 'gtin'},
{data: 'brand_name'},
{data: 'description_short'}
]
Well the columns array determines what to display in the Datatables. Think about it, if the client doesn't even know what columns to display, how would it render the table?
If you want to only search limited columns, you can add searchable: false in columns like so (say you dont want to search based on brand name):
columns: [
{data: 'gtin'},
{data: 'brand_name', searchable: false},
{data: 'description_short'}
]
I have already fixed this by modifying in DatatableController. And it worked well
I have a Hotel Model, which hasMany Guests, which in turn has two dynamic attributes as follows.
public function getNameAttribute(){
return $this->user->name;
}
public function getEmailAttribute(){
return $this->user->email;
}
Now I prefer to fetch them as $hotel->guests()->with('user'). Now when I use it for Datatable, I am not able to get the name & email in the view, for obvious reasons. So my question is, Can I eager-load the name & email attributes, so that whenever the guest model gets fetched, the said attributes are available.
Please note, I don't return it asArray or asJson, hence the concept of $attributes array doesn't apply (at-least as far as I know).
The Controller has the following code:
return Datatables::of($guests)
->addColumn('email',function($guest){
return $guest->email;
})
->addColumn('name',function($guest){
return $guest->name;
})->make(true);
However, the datatable cannot search for name or email because they are not available in database.
The jquery datatable code I use is
$(function() {
$('#guest-table').DataTable({
processing: true,
serverSide: true,
ajax: '{!! route('guests.data') !!}',
columns: [
{ data: 'name', name: 'name', orderable: false },
{ data: 'email', name: 'email', orderable: false},
{ data: 'mobile', name: 'mobile', orderable: false}
]
});
});
I use Laravel 5 and yajra/laravel-datatables package
as per the Yajra, Laravel document, you have to call the property of the related model in this way,
{data: 'relation_name.column', name: 'relationName.column'}
so in your case it will be like this,
{data: 'user.name', name: 'user.name'}
{data: 'user.email', name: 'user.email'}
User double quotes here, I will work if this code is inside blade file
ajax: "{!! route('guests.data') !!}",
If you have external js file then use like this
Create a hidden field in blade file
<input type="hidden" id="route_url" value="{!! route('guests.data') !!}">
And in js file take route url by id
ajax: $("#route_url").val(),
I have a page containing a ExtJS 4.2.2 dataStore and gridPanel showing items created using information from various tables in a MySQL database.
The purpose of the generated grid is to provide a "confirmation" to the user before any information is actually generated in the database in a table separate to those that originated the initial information, as some items can be flagged to be imported; unflagged items are not imported. The page uses HTML, PHP (with CodeIgniter MVC framework) and ExtJS (which implies Javascript and JQuery).
With my current understanding of ExtJS, I would perform the necessary database insert or update by:
Saving all the changes in the grid to the store
Packaging the store's data into a multidimensional array, which is then assigned into a POST variable in some similar manner to a form
Perform a POST to the same page, picking up the POST variable
Loop through the POST information, passing each row's data to the model to perform the SQL query and INSERT/UPDATE as necessary.
I'm thinking this is a long-winded and most likely inefficient method for doing this; having previously worked with Kendo UI, I would have thought there is some form of "save" action on the store that simply calls a controller/model function that would act on the data in fewer steps.
Having looked at Ext.data.Store's numerous potential candidates (the commitChanges method, update event, the write event, store.sync etc.), I have to say I'm completely bewildered by how I could perform the kind of action I'm thinking of; I need to write the information to a table separate from those that generated the initial information, and none of the above seem to do that.
I would appreciate a "simple words" play-by-play of how to get the "dirty" data from the grid saved to the store, then the store data being passed to a MVC Controller-then-Model function that allows for update/insert to the DB (if that is the best way to do this) - I also appreciate that I will have to modify aspects of my ExtJS code, but what about my current setup isn't helping, if anything?
ExtJS code below renders the grid with data, and not much else:
Ext.Loader.setConfig({
enabled: true
});
Ext.Loader.setPath('Ext.ux', '../ux');
Ext.require([
'Ext.ux.CheckColumn'
]);
Ext.onReady(function(){
Ext.define('ConfirmationModel', {
extend: 'Ext.data.Model',
fields: [
'GroupName', // generated if not a member of a group, will eventually be retrieved if already existing in group by checking import table for JobNo, ItemNo and ItemTypeId
'ItemType',
'ItemTypeId',
'JobNo',
'ItemNo',
'ThicknessDepth',
'Cutter',
'CutterId',
'Source',
'Qty',
'Material',
'MaterialId',
'Type',
'TypeId',
'Work',
'WorkId',
'PaintInternal',
'PaintExternal',
'Notes',
'Documented',
'ImportDate', // fetched by checking import table for jobNo, itemNo and itemTypeId
'ImportCheck' // usually automatically checked if not yet imported
]
});
confirmationStore = Ext.create('Ext.data.Store',{
model: 'ConfirmationModel',
autoLoad: true,
proxy: {
// load using HTTP
type: 'ajax',
url: '<?= $site_url ?>Production/ConfirmationJSON/<?php echo $job_no ?>', // currently pulls all the information from three separate tables, pushes to object arrays then converts to and returns as JSON; will also check import table for existing entries as further work
pageParam: false, //to remove param "page"
startParam: false, //to remove param "start"
limitParam: false, //to remove param "limit"
noCache: false, //to remove param "_dc"
reader: {
type: 'json',
model: 'ConfirmationModel'
}
},
groupField: 'GroupName'
});
confirmationGrid = Ext.create('Ext.grid.Panel', {
width: 1200,
store: confirmationStore,
title: 'Import Confirmation',
tbar: [
{
xtype: 'button',
text: 'Update',
handler: function(){
// function to handle the save/update of information goes here?
document.location.href = '<?php echo site_url() ?>Production/Schedule'; // redirect to next page in steps
}
},
{
xtype: 'button',
text: 'Cancel',
handler: function(){
window.history.back();
}
}
],
columns: [
{ text: 'Item<br />No.', width:50, dataIndex: 'ItemNo' },
{ text: 'Job<br />No.', width: 65, dataIndex: 'JobNo' },
{ text: 'Item Type', width: 70, dataIndex: 'ItemType' },
{ text: 'Thickness<br />Depth', width: 65, dataIndex: 'ThicknessDepth' },
{ text: 'Cutter', width: 65, dataIndex: 'Cutter' },
{ text: 'Source', width: 65, dataIndex: 'Source' },
{ text: 'Qty', width: 40, dataIndex: 'Qty' },
{ text: 'Material', flex: 1, dataIndex: 'Material' },
{ text: 'Type', flex: 2, dataIndex: 'Type' },
{ text: 'Work', flex: 2, dataIndex: 'Work' },
{ text: 'Paint - Internal', flex: 2, dataIndex: 'PaintInternal' },
{ text: 'Paint - External', flex: 2, dataIndex: 'PaintExternal' },
{ text: 'Notes', flex: 2, dataIndex: 'Notes' },
{ text: 'Documented?', width: 75, dataIndex: 'Documented' },
{ text: 'Import<br />Date', width: 60, dataIndex: 'ImportDate' },
{
xtype: 'checkcolumn',
sortable: false,
header: 'Import?',
width: 50,
dataIndex:'ImportCheck'
}
],
features: [{
id: 'group',
ftype: 'groupingsummary',
groupHeaderTpl: '{name}',
enableGroupingMenu: false
}],
renderTo: Ext.get('sencha_confirmation')
});
});
There's no need to save data from the grid to the store, since the content you're seeing in the grid is there because it is already in the store (given that the store is the data provider for the grid).
To send the data in this format, you simply need to configure your proxy's writer's allowSingle: false. This will force the proxy to send through the records it is persisting as an array always, even if there is only one record.
Example:
proxy: {
writer: {
allowSingle: false
},
...
}
This can be accomplished by calling sync() on your store. Sync will automatically create a request to the url specified on your proxy, and will batch together new records, updated records, and deleted records. You can call this manually, or add autoSync: true to your store (I prefer controlling when it's called, but that's up to you).
Re: the MVC side of things, that's dependent on how your application is structured. For example, if you have a "save" button on the grid, you could bind to the button's click event, and in your controller listen for this click event and call your store's sync() method there. Of course, this is just an example: there are an number of events/conditions you could respond to and do the same thing.
I'm using DataTables with pipelining. I works great except when I tried to enter an extra column to hold "edit" links. See this table.
Here is a snippet of server_processing.php showing the columns:
/* Array of database columns which should be read and sent back to DataTables.
* Use a space where you want to insert a
* non-database field (for example a counter or static image)
*/
$aColumns = array( 'user','email', );
And here is the clientside:
$(document).ready( function (){
$('#example').dataTable({
"bProcessing": true,
"bServerSide": true,
"sAjaxSource": "scripts/server_processing.php",
"fnServerData": fnDataTablesPipeline,
aoColumns: [null, null, {"bSortable": false}]
}).makeEditable({
sUpdateURL: "UpdateData.php",
sAddURL: "AddData.php",
sAddHttpMethod: "POST",
sDeleteURL: "DeleteData.php",
sDeleteHttpMethod: "POST",
aoColumns: [ { } , { } , null ]
});
});
So, why isn't this working?
Just done this exact same thing myself. I like to configure all my columns using aoColumnDefs, as you can add multiple configuration options for columns in one go.
// Disable sorting on the 3rd column
'aoColumnDefs': [{'aTargets': [2], 'bSortable': false}]
Note that aTargets is an array of column indexes you want to apply those settings to. So if you were to add more link columns (e.g. a Delete link), you can turn off sorting on those without rewriting the column definition every time.
// Disable sorting on the 3rd and 4th column
'aoColumnDefs': [{'aTargets': [2,3], 'bSortable': false}]
And, as I was saying, you can add further configuration options for columns in this same array:
// Disable sorting on the 3rd and 4th column and sort 1st column by string
'aoColumnDefs': [
{'aTargets': [2,3], 'bSortable': false}
{'aTargets': [0], 'sType': 'string'}
]