Memory/Time Efficient Database Entry - php

I have a foreach loop that iterates through an array.
In each instance, I organise the array into a query string and use MySQLi to add it to the database.
function storeProperties($data, $db) {
foreach ($data['property'] as $property) {
$query_string = "INSERT INTO table VALUES(..., ..., ...,)"
$db->query($query_string);
echo $db->error;
}
}
Is there a better way I should be doing this?
Obviously, this method uses n database queries one after another so this is memory intensive and time intensive.
Is there a better way to do this?
Should I be concatenating each query into a single string and running it all outside the for loop?

The following method is from my PDO workhorse, used for bulk insertions. It creates a single INSERT statement with multiple VALUES entries.
Use it as
$db->bulkinsert(
'tbl_my_tablename',
array('fieldname_1','fieldname_2', 'fieldname_3'),
array(
/* rec1 */ array('r1f1', 'r1f2', 'r1f3'),
/* rec2 */ array('r2f1', 'r2f2', 'r2f3'),
/* rec3 */ array('r3f1', 'r3f2', 'r3f3')
));
Please note that the method is an snip from a complex class definition, some methods used here are not contained in the code snippet, especially $this->connect() (connects to PDO),$this->begin() (starts transaction), $this->commit()and $this->rollback(), and the static Log class for Logging similar to Apache Commons ;-)
But I'm sure this is what you might need.
/**
* Performs fast bulk insertion
* Parameters:
* $tablename
* $datafields - non-assiciative array of fieldnames
* or propertynames if $data is an array of objects
* $data - array of either non-associative arrays (in the correct order)
* or array of objects with property names matching the $datafields array
*/
const MAX_BULK_DATA = 3000;
public function bulkinsert($tablename, $datafields, &$data) {
$result = 0;
try {
try {
$this->connect();
$datacount = count($data);
// loop until all data has been processed
$start = 0;
$lastbinds = 0;
$this->begin();
while ($start < $datacount) {
$ins = array();
$bindscount = min(self::MAX_BULK_DATA, $datacount - $start);
if ($bindscount != $lastbinds) {
// prepare the binds
$binds = substr(str_repeat(',?', count($datafields)), 1);
$binds = substr(str_repeat(",($binds)", $bindscount), 1);
$lastbinds = $bindscount;
}
for ($go = $start, $last = $start + $bindscount; $go < $last; $go++) {
if (is_object($data[$go])) {
try {
foreach($datafields as $propname) {
$rfl = new ReflectionProperty($data[$go], $propname);
$rfl->setAccessible(true);
$ins[] = $rfl->getValue($data[$go]);
}
}
catch(ReflectionException $e) {
throw new InvalidArgumentException('PDOCONNECT_ERR_SQL_UNKNOWN_PROPERTY', 0, $e);
}
}
else {
foreach($data[$go] as $value) {
$ins[] = $value;
}
}
}
$sql = sprintf('INSERT INTO %s (%s) VALUES %s', $tablename, join(',',$datafields), $binds);
Log::trace($sql);
$stmt = $this->pdo->prepare($sql);
$stmt->execute($ins);
$start = $last;
$result += $bindscount;
}
$this->commit();
}
catch(PDOException $e) {
// do something with the exception if necessary
throw $e;
}
}
catch(Exception $e) {
$this->rollback();
throw $e;
}
return $result;
}
}

Related

PHP 7.4.18 update: Mysqlnd -- Calling stmt_store_result after fetch now throws an error

We have a bunch of websites using a shared MySQLi class.
PHP 7.4 was working with these and no errors were seen to be generated.
Now, PHP Bug #80837 has been fixed with release of PHP 7.4.18
The problem is that this fix means all our websites are throwing this fatal error and I can't see how to fix the error itself.
The Exception:
[06-May-2021 08:04:19 UTC] PHP Fatal error: Uncaught mysqli_sql_exception: Commands out of sync; you can't run this command
now in /usr/local/lib/php/databaseClass.php:169 Stack trace:
#0 /usr/local/lib/php/databaseClass.php(169): mysqli_stmt->store_result()
#1 /usr/local/lib/php/databaseClass.php(897): Database->fetchResult(Object(mysqli_stmt), true)
#2 /home/account/public_html/.../file.php(45): DatabaseObject->getSelect('SELECT (bad_log...', Array, true)
#3 /home/katemawdsley/public_html/start.php(10): include('/home/account...')
#4 {main} thrown in databaseClass.php on line 169
The Database Class 'fetchResult' method:
/***
* For use with MySQLi->dbiLink Object.
* Returns the result as an array of KEY=>VALUE pairs from the Database.
* #param $result mysqli_result object.
* #return array|mixed
***/
private function fetchResult($result)
{
if ($result instanceof \mysqli_stmt) {
$result->store_result(); // This is line 169
$variables = [];
$data = [];
$meta = $result->result_metadata();
//error_log(print_r(debug_backtrace(),true));
while ($field = $meta->fetch_field()) {
$variables[] = &$data[$field->name]; // pass by reference
}
\call_user_func_array([$result, 'bind_result'], $variables);
$i = 0;
while ($result->fetch()) {
$resultsArray[$i] = [];
foreach ($data as $k => $v) {
$resultsArray[$i][$k] = $v;
if ($this->numericIndexes) {
$resultsArray[$i][] = $v;
}
}
unset($k, $v);
$i++;
}
} elseif ($result instanceof \mysqli_result) {
$rowNumber = 0;
while ($row = $result->fetch_assoc()) {
$resultsArray[$rowNumber] = $row;
if ($this->numericIndexes) {
foreach ($row as $numerical) {
$resultsArray[$rowNumber][] = $numerical;
}
unset($numerical);
}
$rowNumber++;
}
$i = 0;
unset($row, $rowNumber);
}
return $resultsArray;
}
The Database Class 'fetchResult' method called from a 'select' method:
/***
* Function for retrieving SQL query data from the Database
* #param $sql string the SQL select query
* #param null $data string|array the data to check in the query with
* #return array|bool|mixed
***/
public function select($sql, $data = null){
/***
* Now prepare the SQL
***/
try {
$query = $this->databaseObject->prepare($sql);
/****
* $query is prepared here....
***/
...
$query->execute();
}
catch (\mysqli_sql_exception $ex) {
$this->exceptionalError($ex);
return false;
}
catch (\Exception $ex) {
$this->exceptionalError($ex);
return false;
}
if (\mb_strtoupper($reduce, "UTF-8") !== "NORETURN") {
if ($query->field_count > 0 ) {
$query->store_result();
}
$this->rowsFound[] = $query->num_rows;
$output = $this->fetchResult($query); // Call to fetch result method
if ($query->field_count > 0) {
$query->free_result();
}
$query->close();
unset($query);
return $output;
}
/***
* NORETURN value so no result to return.
***/
$successValue = $this->rowsAffected[] = $query->affected_rows;
$query->close();
unset($query);
return $successValue;
}
What is the correct work around for how to construct this fetchResult method in light of the update?
(As you can tell we were completely unaware of this previous issue and it didn't appear on any error logs so was a surprise)
You are calling store_result() twice. You can't do that. Once the results have been buffered in PHP, the connection line is free - there are no more results pending to be fetched. Calling store_result() the second time will throw "Out of sync" error.
You call it first here:
if (\mb_strtoupper($reduce, "UTF-8") !== "NORETURN") {
if ($query->field_count > 0 ) {
$query->store_result();
}
and then here
private function fetchResult($result)
{
if ($result instanceof \mysqli_stmt) {
$result->store_result(); // This is line 169
Remove one usage and you should be fine.
I would recommend working with mysqli_result instead. When you call get_result it will buffer the results and give you a familiar object that you can work with. This could simplify your abstraction class significantly.
Here is how I would refactor this class:
public function select($sql, $data = null)
{
/***
* Now prepare the SQL
***/
try {
$query = $this->databaseObject->prepare($sql);
/****
* $query is prepared here....
***/
$query->execute();
} catch (\mysqli_sql_exception $ex) {
$this->exceptionalError($ex);
return false;
} catch (\Exception $ex) {
$this->exceptionalError($ex);
return false;
}
$result = $query->get_result();
if (\mb_strtoupper($reduce, "UTF-8") !== "NORETURN") {
$this->rowsFound[] = $result->num_rows;
if ($result) {
return $this->fetchResult($result); // Call to fetch result method
}
}
/***
* NORETURN value so no result to return.
***/
$successValue = $this->rowsAffected[] = $query->affected_rows;
return $successValue;
}
private function fetchResult(mysqli_result $result)
{
return $result->fetch_all($this->numericIndexes ? MYSQLI_BOTH : MYSQLI_ASSOC);
}

Array to string conversion notice in PHP

I am trying to display records using PDO LIKE query, but I am getting this error message can I know how to solve this.
This is my code:
$rs = new JSONRecordSet();
$searchbooksSQL = "SELECT Title FROM l_stock WHERE Title LIKE ?";
$params = array("%$term%");
echo $rs->getRecordSet($searchbooksSQL, $params);
This is the getRecordSet code:
class R_RecordSet {
function getRecordSet($sql, $params = null) {
if (is_array($params)) {
$this->stmt = $this->db->prepare($sql);
// execute the statement passing in the named placeholder and the value it'll have
$this->stmt->execute($params);
} else {
$this->stmt = $this->db->query($sql);
}
return $this->stmt;
}
}
class JSONRecordSet extends R_RecordSet {
function getRecordSet($sql, $elementName = "ResultSet", $params = null) {
$stmt = parent::getRecordSet($sql, $params);
$recordSet = $stmt->fetchAll(PDO::FETCH_ASSOC);
$nRecords = count($recordSet);
if ($nRecords == 0) {
$status = 'error';
$message = json_encode(array("text" => "No records found"));
$result = '[]';
} else {
$status = 'ok';
$message = json_encode(array("text" => ""));
$result = json_encode($recordSet);
}
return "{\"status\": \"$status\", \"message\":$message, \"$elementName\" :{\"RowCount\": $nRecords ,\"Result\": $result}}";
}
}
The error message i am getting is "Notice: Array to string conversion"
getRecordSet() is defined as:
function getRecordSet($sql, $elementName = "ResultSet", $params = null) {
however, you are calling it as:
echo $rs->getRecordSet($searchbooksSQL, $params);
You will need to modify your code to pass in an appropriate $elementName parameter. (The default is probably reasonable.)
echo $rs->getRecordSet($searchbooksSQL, 'ResultSet', $params);
Additionally, you should probably use json_encode() to generate the final result from JSONRecordSet::getRecordSet(), rather than building it up with string concatenation. It will make the code easier to read and understand.
Also, your two implementations of getRecordSet() are incompatible with each other, according to the Liskov Substitution Principle due to the change in the semantics of the input parameters, and is likely what led you to the parameter mismatch at your call site in the first place. You probably want to re-order JSONRecordSet::getRecordSet() parameters to:
function getRecordSet($sql, $params = null, $elementName = 'ResultSet') {

Issues with returning Array from Connection Class

I am having an issue returning the array.
This is part of my connection class.
public function CreateDataSet($IncomingSql) {
$this->stmt = sqlsrv_query($this->conn, $IncomingSql);
if ($this->stmt) {
$this->Rows = sqlsrv_has_rows($this->stmt);
if ($this->Rows) {
while($this->Row = sqlsrv_fetch_array($this->stmt, SQLSRV_FETCH_NUMERIC))
{
$this->MyArray[] = $this->Row;
}
}
return $this->MyArray;
//$this->Result = $this->Row;
//return $this->Result;
}
}
And this is how I am calling it on my page...
$conn3 = new ConnectionClass;
$MyDataSet = $conn3->CreateDataSet('Select top (100) city, state, postalcode from store');
echo $MyDataSet[0];
I am using the echo just as a test.
The $MYDataSet is echoing nothing. I can see the $MyArray fill up with all 100 records when I debug, but it does not return it..

insert array element into database

I have this function : it's work correctly,
function ms_get_did_detail($id) {
global $link;
$q2="select Dest,Priority from destpr where Id='$id'";
if($res2=mssql_query($q2)) {
while($row2[]=mssql_fetch_array($res2,MSSQL_ASSOC)) {
return $row2;
}
return 0;
}
return 0;
}
I want insert every element (every Dest & Priority) into MYSQL
if($info=ms_get_did_detail($value)) {
print_r($info);
$destination = $info['Dest'];
$priority = $info['Priority'];
my_did_destination ($priority , $dest , $active , $did_voip , $cc_id);
}
It returns array like this :
[0]=> Array (
[Dest] => 100
[Priority] => 1
)
[1]=> Array (
[Dest] => 200
[Priority] => 3
)
[2] => (
)
also , I have this function to insert value in database :
function my_did_destination($priority="",$destination="") {
global $link_voip;
$sql="INSERT INTO cc_did_destination (destination,priority)
VALUES ('$destination','$priority')";
$retval = mysql_query( $sql , $link_voip);
if(! $retval ) {
die('Could not enter data: ' . mysql_error());
}
}
but It's insert empty value within
You are inserting all rows with an ID of 0, so, if a row with id=0 already exists, it will fail and will not be inserted.
Maybe the easiest solution would be to make yout ID column autoincrement with an SQL statement like:
ALTER TABLE cc_did_destination
MODIFY COLUMN id INT auto_increment;
And then change your INSERT statement for:
$sql="INSERT INTO cc_did_destination (destination,priority)
VALUES ('$destination','$priority')";
Your $info is array of rows, it has numeric keys, not 'Dest'.
You should add index, like $dest = $info[0]['Dest'].
if($info=ms_get_did_detail($value))
{
print_r($info);
$dest = $info[0]['Dest'];
$priority = $info[0]['Priority'];
my_did_destination ($priority , $dest , $active , $did_voip , $cc_id);
}
Or you can iterate through $info with a loop:
if($info=ms_get_did_detail($value))
{
foreach($info as $row) {
$dest = $row['Dest'];
$priority = $row['Priority'];
my_did_destination ($priority , $dest);
}
}
also, remove id from your insert statement
your array is:
[0]=> Array (
[Dest] => 100
[Priority] => 1
)
[1]=> Array (
[Dest] => 200
[Priority] => 3
)
[2] => (
)
so it is a multidimensional array. if you need to insert all those entries, you shouldn't run multiple queries for the same thing. just use mysql batch insert syntax. (e.g. INSERT INTO tbl (col1,col2,col3) VALUES(a,b,c),(d,e,f),(g,h,i))
build the query string for insert.
foreach($a as $i => $v)
{
$b[] = '("'.$v['Dest'].'","'.$v['Priority'].'")';
}
$c = implode(',', $b);
$sql = "INSERT INTO cc_did_destination (destination,priority)
VALUES ".$c;
then run the query
N.B.
Please, don't use mysql_* functions in new code. They are no longer maintained and are officially deprecated. See the red box? Learn about prepared statements instead, and use PDO or MySQLi - this article will help you decide which. If you choose PDO, here is a good tutorial.
There are a couple of issues here.
Firstly your first function returns an array of arrays. Ie, it returns an array with subscript 0 for the first row (it only ever returns one rows details), which is an array containing that rows details.
You assign this to the $info variable, so it contains:-
[0]=> Array (
[Dest] => 100
[Priority] => 1
)
You then assign $info['Dest'] to $destination and $info['Priority'] to $priority. However neither of these exist. You would need $info[0]['Dest'] and $info[0]['Priority'].
2nd issue is that you are trying to assign a specific value to the auto increment id field. Just leave it out of the insert, or give it a value of null.
Quick rewrite and I would suggest you need something like this:-
<?php
if($info=ms_get_did_detail($value))
{
print_r($info);
foreach($info AS $info_row)
{
$destination = $info_row['Dest'];
$priority = $info_row['Priority'];
my_did_destination ($priority , $dest , $active , $did_voip , $cc_id);
}
}
function ms_get_did_detail($id)
{
global $link;
$q2="select Dest,Priority from destpr where Id='$id'";
if($res2=mssql_query($q2))
{
if ($row2[]=mssql_fetch_array($res2,MSSQL_ASSOC))
{
while ($row2[]=mssql_fetch_array($res2,MSSQL_ASSOC))
{
}
return $row2;
}
else
{
return 0;
}
}
return 0;
}
function my_did_destination($priority="",$destination="")
{
global $link_voip;
$priority = mysql_real_escape_string($priority);
$destination = mysql_real_escape_string($destination);
$sql="INSERT INTO cc_did_destination (id,destination,priority) VALUES (NULL,'$destination','$priority')";
$retval = mysql_query( $sql , $link_voip);
if(! $retval )
{
die('Could not enter data: ' . mysql_error());
}
}
EDIT
If you want to avoid multiple inserts unnecessarily then it might be easier to use an object. This way you can do the inserts easily when there are enough batched up (I normally do 255 at a time).
Something like this, although you probably should use mysqli_*
<?php
if($info=ms_get_did_detail($value))
{
print_r($info);
$insert_object = new insert_details($link_voip);
foreach($info AS $info_row)
{
$insert_object->set_row($info_row['Priority'], $info_row['Dest']);
}
unset($insert_object);
}
function ms_get_did_detail($id)
{
global $link;
$q2="select Dest,Priority from destpr where Id='$id'";
if($res2=mssql_query($q2))
{
if ($row2[]=mssql_fetch_array($res2,MSSQL_ASSOC))
{
while ($row2[]=mssql_fetch_array($res2,MSSQL_ASSOC))
{
}
return $row2;
}
else
{
return 0;
}
}
return 0;
}
class insert_details()
{
private $db;
private $insert_row = array();
public function __CONSTRUCT($db)
{
$this->db = $db;
}
public function __DESTRUCT()
{
$this->do_insert();
}
public function set_row($priority="",$destination="")
{
$priority = mysql_real_escape_string($priority, $this->db);
$destination = mysql_real_escape_string($destination, $this->db);
$this->insert_row[] = "(NULL,'$destination','$priority')";
if (count($this->insert_row) > 255)
{
$this->do_insert();
}
}
private function do_insert()
{
$sql="INSERT INTO cc_did_destination (id,destination,priority) VALUES ".implode(',', $this->insert_row);
$retval = mysql_query($sql, $this->db);
if(! $retval )
{
die('Could not enter data: ' . mysql_error());
}
$this->insert_row = array();
}
}
Quick rough mysqli_* equivalent, assuming that $link_voip is a mysqli connection. Note that prepared statements with bound parameters are an option (and it makes it harder to forget to escape variables), but it can become a bit messy when you are doing multiple inserts like this.
<?php
if($info=ms_get_did_detail($value))
{
print_r($info);
$insert_object = new insert_details($link_voip);
foreach($info AS $info_row)
{
$insert_object->set_row($info_row['Priority'], $info_row['Dest']);
}
unset($insert_object);
}
function ms_get_did_detail($id)
{
global $link;
$q2="select Dest,Priority from destpr where Id='$id'";
if($res2=mssql_query($q2))
{
if ($row2[]=mssql_fetch_array($res2, MSSQL_ASSOC))
{
while ($row2[]=mssql_fetch_array($res2, MSSQL_ASSOC))
{
}
return $row2;
}
else
{
return 0;
}
}
return 0;
}
class insert_details()
{
private $db;
private $insert_row = array();
public function __CONSTRUCT($db)
{
$this->db = $db;
}
public function __DESTRUCT()
{
$this->do_insert();
}
public function set_row($priority="",$destination="")
{
$priority = mysqli_real_escape_string($this->db, $priority);
$destination = mysqli_real_escape_string($this->db, $destination);
$this->insert_row[] = "(NULL,'$destination','$priority')";
if (count($this->insert_row) > 255)
{
$this->do_insert();
}
}
private function do_insert()
{
$sql="INSERT INTO cc_did_destination (id,destination,priority) VALUES ".implode(',', $this->insert_row);
$retval = mysqli_query($this->db, $sql);
if(! $retval )
{
die('Could not enter data: ' . mysqli_sqlstate($this->db));
}
$this->insert_row = array();
}
}

How to change the number of specifiers with count of providing array in php

My code is given bellow
Controller code:
class statController extends baseController {
public function index() {
$this->registry->template->projectsWise = $this->selectProjectWise();
$this->registry->template->show("stat");
}
private static function selectStat($query, $values) {
$statMapper = new statMapper();
$select = new common();
$select->setItems($query);
$select->setValues($values);
return $statMapper->select($select);
}
private function selectProjectWise() {
$date = date("Y-m-d");
$values = array($date, $date);
return self::selectStat("project_wise", $values);
}
private function selectLocationWise(){
$date = date("Y-m-d");
$values = array($date, $date, 1);
return self::selectStat("location_wise", $values);
}
Mapper code:
class statMapper extends baseMapper {
private $query = array(
"project_wise" => "SELECT p.proj_Name, p.proj_Type, p.proj_Category, COUNT(ra.emp_Id) as allocation FROM project AS p, resource_assignment AS ra WHERE p.proj_Start_Date <= '%s' AND p.proj_End_Date >= '%s' AND p.proj_Status = 1 AND ra.proj_Id = p.proj_Id GROUP BY p.proj_Name ",
"location_wise" => "SELECT e.user_Location, p.proj_Name, p.proj_Type, p.proj_Category, COUNT(ra.emp_Id) as allocation FROM project AS p, resource_assignment AS ra, employee as e WHERE p.proj_Start_Date <= '%s' AND p.proj_End_Date >= '%s' AND p.proj_Status = %d AND ra.proj_Id = p.proj_Id GROUP BY p.proj_Name "
);
public function select($select) {
$connect = parent::connect();
$query = sprintf($this->query[$select->getItems()], $select->getValues()[0], $select->getValues()[1]);
try {
$result = $connect->query($query);
if ($result) {
$table = array();
while ($row = $result->fetch_object()) {
$table[] = $row;
}
return $table;
parent::disconnect();
} else {
throw (new Exception($connect->error . "<br/>" . $query . "<br/>"));
}
} catch (Exception $exp) {
require_once 'views/error.php';
exit();
}
}
}
I have added 3 specifiers ($query->"location_wise") In statController->selectLocationWise() function.
When I use it how do I add $select->getValues()[2] value in statMapper->select(), instead of add it manually.
You can use http://www.php.net/manual/ru/function.vsprintf.php instead of sprintf to pass whole array as argument.
But anyway your code looks bad. From the outside view of StatMapper there is no way to identify neither types of arguments, nor their count. Code will be hard to be maintained.
I advise to use Data Access Objects pattern or learn framework.

Categories