I am building a store. When a user decides to make a purchase I need to store it in the database, but since the purchase might be of more than 1 item I'm wondering which way is the best to insert multiple rows, here are my 3 options.
I use a database object that combines PDO with PDOStatement, which is why I don't define $stmt
anywhere.
The input is:
$input = array(
'user_id' => 15,
'food_id' => 2,
'quantity' => 15
);
The table I'm inserting into:
CREATE TABLE `users_foods` (
`user_id` int(10) unsigned NOT NULL,
`food_id` int(10) unsigned NOT NULL,
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Option one
$insert_data = array();
while($input['quantity']){
$insert_data[] = array($input['user_id'], $input['food_id']);
$input['quantity']--;
}
$db->insert('users_foods', array('user_id', 'food_id'), $insert_data);
Respectively used functions are
public function insert($table, array $columns, array $values){
$valArr = array();
$data = array();
$id = 0;
if(!empty($values)){
if(is_array($values[0])){
foreach($values as $k => $v){
foreach($v as $key => $val){
$data[':i'.$id] = $val;
$v[$key] = ':i'.$id;
$id++;
}
$valArr[] = '('.join(',', $v).')';
}
}else{
foreach($values as $key => $val){
$data[':i'.$id] = $val;
$v[$key] = ':i'.$id;
$id++;
}
$valArr[] = '('.join(',', $v).')';
}
}
return $this->prepare('INSERT INTO '.$table.' ('.join(',', $columns).') VALUES '.join(',', $valArr))->execute($data);
}
private function parseWhereClause(array $where, $iteration = 0, $id = 0, $sd = null){
$sql = array();
$data = array();
if(count($where) == 3 && !is_array(reset($where))){
if(is_array($where[2])){
foreach($where[2] as $expr){
$sql[] = ':w'.$iteration.$id;
$data[':w'.$iteration.$id++] = $expr;
}
switch(strtolower($where[1])){
case 'in': $sql = $where[0] . ' ' . $where[1] . ' ('.join(',', $sql).')'; break;
case 'between': $sql = $where[0] . ' ' . $where[1] . ' '.join(' AND ', $sql); break;
}
}else{
$sql = $where[0] . ' ' . $where[1] . ' :w'.$iteration.$id;
$data[':w'.$iteration.$id] = $where[2];
}
}else{
if(!empty($where)){
$id = 0;
foreach($where as $c => $expressions){
$recur = $this->parseWhereClause($expressions, ++$iteration, $id++, $c);
$sql[] = $recur['sql'];
$data += $recur['data'];
}
$sql = '('.join(' '.$sd.' ', $sql).')';
}else{
$sql = '1';
}
}
return array(
'sql' => $sql,
'data' => $data
);
}
This method will only run one large query.
Option two
$db->prepare('INSERT INTO users_foods (user_id, food_id) VALUES (?, ?)');
while($input['quantity']){
$db->execute(array($input['user_id'], $input['food_id']));
$input['quantity']--;
}
not quite sure how this prepare-execute thing works under the hood though, but I believe that this will run 15 queries (because quantity is 15 in the example).
Option three
MySQL
CREATE PROCEDURE `buy_food`(IN `user_id` INT UNSIGNED, IN `food_id` INT UNSIGNED, IN `quantity` INT UNSIGNED)
BEGIN
WHILE quantity > 0 DO
INSERT INTO users_foods (`user_id`, `food_id`) VALUES (user_id, food_id);
SET quantity = quantity - 1;
END WHILE;
END
PHP
$db->prepare('CALL buy_food(?, ?, ?)')->execute(array(
$input['user_id'],
$input['food_id'],
$input['quantity']
));
Which one do you think is the best way to go and why?
$db
is an instance of a wrapper aroundPDO
: Get rid of it: You're calling$db->prepare
, which suggests it's an instance ofPDO
, but you're also calling$db->execute
, which is a method ofPDOStatement
. That's just awful. Aslo: aPDOStatement
has a methodcloseCursor
, meant to be used in case of multipleINSERT
queries \$\endgroup\$$db
is in the PS. Why would you suggest that is "awful". I think it is pretty handy, saving me a lot of lines. I can always callgetStmt()
and return thePDOStatement
object when I need it. \$\endgroup\$PDO
and fetching results throughPDOStatement
) violates the SRP, it also makes it hard to re-use a statement safely (I could be fetching results from X, processing them, and inserting them in Y, requiring at least 2 statements). You can never reliably callcloseCursor
or be sure the statement is not referenced anywhere. \$\endgroup\$