Slow data insertion into mysql database - php

I am trying to make a csv upload page for an application that Im building. It needs to be able to upload thousands of rows of data in seconds each row including a first name, last name, and phone number. The data is being uploaded to a vm that is running ubuntu server. When I run the script to upload the data it takes almost 2 minutes to upload 1500 rows. The script is using PDO, I have also made a test script in python to see if It was a php issue and the python script is just as slow. I have made csv upload scripts in the past that are exactly the same that would upload thousands of rows in seconds. We have narrowed the issue down to the script as we have tested it on other vms that are hosted closer to us and the issue still persist. Is there an obvious issue with the script or PDO that could be slowing it down? Below is the code for the script.
<?php
$servername =[Redacted];
$username = [Redacted];
$password = [Redacted];
try {
$conn = new PDO("mysql:host=$servername;dbname=test", $username, $password);
// set the PDO error mode to exception
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "Connected successfully";
} catch(PDOException $e) {
echo "Connection failed: " . $e->getMessage();
}
echo print_r($_FILES);
$fileTmpPath = $_FILES['fileToUpload']['tmp_name'];
$fileName = $_FILES['fileToUpload']['name'];
$fileSize = $_FILES['fileToUpload']['size'];
$fileType = $_FILES['fileToUpload']['type'];
$CSVfp = fopen($fileTmpPath, "r");
$final_array = [];
while (($data = fgetcsv($CSVfp)) !== false) {
$recived = [];
foreach ($data as $i) {
array_push($recived, $i );
}
array_push($final_array, $recived);
}
echo print_r($final_array);
fclose($CSVfp);
$non_compliant_rows = [];
foreach ($final_array as $key => $row){
$fname = preg_replace('/[^A-Za-z0-9]/', "", $row[0]);
$lname = preg_replace('/[^A-Za-z0-9]/', "", $row[1]);
$mobileNumber = preg_replace( '/[^0-9]/i', '', $row[2]);
$sanatized_row = array($fname, $lname, $mobileNumber);
$recived[$key] = $sanatized_row;
if (strlen($mobileNumber) > 10 or strlen($mobileNumber) < 9){
array_push($non_compliant_rows, $final_array[$key]);
unset($final_array[$key]);
}
}
$final_array = array_values($final_array);
echo print_r($final_array);
foreach($final_array as $item){
try{
$stmt = $conn->prepare("INSERT INTO bulk_sms_list(fname, lname, pn, message, send) VALUES (?, ?, ?, 'EMPTY', 1) ;");
$stmt->execute($item);
}catch(PDOException $e){
echo $e;
}
}
echo "done";
The phone numbers column has a UNIQUE constraint to prevent us from having duplicate phone numbers. We have tried to use batch inserting but if one row doesn't comply with the constraints then all of the insertions fail.
below is the schema of the table:
+---------+------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| fname | text | NO | | NULL | |
| lname | text | NO | | NULL | |
| pn | text | NO | UNI | NULL | |
| message | text | YES | | NULL | |
| send | int | NO | | 1 | |
+---------+------+------+-----+---------+----------------
EDIT:
I have timed the clean up portion of the script at the request of #aynber. the time of the clean up was 0.24208784103394 Seconds. The time it took to do the sql portion is 108.2597219944 Seconds

The fastest solution should be to use LOAD DATA LOCAL INFILE. Since you answered in a comment that duplicate phone numbers should result in skipping a row, you can use the IGNORE option.
Load directly from the file, instead of processing it with PHP.
You can do some of your transformations in the LOAD DATA statement.
For example:
LOAD DATA INFILE ? IGNORE INTO TABLE bulk_sms_list
FIELDS TERMINATED BY ','
(#fname, #lname, #mobile)
SET fname = REGEXP_REPLACE(#fname, '[^A-Za-z0-9]', ''),
lname = REGEXP_REPLACE(#lname, '[^A-Za-z0-9]', ''),
pn = IF(LENGTH(#mobile) BETWEEN 9 AND 10, #mobile, NULL),
message = 'EMPTY',
send = 1;
Then follow the import with some cleanup to get rid of any rows with invalid phone numbers:
DELETE FROM bulk_sms_list WHERE pn IS NULL;
Read https://dev.mysql.com/doc/refman/8.0/en/load-data.html for more information.

Related

PHP Compare column values and edit database accordingly

I am a newbie to PHP and I am stuck at a certain point. I tried looking up a solution for it however, I didn't find exactly what I need.
My goal is to create a leaderboard, in which the values are displayed in descending order plus the rank and score are displayed. Furthermore, it should also display whether or not a tie is present.
The database should look like this:
+---------+------+----------------+-------+------+
| user_id | name | email | score | tied |
+---------+------+----------------+-------+------+
| 1 | SB | sb#gmail.com | 1 | 0 |
+---------+------+----------------+-------+------+
| 2 | AS | as#web.de | 2 | 0 |
+---------+------+----------------+-------+------+
| 3 | BR | br#yahoo.com | 5 | 1 |
+---------+------+----------------+-------+------+
| 4 | PJ | pj#gmail.com | 5 | 1 |
+---------+------+----------------+-------+------+
And the outputted table should look something like this:
+------+-------------+-------+------+
| rank | participant | score | tied |
+------+-------------+-------+------+
| 1 | BR | 5 | Yes |
+------+-------------+-------+------+
| 2 | PJ | 5 | Yes |
+------+-------------+-------+------+
| 3 | AS | 2 | No |
+------+-------------+-------+------+
| 4 | SB | 1 | No |
+------+-------------+-------+------+
I managed to display the rank, participant and the score in the right order. However, I can't bring the tied column to work in the way I want it to. It should change the value, whenever two rows (don't) have the same value.
The table is constructed by creating the <table> and the <thead> in usual html but the <tbody> is created by requiring a php file that creates the table content dynamically.
As one can see in the createTable code I tried to solve this problem by comparing the current row to the previous one. However, this approach only ended in me getting a syntax error. My thought on that would be that I cannot use a php variable in a SQL Query, moreover my knowledge doesn't exceed far enough to fix the problem myself. I didn't find a solution for that by researching as well.
My other concern with that approach would be that it doesn't check all values against all values. It only checks one to the previous one, so it doesn't compare the first one with the third one for example.
My question would be how I could accomplish the task with my approach or, if my approach was completely wrong, how I could come to a solution on another route.
index.php
<table class="table table-hover" id="test">
<thead>
<tr>
<th>Rank</th>
<th>Participant</th>
<th>Score</th>
<th>Tied</th>
</tr>
</thead>
<tbody>
<?php
require("./php/createTable.php");
?>
</tbody>
</table>
createTable.php
<?php
// Connection
$conn = new mysqli('localhost', 'root', '', 'ax');
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
// SQL Query
$sql = "SELECT * FROM names ORDER BY score DESC";
$result = $conn->query("$sql");
// Initalizing of variables
$count = 1;
$previous = '';
while($row = mysqli_fetch_array($result)) {
$current = $row['score'];
$index = $result['user_id']
if ($current == $previous) {
$update = "UPDATE names SET tied=0 WHERE user_id=$index";
$conn->query($update);
}
$previous = $current;
?>
<tr>
<td>
<?php
echo $count;
$count++;
?>
</td>
<td><?php echo $row['name'];?></td>
<td><?php echo $row['score'];?></td>
<td>
<?php
if ($row['tied'] == 0) {
echo 'No';
} else{
echo 'Yes';
}
?>
</td>
</tr>
<?php
}
?>
I think the problem is here
$index = $result['user_id'];
it should be
$index = $row['user_id'];
after updating tied you should retrieve it again from database
So I solved my question by myself, by coming up with a different approach.
First of all I deleted this part:
$current = $row['score'];
$index = $result['user_id']
if ($current == $previous) {
$update = "UPDATE names SET tied=0 WHERE user_id=$index";
$conn->query($update);
}
$previous = $current;
and the previous variable.
My new approach saves the whole table in a new array, gets the duplicate values with the array_count_values() method, proceeds to get the keys with the array_keys() method and updates the database via a SQL Query.
This is the code for the changed part:
// SQL Query
$sql = "SELECT * FROM names ORDER BY score DESC";
$result = $conn->query("$sql");
$query = "SELECT * FROM names ORDER BY score DESC";
$sol = $conn->query("$query");
// initalizing of variables
$count = 1;
$data = array();
// inputs table into an array
while($rows = mysqli_fetch_array($sol)) {
$data[$rows['user_id']] = $rows['score'];
}
// -- Tied Column Sort --
// counts duplicates
$cnt_array = array_count_values($data);
// sets true (1) or false (0) in helper-array ($dup)
$dup = array();
foreach($cnt_array as $key=>$val){
if($val == 1){
$dup[$key] = 0;
}
else{
$dup[$key] = 1;
}
}
// gets keys of duplicates (array_keys()) and updates database accordingly ($update query)
foreach($dup as $key => $val){
if ($val == 1) {
$temp = array_keys($data, $key);
foreach($temp as $k => $v){
$update = "UPDATE names SET tied=1 WHERE user_id=$v";
$conn->query($update);
}
} else{
$temp = array_keys($data, $k);
foreach($temp as $k => $v){
$update = "UPDATE names SET tied=0 WHERE user_id=$v";
$conn->query($update);
}
}
}
Thank you all for answering and helping me get to the solution.
instead of the update code you've got use something simular
$query = "select score, count(*) as c from names group by score having c > 1";
then you will have the scores which have a tie, update the records with these scores and your done. Make sure to set tie to 0 at first for all rows and then run this solution
UPDATE for an even faster solution sql based:
First reset the database:
$update = "UPDATE names SET tied=0";
$conn->query($update);
All records have a tied = 0 value now. Next update all the records which have a tie
$update = "update docs set tied = 1 where score IN (
select score from docs
group by score having count(*) > 1)";
$conn->query($update);
All records with a tie now have tied = 1 as we select all scores which have two or more records and update all the records with those scores.

Data truncuated for a column error with PHP and mySQL?

I have written the following code on PHP and i am getting the error:
Data truncated for column 'datetime_gmt' at row 1;
Here is the code:
$lines = new SplFileObject('/home/file.txt');
$x = 0;
while(!$lines->eof()) {
$lines->next();
if($x == 0){
$lines->next();
}
$row = explode(',',$lines);
for($i = 0; $i<4; $i++){
if(!isset($row[$i])){
$row[$i] = null;
}
}
$y = (float) $row[1];
$z = (float) $row[2];
$load_query = "INSERT IGNORE INTO new (datetime_gmt,field2,field3)
VALUES ('".$row[0]."','".$y."','".$z."');";
$x++;
}
$lines = null;
The column is of type 'datetime' and has '0000-00-00 00:00:00' as DEFAULT, and it is the PRI of the table. If you are wondering about the "x" variable, it's for skipping the first 2 lines.
EDIT 1
Here is a sample data:
2013-12-11 8:22:00, 1.462E+12, 3.33E+11
2013-12-12 4:10:00, 1.462E+12, 3.33E+11
2013-12-13 11:52:00, 1.462E+12, 3.33E+11
And here is the description of the table "new":
Field | Type | Null | Key | Default | Extra
datetime_gmt | datetime |No | PRI |0000-00-00 00:00:00 |
field2 | bigint(20)|YES | |NULL |
field3 | bigint(20)|YES | |NULL |
using:
SELECT sum(char_length(COLUMN_NAME))
FROM TABLE_NAME;
I get 19 as the size of the column.
Please run this query directly into your database: INSERT IGNORE INTO new (datetime_gmt,field2,field3) VALUES ('2013-12-11 8:22:00','1462000000000','333000000000');
If that does not add a row, remove the default on the datetime_gmt column and re-try.
Note: You have a syntax error with your code.
Change this:
$load_query = "INSERT IGNORE INTO new (datetime_gmt,field2,field3)
VALUES ('".$row[0]"','".$y."','".$z."');";
To this:
$load_query = "INSERT IGNORE INTO new (datetime_gmt,field2,field3)
VALUES ('".$row[0]."','".$y."','".$z."');";
If the aforementioned doesn't work, try to have just engine substitution in your SQL Modes:
set ##sql_mode='no_engine_substitution';
Then make sure that it shows NO_ENGINE_SUBSTITUTION by running the following:
select ##sql_mode;
Then attempt to run your code again. The set ##sql_mode might not be server wide and it may only work for your current session.

PHP PDO lastinsertid of previous function

I feel like I'm really overly complicating this whole scenario. Hopefully somebody can help.
I have a form which submits data to two tables (items and uploads). The form data goes to items, and the attachment to uploads. Basically I'd like both tables to have a corresponding itemId column.
My two functions create() and uploadFile() both work. However, I'm not sure how to use the the lastInsertId value of $crud->create() in my variable named $itemId - see comments in my code.
Reduced versions of my functions are below, including comments.
class.crud.php
class crud {
private $db;
function __construct($DB_con) {
$this->db = $DB_con;
}
public function create($inv, $ip, $make){
$stmt = $this->db->prepare("INSERT INTO items (inv,ip,make) VALUES (:inv,:ip,:make");
$stmt->bindparam(":inv", $inv);
$stmt->bindparam(":ip", $ip);
$stmt->bindparam(":make", $make);
$stmt->execute();
return true;
}
public function uploadFile($itemId, $inv, $file, $file_type, $file_size) {
$stmt = $this->db->prepare("INSERT INTO uploads (itemId,inv,file,type,size) VALUES (:itemId,:inv,:file,:file_type,:file_size)");
$stmt->bindParam(":itemId", $itemId); // inserts 777
$stmt->bindParam(":inv", $inv);
$stmt->bindparam(":file", $file);
$stmt->bindparam(":file_type", $file_type);
$stmt->bindparam(":file_size", $file_size);
$stmt->execute();
return true;
}
}
add-data.php
if (isset($_POST['btn-save'])) {
$itemId = '777'; //this successfully inserts 777 into the uploads.itemId teble, but i'd like to insert the lastInsertId value of $crud->create()
$inv = $_POST['inv'];
$ip = $_POST['ip'];
$make = $_POST['make'];
$file = rand(1000, 100000) . "-" . $_FILES['file']['name'];
$file_loc = $_FILES['file']['tmp_name'];
$file_size = $_FILES['file']['size'];
$file_type = $_FILES['file']['type'];
$folder = "uploaded_files/";
if ($crud->create($inv, $ip, $make)) {
echo 'success';
} else {
echo 'error';;
}
if (move_uploaded_file($file_loc, $folder . $file)) {
$crud->uploadFile($itemId, $inv, $file, $file_type, $file_size);
}
}
<form method='post' enctype="multipart/form-data">
<input type='text' name='inv'>
<input type='text' name='ip'>
<input type='text' name='make'>
<input type='file' name='file'>
<button type="submit" name="btn-save"></button>
</form>
The structure of both my tables are as follows;
items (itemId is primary, unique and auto-increment)
+--------+---------+-----------------+-------+
| itemId | inv | ip | make |
+--------+---------+-----------------+-------+
| 1 | 1293876 | 123.123.123.123 | Dell |
+--------+---------+-----------------+-------+
| 2 | 4563456 | 234.234.234.234 | Dell |
+--------+---------+-----------------+-------+
| 3 | 7867657 | 345.345.345.345 | Apple |
+--------+---------+-----------------+-------+
items (upload_id is primary, unique and auto-increment)
+-----------+--------+-----+----------+------------+------+
| upload_id | itemId | inv | file | type | size |
+-----------+--------+-----+----------+------------+------+
| 56 | 777 | 123 | test.txt | text/plain | 266 |
+-----------+--------+-----+----------+------------+------+
| 57 | 777 | 123 | test.txt | text/plain | 266 |
+-----------+--------+-----+----------+------------+------+
| 58 | 777 | 123 | test.txt | text/plain | 266 |
+-----------+--------+-----+----------+------------+------+
Please forgive the messy code. I'm just trying to get the logic correct and then I can work on it.
Any advice is appreciated.
So with help from #Maximus2012 I was able to solve this.
I changed my create() function to return lastInsertId() in place of just true or false. Then, I assigned the value returned by the create() function to a variable rather than using a static value.
So my working code now looks like this;
public function create($inv, $ip, $make){
$stmt = $this->db->prepare("INSERT INTO items (inv,ip,make) VALUES (:inv,:ip,:make");
$stmt->bindparam(":inv", $inv);
$stmt->bindparam(":ip", $ip);
$stmt->bindparam(":make", $make);
$stmt->execute();
return $this->db->lastInsertId();
}
Then in my add-data.php page I simply changed one vcariable to the following;
$itemId = $crud->create($inv, $ip, $make);
Solved.

mysqli_fetch_assoc crash but mysqli_fetch_array(MYSQLI_NUM) works

When I try to use the fetch_assoc or fetch_array(MYSQLI_ASSOC), the program just crashes and doesn't display any error information(I do have changed error_reporting to E_ALL).But it works fine when using fetch_array(MYSQLI_NUM) or fetch_row().
The weird thing is I can run my program on Wamp. But I cannot on the Apache+PHP+MYSQL environment installed manually.
so is this a PHP configuration problem or a MYSQL problem ?
$studentId = $this->mydblink->real_escape_string($studentId);
$result = $this->mydblink->query("SELECT * FROM student WHERE id = '$studentId'");
if($result->num_rows <= 0){
$result->free();
return null;
}
else{
$returnValue = array();
while($row = $result->fetch_assoc()){
array_push($returnValue,$row);
}
$result->free();
return $returnValue;
}
fetch_array() makes associative array in which there is a key and value.
fetch_assoc() only has value
You can also use fetch_object () that creates objects of your table name.
| id | category | name |
----------------------------------
| 1 | 2, 5, 7, 28 | Will |
----------------------------------
| 2 | 2, 9, 15, 18 | Drew |
EXAMPLES:
$sql=mysqli_query($connect, "SELECT id, category, name FROM users");
$data=mysqli_fetch_array($sql);
// 0=>id, 1=>category, 2=>name
$data[1]; // is same like $data['category'];
$data['category'];
$data=mysqli_fetch_assoc($sql);
$data['name'];
$data=mysqli_fetch_object($sql);
$data->name;
$data->category;
$data->id;

php get mysql result not as array, but as text

I want to get text result like:
+----+-------+----------------------------------+----+
| id | login | pwd | su |
+----+-------+----------------------------------+----+
| 1 | root | c4ca4238a0b923820dcc509a6f75849b | 1 |
+----+-------+----------------------------------+----+
1 row in set (0.00 sec)
in PHP (string).
Example in php:
$query = mysql_query("SELECT * FROM users");
for (; $row = mysql_fetch_assoc($query); $data[] = $row);
I get array of arrays ($data):
$data =
0 => (id=>1, login=>root..)
But i want to get this as string:
+----+-------+----------------------------------+----+
| id | login | pwd | su |
+----+-------+----------------------------------+----+
| 1 | root | c4ca4238a0b923820dcc509a6f75849b | 1 |
+----+-------+----------------------------------+----+
1 row in set (0.00 sec)
By the way for query "insert into users set login = 'sp', pwd = 1, su = 0", this string must be "Query OK, 1 row affected, 2 warnings (0.18 sec)".
Like sql terminal though php!
Either invoke mysql client as an external command, or build the ASCII drawing table yourself. You can't get it purely by using PHP's MySQL library functions. Off the top of my head,
$result = `echo 'SELECT * FROM users' | mysql --user=username --password=password dbname`;
(ugly, slow, insecure, don't recommend); or simply get it as the array, iterate, and decorate with plusses imitating what mysql does (recommended). It's really easy.
However, I have no clue why you'd want that, unless you're using your PHP program as a command-line tool.
Collect the database result into an array of arrays(your $data variable is correct)
echo ascii_table($data);
I stole the following from someone named stereofrog
function ascii_table($data) {
$keys = array_keys(end($data));
# calculate optimal width
$wid = array_map('strlen', $keys);
foreach($data as $row) {
foreach(array_values($row) as $k => $v)
$wid[$k] = max($wid[$k], strlen($v));
}
# build format and separator strings
foreach($wid as $k => $v) {
$fmt[$k] = "%-{$v}s";
$sep[$k] = str_repeat('-', $v);
}
$fmt = '| ' . implode(' | ', $fmt) . ' |';
$sep = '+-' . implode('-+-', $sep) . '-+';
# create header
$buf = array($sep, vsprintf($fmt, $keys), $sep);
# print data
foreach($data as $row) {
$buf[] = vsprintf($fmt, $row);
$buf[] = $sep;
}
# finis
return implode("\n", $buf);
}
You can please query the following sql:
SELECT GROUP_CONCAT(id, ' | ', login, ' | ', pwd, ' | ', su)
AS 'User Object' FROM users group by id
Result Would Be:

Categories