How to speed up the search function of datatables? - mysqli

here's my code to search in tables (datatables):
Total records: 333,213
Estimated time to search/appear the result: 5 to 10 seconds.
using : ajax: "sample.php", // json datasource
how to make it fast?
what should I fix the database or the code I'm using.
<?php
/* Database connection start */
include ('connectvl.php');
/* Database connection end */
// storing request (ie, get/post) global array to a variable
$requestData= $_REQUEST;
$columns = array(
// datatable column index => database column name
0=> 'id',
1=> 'FULLNAME',
2=> 'BrgyName',
3=> 'BDAY',
4=> 'RESSTREET'
);
// getting total number records without any search
$sql = "SELECT id";
$sql.=" FROM voterslist2012";
$query=mysqli_query($conn, $sql) or die("cswd_listofpendingprocessgrid.php: get employees");
$totalData = mysqli_num_rows($query);
$totalFiltered = $totalData; // when there is no search parameter then total number rows = total number filtered rows.
$sql = "SELECT id, FULLNAME, BrgyName, BDAY, RESSTREET";
$sql.=" FROM voterslist2012 WHERE 1=1";
if( !empty($requestData['search']['value']) ) { // if there is a search parameter, $requestData['search']['value'] contains search parameter
$sql.=" AND ( id LIKE '".$requestData['search']['value']."%' ";
$sql.=" OR FULLNAME LIKE '".$requestData['search']['value']."%' ";
$sql.=" OR BDAY LIKE '".$requestData['search']['value']."%' ";
$sql.=" OR BrgyName LIKE '".$requestData['search']['value']."%' )";
}
// If there is a search parameter
/*if( !empty($requestData['search']['value']) ) {
$search = mysqli_real_escape_string(
$conn,
// Match beginning of word boundary
"[[:<:]]".
// Replace space characters with regular expression
// to match one or more space characters in the target field
implode("[[.space.]]+",
preg_split("/\s+/",
// Quote regular expression characters
preg_quote(trim($requestData['search']['value']))
)
).
// Match end of word boundary
"[[:>:]]"
);
$sql.=" AND ( id REGEXP '$search' ";
$sql.=" OR FULLNAME REGEXP '$search' ";
$sql.=" OR BrgyName REGEXP '$search' ";
$sql.=" OR BDAY REGEXP '$search' ";
$sql.=" OR RESSTREET REGEXP '$search' )";
}*/
$query=mysqli_query($conn, $sql) or die("cswd_listofpendingprocessgrid.php: get employees");
$totalFiltered = mysqli_num_rows($query); // when there is a search parameter then we have to modify total number filtered rows as per search result.
$sql.=" ORDER BY ". $columns[$requestData['order'][0]['column']]." ".$requestData['order'][0]['dir']." LIMIT ".$requestData['start']." ,".$requestData['length']." ";
/* $requestData['order'][0]['column'] contains colmun index, $requestData['order'][0]['dir'] contains order such as asc/desc */
$query=mysqli_query($conn, $sql) or die("cswd_listofpendingprocessgrid.php: get employees");
$data = array();
while( $row=mysqli_fetch_array($query) ) { // preparing an array
$nestedData=array();
$nestedData[] = $row["id"];
$nestedData[] = $row["FULLNAME"];
$nestedData[] = $row["BDAY"];
$nestedData[] = $row["BrgyName"];
$nestedData[] = $row["RESSTREET"];
$data[] = $nestedData;
}
$json_data = array(
"draw" => intval( $requestData['draw'] ), // for every request/draw by clientside , they send a number as a parameter, when they recieve a response/data they first check the draw number, so we are sending same number in draw.
"recordsTotal" => intval( $totalData ), // total number of records
"recordsFiltered" => intval( $totalFiltered ), // total number of records after searching, if there is no searching then totalFiltered = totalData
"data" => $data // total data array
);
echo json_encode($json_data); // send data as json format
?>

Using LIKE in your WHERE clause is always going to be dead slow, since it bypasses any indexes present and uses a full table scan. I'm going to leave the issue of prepared statements vs. dynamic SQL for others to raise (You should be using prepared statements. ;) ), but if you make id your primary key and then do an exact match in your WHERE clause, the difference will bloody well astound you.
EDIT: If practicable, you should consider placing indexes on any fields you search frequently. Your BDAY column would be a good candidate. I'm not familiar enough with MySQL's optimizer to say if indexing FULLNAME or BRGYNAME be a good idea, but you could experiment.

Related

How to search numeric by Sphinx correctly?

I need make search on billion records in MySQL and it's very long process (it's works now). May be Sphinx help me? How correctly to configure Sphinx for search numbers? Should I use integer attribute for searching (not string field)?
I need to get only row where the timestamp 'nearest or equal' to query:
CREATE TABLE test ( date TIMESTAMP(6) UNIQUE, num INT(32) );
| 2018-07-02 05:50:33.084011 | 282 |
| 2018-07-02 05:50:33.084028 | 475 |
...
(40 M such rows... all timestamps is unique, so this column are unique index so I no need in create additional index I suppose.)
sphinx.conf:
source src1
{
type = mysql
...
sql_query = SELECT * FROM test
}
indexer...
Sphinx 3.0.3
...
indexing index 'test'...
collected 40000000 docs, 0.0 MB
In my test I find nearest timestamp to query:
$start = microtime(true);
$query = '2018-07-02 05:50:33.084011';
$connMySQL = new PDO('mysql:host=localhost;dbname=test','','');
$sql = "SELECT * FROM test WHERE date <= '$search' ORDER BY date DESC LIMIT 1";
$que = $connMySQL->query($sql);
$result = $que->fetchAll(PDO::FETCH_ASSOC);
$query = $connMySQL->query('reset query cache');
$connMySQL = null;
print_r ($result);
echo 'Time MySQL:'.(microtime(true) - $start).' sec.';
$start = microtime(true);
$query = '2018-07-02 05:50:33.084029';
$connSphinxQL = new PDO('mysql:host=localhost;port=9306;dbname=test','root','');
$sql = "SELECT * FROM test WHERE date <= '$search' ORDER BY date DESC LIMIT 1";
$que = $connSphinxQL->query($sql);
$result = $que->fetchAll(PDO::FETCH_ASSOC);
$query = $connSphinxQL->query('reset query cache');
$connSphinxQL = null;
print_r ($result);
echo 'Time Sphinx:'.(microtime(true) - $start).' sec.';
Output:
[date] => 2018-07-02 05:50:33.084011 [num] => 282 Time MySQL: 0.00193 sec.
[date] => 2018-07-02 05:50:33.084028 [num] => 475 Time Sphinx: 0.00184 sec.
I suggested to see some different resuts, but noticed that before indexing I have got the same result, so I think Sphinx searches directy in MySQL by the reason of my wrong configuration.
Only ask here I found: no text search
Should I use integer attribute for searching (not string field)?
Yes. But an added complication, is a index NEEDS at least one field (sphinx isnt really designed as a general database, its intended for text queries!)
Can synthesize a fake one.
sql_query = SELECT unix_timestamp(`date`) AS id, 'a' AS field, num FROM test
sql_attr_uint = num
Also shows that need a unique integer as the first column, to be a document_id, seems as your timestamp is unique, can use that. a UNIX_TIMESTAMP is a nice easy way to represent a timestamp as a plain integer.
Can use id in queries too, for filtering, so would need to convert to a timestamp at the same time.
$query = '2018-07-02 05:50:33.084011';
$id = strtotime($query)
$sql = "SELECT * FROM test WHERE id <= '$id' ORDER BY id DESC LIMIT 1";

Export data from SQL database to CSV file, some rows of data get split into more than one row

When I export data from my SQL database to CSV file, some rows of data (records) get split into more than one row, as if there is a CR. I know that one reason is the following: One of the columns of the data is "Notes" that contains text that sometimes does contain a CR; I understand why this causes a new row in the CSV, but I would like that not to happen, either. How can I strip the CR, but add a period+space to format the Note so it's readable even without the CR?
However, I also get the extra row even if there is no CR, meaning the CSV has a blank row after a record, or the Note is on an extra line. I've included a screenshot of a portion of the CSV file to illustrate this and also illustrate that not all records show the behavior.
Here is my code. I did not write this, I inherited it. Also, I am not very experienced writing code.
header('Content-Type: application/msexcel-tab');
header('Content-Disposition: attachment; filename="Invaders of Texas Data -- '.date("Y-m-d").'.xls"');
$whereclause = '';
$passclause = '';
$satellite = $_REQUEST['satellite'];
$collector = $_REQUEST['collector'];
$sn = $_REQUEST['sn'];
$cn = $_REQUEST['cn'];
if ($satellite){
$whereclause .= " AND `satellite_id` = ".$satellite." ";
$passclause .= "&satellite=".$satellite;
}
if ($collector){
$whereclause .= " AND `collector_id` = ".$collector." ";
$passclause .= "&collector=".$collector;
}
if ($sn){
$whereclause .= " AND `plant_id` LIKE '".$sn."' ";
$passclause .= "&sn=".$sn;
}
if ($cn){
$whereclause .= " AND `plant_id` LIKE '".$cn."' ";
$passclause .= "&cn=".$cn;
}
$count_sql = "
SELECT COUNT(*) AS `counttotal`
FROM `inv_sites`
WHERE 1
$whereclause
AND `valid` LIKE 'Yes'
;
";
//echo $count_sql;
$count_total = mysql_fetch_array(mysql_query($count_sql));
$sql = "
SELECT *
FROM `inv_sites`
WHERE 1
$whereclause
AND `valid` LIKE 'Yes'
ORDER BY `collection_date` ASC
;
";
$the_result = mysql_query($sql);
?>
Invaders of Texas
www.texasinvasives.org
Exported: <?= date("Y-m-d G:i"); ?>
Obs_ID Date USDA Species Time_Spent Satellite Collector Lat Long Location_Error Loc_Err_Units Disturbance Patch_Type Abundance Validated Valid_Name Valid_Date Notes
<?php
if ($this_row = mysql_fetch_array($the_result)){
do {
?>
<?=$this_row['site_id'];?> <?=$this_row['collection_date'];?> <?=$this_row['plant_id']?> <?=sn_from_usda($this_row['plant_id'])?> <?=$this_row['collection_time'];?> <?=satellite_from_id($this_row['satellite_id']);?> <?=$this_row['collector_id'];?> <?=$this_row['latitude'];?> <?=$this_row['longitude'];?> <?=$this_row['error'];?> <?=$this_row['error_unit'];?> <?=$this_row['disturbance'];?> <?=$this_row['patch_type'];?> <?=$this_row['abundance'];?> <?=$this_row['valid'];?> <?=$this_row['valid_name'];?> <?=$this_row['valid_date'];?> <?=$this_row['notes'];?>
<?php
} while ($this_row = mysql_fetch_array($the_result));
}
?>
I'd appreciate any help!! Thanks.
You could replace the newlines in the PHP or the SQL query.
You have the following line above.
<?=$this_row['site_id'];?> <?=$this_row['collection_date'];?> <?=$this_row['plant_id']?> <?=sn_from_usda($this_row['plant_id'])?> <?=$this_row['collection_time'];?> <?=satellite_from_id($this_row['satellite_id']);?> <?=$this_row['collector_id'];?> <?=$this_row['latitude'];?> <?=$this_row['longitude'];?> <?=$this_row['error'];?> <?=$this_row['error_unit'];?> <?=$this_row['disturbance'];?> <?=$this_row['patch_type'];?> <?=$this_row['abundance'];?> <?=$this_row['valid'];?> <?=$this_row['valid_name'];?> <?=$this_row['valid_date'];?> <?=$this_row['notes'];?>
Try replacing it with the below (the change is on the very end).
<?=$this_row['site_id'];?> <?=$this_row['collection_date'];?> <?=$this_row['plant_id']?> <?=sn_from_usda($this_row['plant_id'])?> <?=$this_row['collection_time'];?> <?=satellite_from_id($this_row['satellite_id']);?> <?=$this_row['collector_id'];?> <?=$this_row['latitude'];?> <?=$this_row['longitude'];?> <?=$this_row['error'];?> <?=$this_row['error_unit'];?> <?=$this_row['disturbance'];?> <?=$this_row['patch_type'];?> <?=$this_row['abundance'];?> <?=$this_row['valid'];?> <?=$this_row['valid_name'];?> <?=$this_row['valid_date'];?> <?=trim(preg_replace('/\s+/', ' ', $this_row['notes']));?>
The preg_replace allows you to use regular expressions in php to remove the newlines.
If this doesn't work you may need to alter your SQL query to remove the newline from the database query.
See this post
Pete

how to get specific rows page number in pagination

I have a question on MySQL paging. A user's record is displayed on a table with many other user's record. and the table is sorted/paged. Now I need to display the page that containing the user's row directly after the user login. How can I achieve this?
create table t_users (id int auto_increment primary key, username varchar(100)); insert t_users(username) values ('jim'),('bob'),('john'),('tim'),('tom'), ('mary'),('elise'),('karl'),('karla'),('bob'), ('jack'),('jacky'),('jon'),('tobias'),('peter');
I searched the google but not found answer so please help
There are two steps for this:
1. Determine the row's position in your sorted table.
Copied and tweaked from: https://stackoverflow.com/a/7057822/2391142
Use this SQL...
SELECT z.rank FROM (
SELECT id, #rownum := #rownum + 1 AS rank
FROM t_users, (SELECT #rownum := 0) r
ORDER BY id ASC
) as z WHERE id=1;
...replacing the ORDER BY id ASC with whatever your actual sort order is. And replacing the number 1 in WHERE id=1 with the provided number in that index.php?u=id url.
2. Determine the page number based on the row's position.
Use this PHP to determine the needed page number...
$rows_per_page = 50;
$user_row_position = [result you got from step 1];
$page = ceil($user_row_position / $rows_per_page);
...replacing the 50 with whatever your real rows-per-page limit is, and putting the real SQL result in $users_row_position.
And voila. You'll have the destination page number in the $page variable and hopefully you can take it from there.
EDIT
After further discussion in the comments, use this bit of PHP:
$page = 0;
$limit = 10;
// If a user ID is specified, then lookup the page number it's on.
if (isset($_GET['u'])) {
// Check the given ID is valid to avoid SQL injection risks.
if (is_numeric($_GET['u'])) {
// Lookup the user's position in the list.
$query = mysqli_fetch_array(mysqli_query($link, "SELECT z.rank FROM (SELECT id, #rownum := #rownum + 1 AS rank FROM sites, (SELECT #rownum := 0) r WHERE online='0') as z WHERE id=" . $_GET['u']));
$position = $query[0];
if (is_numeric($position)) {
// Convert the result to a number before doing math on it.
$position = (int) $position;
$page = ceil($position / $limit);
}
}
}
// If a page number is specified, and wasn't already set by looking a user, then lookup the real starting row.
if ($page == 0 && isset($_GET['page'])) {
// Check your given page number is valid too.
if (is_numeric($_GET['page'])) {
$page = (int) $_GET['page'];
}
}
// Notice that if anything fails in the above checks, we just pretend it never
// happened and keep using the default page and start number of 0.
// Determine the starting row based off the page number.
$start = ($page - 1) * $limit;
// Get the list of sites for the provided page only.
$query = mysqli_query($link, "SELECT * FROM sites WHERE online='0' LIMIT " . $start . ", " + $limit);
while ($row = mysqli_fetch_array($query)) {
// Stuff to render your rows goes here.
// You can use $row['fieldname'] to extract fields for this row.
}

zend framework count the number of rows in mysql query

I am new to zend framework
I want to calculate number of rows in my query
this is my code:
$nm = new Zend_Db_Table('emp');
$row = $nm->fetchRow($nm->select()->where('id= ?', $a));
yes you can try in this way to get total number of rows return by your sql query.
$select = $this->select();
$select->where('id= ?', $a);
$result=$this->fetchAll($select);
if(empty($result)){
return false;
}else{
echo "Total number of users : -> ".count($result->toArray());
}
let me know if i can help you more.
If you are testing for the existance of a record by primary key then you can use $nm->find($a) and check for any results.
if you expect the result set to be small then you can do
$nm->fetchAll($nm->select()->where("id = ?", $a);
If you expect the result set to get big and all you are really after is the count then authoring a query that asks for the count of a field would probably make the most sense to keep the query from eating up a lot of server memory:
$row = $nm->getAdapter()->fetchRow("select count(*) as num_rows".
" from ".$nm->info(Zend_Db_Table_Row_Abstract::NAME).
" where ".$nm->getAdapter()->quoteInto("id = ?", $a));
echo "Users ".$row["num_rows"];
In Zend Framework 1, you can use count() function at the last of query for getting total number of rows in the table.
For example:
$row = $nm->fetchAll($nm->select()->where('id = ?', $a ))->count();
NOTE: This will only return total number of rows in the table. Output is Integer.

Perl DBI insert and select

I want to copy a single record from a table, modify some of the fields in the record and insert into the same table. The table has 90 columns.
Thought of using insert..select in one statement but there are 90 columns and i need to tell the column name in the select query. How can i do it in a better way in perldbi. Pls provide me an example.
Fetch and cache the column names for your table using the NAME statement attribute
my $sth = $dbh->prepare('SELECT * FROM my_table where 1=0');
$sth->execute;
my $cols = $sth->{NAME};
then use $cols to construct your insert...select using some replacement function to inject your modifications in the select.
my %mods_for_column = ( 'foo' => 'foo+10', 'bar' => 'trim(bar)' );
my $inscols = join(',', #$cols);
my $selcols = join(',',
map { exists($mods_for_column($_)) ? $mods_for_column($_) : $_ } #$cols
);
my $sql = "insert into my_table ($inscols) select $selcols from my_table where mumble...";