Creating an SQL Query based on data from an Array - php

I'm trying to find a way to simplify an existing function which communicated with our database. The function currently has several parameters (upwards of 15), and everytime a record is added or updated, all the parameters are required.
I have the following PHP Function (simplified):
function addSomethingToDB($var1, $var2, $var3, $var4...) {
# Do SQL Injection checks
...
$query = 'INSERT INTO `table` (`var1`,`var2`,`var3`,`var4`) VALUES ($var1, $var2, $var3, $var4)';
# OR
$stmt = $db->prepare('INSERT INTO `table` (`var1`,`var2`,`var3`,`var4`) VALUES (?,?,?,?)');
$stmt->bind_param('ssss', $var1, $var2, $var3, $var4);
}
The above code obviously gets pretty messy if you have more than a few variables. And it's difficult to work with if not all variables are required. Because of this I attempted a second scenario where I either had one main/required parameter followed by an array or I just had an array as the parameter.
function addSomethingToDB($var1, array $attributes) {}
The goal here was to allow the array to have a more flexible approach in case the SQL query either needs to be extended in the future, or to build the SQL query based on optional values.
For example:
If Var2 and Var4 are not provided, the array would look like:
{
'var1': 'Var1_Value',
'var3': 'Var3_Value'
}
and the SQL would be:
$query = 'INSERT INTO `table` (`var1`,`var3`) VALUES ($var1, $var3);
As you can see, in the above scenario, the query was adapted for only the necessary values.
What I want to try and achieve is to build the SQL query based on the values provided. The first was I assume would be to have an IF ELSE statement or a SWITCH. Which gives me something weird like the following:
function getlogs($type, $id='') {
$types = array('client_log', 'system_log', 'user_log', 'api_log', 'project_log', 'lead_log');
if (in_array($type, $types)) {
if ('client_log' == $type) {
if (!empty($id)) {
$query = 'SELECT * FROM `logs` WHERE `client_id` = ' . $id . ' AND `type` = "client_log"';
} else {
$query = 'SELECT * FROM `logs` WHERE `type` = "client_log"';
}
} elseif ('project_log' == $type) {
if (!empty($id)) {
$query = 'SELECT * FROM `logs` WHERE `project_id` = ' . $id . ' AND `type` = "project_log"';
} else {
$query = 'SELECT * FROM `logs` WHERE `type` = "project_log"';
}
} elseif ('user_log' == $type) {
if (!empty($id)) {
$query = 'SELECT * FROM `logs` WHERE `staff_id` = ' . $id . ' AND `type` = "staff_log"';
} else {
$query = 'SELECT * FROM `logs` WHERE `type` = "staff_log"';
}
} elseif ('lead_log' == $type) {
if (!empty($id)) {
$query = 'SELECT * FROM `logs` WHERE `client_id` = ' . $id . ' AND `type` = "lead_log"';
} else {
$query = 'SELECT * FROM `logs` WHERE `type` = "lead_log"';
}
} else {
$query = 'SELECT * FROM `logs` WHERE `type` = ' . $type;
}
$logs = Config::$db->query($query);
return $logs->fetch_all(MYSQLI_ASSOC);
} else {
return 'invalid log type';
}
$stmt->close();
}
The above is not quite the code I want to be writing, it's a similar example where the query related to the Log Type is being called. But that is a lot of mess that is not pleasing to look at. Also, the above code does not use Arrays which is what I hope to be using.
Lastly, the code I am hoping to write is mostly related to Updating existing records. So, say we have a User. The user has an Email and a Password and Address. According to the above code (first one) we will be updating the Email, Password, and Address every time the user updates any one of his field. I would like to avoid that.
My assumption is that I'd have to do something like so:
# 1. Loop Array using maybe For or Foreach
# 2. Have a whitelisted array of allowed values.
# 3. Append to query if an Array value exists.
# 4. Run query.
I fear my problem is at Point 3. I can't seem to figure out how to build the query without going through a lot of messy IF ELSE statements.
Now, by this point, I have certainly searched around SO to find a similar question, however, searches related to SQL and Arrays are almost entirely related to adding "multiple rows in a single SQL query" or something similar.

You can approach this by using arrays, in which keys are column name and containing the values
$columns = [
'field1' => 'value1',
'field2' => 'value2',
'field3' => 'value3',
'field4' => 'value4'
];
addSomethingToDB($columns);
function addSomethingToDB($cols){
# Do SQL Injection checks
$query = "INSER INTO `tablename` ( ".implode(",",array_keys($cols))." ) VALUES ( '".implode("','",array_values($cols))."' )";
}

Related

update profile php

I have create a profile page in php. The page includes the address and telephone fields and prompts the users to insert their data. Data are then saved in my table named profile.
Everything works fine, but the problem is that the table updated only if it includes already data. How can I modify it (probably mysql query that I have in my function), so that data will be entered into the table even if it is empty. Is there a something like UPDATE OR INSERT INTO syntax that I can use?
Thanks
<?php
if ( isset($_GET['success']) === true && empty($_GET['success'])===true ){
echo'profile updated sucessfuly';
}else{
if( empty($_POST) === false && empty($errors) === true ){
$update_data_profile = array(
'address' => $_POST['address'],
'telephone' => $_POST['telephone'],
);
update_user_profile($session_user_id, $update_data_profile);
header('Location: profile_update.php?success');
exit();
}else if ( empty($errors) === false ){
echo output_errors($errors);
}
?>
and then by using the following function
function update_user_profile($user_id, $update_data_profile){
$update = array();
array_walk($update_data_profile, 'array_sanitize');
foreach($update_data_profile as $field => $data )
{
$update[]='`' . $field . '` = \'' . $data . '\'';
}
mysql_query(" UPDATE `profile` SET " . implode(', ', $update) . " WHERE `user_id` = $user_id ") or die(mysql_error());
}
I'm new to the posted answer by psu, and will definatly check into that, but from a quick readthrough, you need to be very careful when using those special syntaxes.
1 reason that comes to mind: you have no knowledge of what might be happening to the table that you're inserting to or updating info from. If multiple uniques are defined, then you might be in serious trouble, and this is a common thing when scaling applications.
2 the replace into syntax is a functionality i rarely wish to happen in my applications. Since i do not want to loose data from colomns in a row that was allready in the table.
i'm not saying his answer is wrong, just stating precaution is needed when using it because of above stated reasons and possible more.
as stated in the first article, i might be a newbie for doing this but at this very moment i prefer:
$result = mysql_query("select user_id from profile where user_id = $user_id limit 1");
if(mysql_num_rows($result) === 1){
//do update like you did
}
else{
/**
* this next line is added after my comment,
* you can now also leave the if(count()) part out, since the array will now alwayss
* hold data and the query won't get invalid because of an empty array
**/
$update_data_profile['user_id'] = $user_id;
if(count($update_data_profile)){
$columns = array();
$values = array();
foreach($update_data_profile as $field => $data){
$columns[] = $field;
$values[] = $data;
}
$sql = "insert into profile (" . implode(",", $columns) .") values ('" . implode("','", $values) . "')" ;
var_dump($sql); //remove this line, this was only to show what the code was doing
/**update**/
mysql_query($sql) or echo mysql_error();
}
}
You cannot update the table if there isn't any data in it corresponding the user_id, meaning that you must have a row containing the user_id and null or something else for the other fields.
a) You can try to check if the table contains data and if not insert it else use update (not ideal)
$result = mysql_query("UPDATE ...");
if (mysql_affected_rows() == 0)
$result = mysql_query("INSERT ...");
b) Checkout this links
http://www.kavoir.com/2009/05/mysql-insert-if-doesnt-exist-otherwise-update-the-existing-row.html
http://dev.mysql.com/doc/refman/5.0/en/replace.html
http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
#Stefanos
you can use use "REPLACE INTO " command in place of "INSERT INTO" in the SQL query.
for example
Suppose you have insert query
INSERT INTO EMPLOYEE (NAME,ADD) values ('ABC','XYZZ');
Now you can use following query as combination of insert and update
REPLACE INTO EMPLOYEE (NAME,ADD) values ('ABC','XYZZ');
Hope this will help!

Ignore particular WHERE criteria

I want to execute a parameterized query to perform a search by user-supplied parameters. There are quite a few parameters and not all of them are going to be supplied all the time. How can I make a standard query that specifies all possible parameters, but ignore some of these parameters if the user didn't choose a meaningful parameter value?
Here's an imaginary example to illustrate what I'm going for
$sql = 'SELECT * FROM people WHERE first_name = :first_name AND last_name = :last_name AND age = :age AND sex = :sex';
$query = $db->prepare($sql);
$query->execute(array(':first_name' => 'John', ':age' => '27');
Obviously, this will not work because the number of provided parameters does not match the number of expected parameters. Do I have to craft the query every time with only the specified parameters being included in the WHERE clause, or is there a way to get some of these parameters to be ignored or always return true when checked?
SELECT * FROM people
WHERE (first_name = :first_name or :first_name is null)
AND (last_name = :last_name or :last_name is null)
AND (age = :age or :age is null)
AND (sex = :sex or :sex is null)
When passing parameters, supply null for the ones you don't need.
Note that to be able to run a query this way, emulation mode for PDO have to be turned ON
First, start by changing your $sql string to simply:
$sql = 'SELECT * FROM people WHERE 1 = 1';
The WHERE 1 = 1 will allow you to not include any additional parameters...
Next, selectively concatenate to your $sql string any additional parameter that has a meaningful value:
$sql .= ' AND first_name = :first_name'
$sql .= ' AND age = :age'
Your $sql string now only contains the parameters that you plan on providing, so you can proceed as before:
$query = $db->prepare($sql);
$query->execute(array(':first_name' => 'John', ':age' => '27');
If you can't solve your problem by changing your query... There are several libraries that help with assembling queries. I've used Zend_Db_Select in the past but every framework likely has something similar:
$select = new Zend_Db_Select;
$select->from('people');
if (!empty($lastName)) {
$select->where('lastname = ?', $lastname);
}
$select->order('lastname desc')->limit(10);
echo $select; // SELECT * FROM people WHERE lastname = '...' ORDER BY lastname desc LIMIT 10
I've tested the solution given by #juergen but it gives a PDOException since number of bound variables does not match. The following (not so elegant) code works regardless of no of parameters:
function searchPeople( $inputArr )
{
$allowed = array(':first_name'=>'first_name', ':last_name'=>'last_name', ':age'=>'age', ':sex'=>'sex');
$sql = 'SELECT * FROM sf_guard_user WHERE 1 = 1';
foreach($allowed AS $key => $val)
{
if( array_key_exists( $key, $inputArr ) ){
$sql .= ' AND '. $val .' = '. $key;
}
}
$query = $db->prepare( $sql );
$query->execute( $inputArr );
return $query->fetchAll();
}
Usage:
$result = searchPeople(array(':first_name' => 'John', ':age' => '27'));

PDO with table prefix

I have the follow Model class, which all my models extends.
class Model {
[...]
protected static $_query; // Query preparated
public function prepare($query = null) {
[...] // Connect to PDO, bla bla bla
self::$_query = self::$link->prepare($query);
}
[...]
}
class Login extends Model {
public function getUser($username = null) {
self::prepare('SELECT * FROM usuarios WHERE usuario = :username LIMIT 1');
self::bindValue('username', $username);
return self::fetch();
}
}
The problem is, I need to insert prefix to my mysql, to avoid table conflicts, but don't want to edit all my querys.
clientone_tablename
clienttwo_tablename
clientthree_tablename
How I can do this, parse and insert table prefix when prepare the query?
I have not tried nothing because what I know is, extend my custom PDO to PHP PDO class, which is not much now..
I have seen this: PDO - Working with table prefixes. But don't worked propertly..
Thanks!
So i've assume you have only 1 MySQL database (minimum package on your webhost) and need to store a copy of a system for each of your clients.
What I was suggesting, is that you create a separate set of tables as you already are (for each client), but the name wont matter because you have a look-up of the table names in your clients table.
Heres my example for you: The clients table should store the table names of their own tables
(e.g. users_tbl = clientone_users for client id:1) So that later on you can just query the clients table and get his/her table names, then use that result to query on his/her user, news, pages, and files tables.
# SQL: new table structure
-- store the names of the clients tables here
CREATE TABLE clients(
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY(id),
name VARCHAR(50),
address VARCHAR(250),
email VARCHAR(50),
pass BLOB,
/* table names*/
users_tbl VARCHAR(70),
news_tbl VARCHAR(70),
pages_tbl VARCHAR(70),
files_tbl VARCHAR(70)
) ENGINE = InnoDB;
# PHP: Some definitions for the table structure
$tbl_names = array("_users","_news","_pages","_files");
$tbl_fields = array();
$tbl_fields[0] = array("id INT","users_col1 VARCHAR(10)","users_col2 VARCHAR(20)");
$tbl_fields[1] = array("id INT","news_col1 DATE",...);
$tbl_fields[2] = array(...);
$tbl_fields[3] = array(...);
// refers to YOUR clients table field names (see above)
$clients_fields = array("users_tbl", "news_tbl", "pages_tbl", "files_tbl");
# PHP: Create a user and create the users database
function createUser($name, $address, $email, $pass, $salt) {
global $db, $tbl_names, $tbl_fields;
$success = false;
if ($db->beginTransaction()) {
$sql = "INSERT INTO clients(name, address, email, pass)
VALUES (?, ?, ?, AES_ENCRYPT(?, ?));"
$query = $db->prepare($sql);
$query->execute(array($name, $address, $email, $pass, $salt));
if ($query->rowCount() == 1) { # if rowCount() doesn't work
# get the client ID # there are alternative ways
$client_id = $db->lastInsertId();
for ($i=0; $i<sizeof($tbl_names); $i++) {
$client_tbl_name = $name . $tbl_names[$i];
$sql = "CREATE TABLE " . $client_tbl_name . "("
. implode(',', $tbl_fields[$i]) . ");";
if (!$db->query($sql)) {
$db->rollBack();
return false;
} else {
$sql = "UPDATE clients SET ".clients_fields[$i]."=? "
."WHERE id=?;";
$query = $db->prepare($sql);
if (!$query->execute(
array($client_tbl_name, (int)$client_id)
)) {
$db->rollBack();
return false;
}
}
}
$db->commit();
$success = true;
}
if (!$success) $db->rollBack();
}
return $success;
}
# PHP: Get the Client's table names
function getClientsTableNames($client_id) {
$sql = "SELECT (users_tbl, news_tbl, pages_tbl, files_tbl)
FROM clients WHERE id=?;";
$query = $db->prepare($sql);
if ($query->execute(array((int)$client_id)))
return $query->fetchAll();
else
return null;
}
# PHP: Use the table name to query it
function getClientsTable($client_id, $table_no) {
$table_names = getClientsTableNames($client_id);
if ($table_names != null && isset($table_names[$table_no])) {
$sql = "SELECT * FROM ".$table_names[$table_no].";";
$query = $db->prepare($sql);
if ($query->execute(array((int)$client_id)))
return $query->fetchAll();
}
return null;
}
Just rewrite your queries to use a table prefix found in a variable somewhere. Parsing all your queries for tablenames is more trouble than it is worth. (Do you really want to write an SQL parser?)

Issues incrementing a field in MySQL/PHP with prepared statements

I have the following code which is supposed to increment a field value by 1 in a prepared PHP mysql statement:
function db_OP_doVote($pdo, $postid, $votetype)
{
$prepStatement = $pdo->prepare(
"UPDATE content_posts SET `:votetype` = `:votetype` + 1 WHERE `id` = :id"
);
$prepStatement->execute(array(':votetype' => $votetype, ':id' => $postid));
echo "Success";
}
This however, does nothing. No error is thrown back about incorrect SQL syntax and the script runs to completion, but my field does not update at all.
The values for this script are fed through a jQuery post() to this script:
//isset checking here
$postID = (int)$_POST['id'];
$voteType = $_POST['type'];
if ($voteType == "y")
{
$trueType = "v-cool";
}
elseif ($voteType == "m")
{
$trueType = "v-meh";
}
elseif ($voteType == "n")
{
$trueType = "v-shit";
}
else
{
die();
}
$db = db_Connect();
db_OP_doVote($db, $postID, $trueType);
Which also appears to filter the values and send them fine. I can't wrap my head around what the issue could be. The field being incremented is a BIGINT(20).
What am I missing?
EDIT: Solved the issue.
N.B's comment hit the nail on the head - binding the column name causes it to be quoted, which invalidates the query. Thanks!
you can't use binding for the field names.
from the question it seems that your setup is wrong.
you should have another table with votes and vote types as data.
You can't parameterize column names with PDO. What you can do is have hard-coded values (which you basically already have) and construct the SQL string accordingly. I would check this value in the actual function too though, just to be on the safe side:
function db_OP_doVote($pdo, $postid, $votetype)
{
if( !in_array( $votetype, array( 'v-cool', 'v-meh', 'v-shit' /*, etc. */ ), true ) )
{
throw new InvalidArgumentException( 'Unexpected $votetype: ' . $votetype );
// or simply return false perhaps
}
$sql = '
UPDATE content_posts
SET `' . $votetype . '` = `' . $votetype . '` + 1
WHERE `id` = :id
';
$prepStatement = $pdo->prepare( $sql );
$prepStatement->execute(array(':id' => $postid));
echo "Success";
}
However, this strategy suggests your database design could use a little more attention. The way you have it now, is that for every type of vote, you have a column. This is not really efficient and/or flexible database design. What happens if you get asked to add another type of vote?
I'd suggest adding another table, to be more flexible:
CREATE TABLE `content_post_vote` (
`content_post_id` int(11) NOT NULL,
`vote_type` enum('cool','meh','shit') NOT NULL, # using enum() to assure valid vote types
`votes` bigint(20) DEFAULT NULL,
PRIMARY KEY (`content_post_id`,`vote_type`)
)
Then your query would be something like:
$sql = '
INSERT INTO `content_post_vote` (`content_post_id`,`vote_type`,`votes`)
VALUES( :id, :votetype, 1 )
ON DUPLICATE KEY UPDATE `votes` = `votes` + 1
';
What this does is insert a vote if there is no record for a certain primary key (content_post_id,vote_type) yet, and else update the record with a vote if the record already exists.
Then to query the database for how many votes of a particular type a particular content_post has gotten, you do this:
$sql = '
SELECT `votes` # or perhaps more columns
FROM `content_post_vote`
WHERE `content_post_id` = :id AND
`vote_type` = :votetype
';

Bulk Parameterized Inserts

I'm trying to switch some hard-coded queries to use parameterized inputs, but I've run into a problem: How do you format the input for parameterized bulk inserts?
Currently, the code looks like this:
$data_insert = "INSERT INTO my_table (field1, field2, field3) ";
$multiple_inserts = false;
while ($my_condition)
{
if ($multiple_inserts)
{
$data_insert .= " UNION ALL ";
}
$data_insert .= " SELECT myvalue1, myvalue2, myvalue3 ";
}
$recordset = sqlsrv_query($my_connection, $data_insert);
A potential solution (modified from How to insert an array into a single MySQL Prepared statement w/ PHP and PDO) appears to be:
$sql = 'INSERT INTO my_table (field1, field2, field3) VALUES ';
$parameters = array();
$data = array();
while ($my_condition)
{
$parameters[] = '(?, ?, ?)';
$data[] = value1;
$data[] = value2;
$data[] = value3;
}
if (!empty($parameters))
{
$sql .= implode(', ', $parameters);
$stmt = sqlsrv_prepare($my_connection, $sql, $data);
sqlsrv_execute($stmt);
}
Is there a better way to accomplish a bulk insert with parameterized queries?
Well, you have three options.
Build once - execute multiple. Basically, you prepare the insert once for one row, then loop over the rows executing it. Since the SQLSERVER extension doesn't support re-binding of a query after it's been prepared (you need to do dirty hacks with references) that may not be the best option.
Build once - execute once. Basically, you build one giant insert as you said in your example, bind it once, and execute it. This is a little bit dirty and misses some of the benefits that prepared queries gives. However, due to the requirement of references from Option 1, I'd do this one. I think it's cleaner to build a giant query rather than depend on variable references.
Build multiple - execute multiple. Basically, take the method you're doing, and tweak it to re-prepare the query every so many records. This prevents overly big queries and "batches" the queries. So something like this:
$sql = 'INSERT INTO my_table (field1, field2, field3) VALUES ';
$parameters = array();
$data = array();
$execute = function($params, $data) use ($my_connection, $sql) {
$query = $sql . implode(', ', $parameters);
$stmt = sqlsrv_prepare($my_connection, $query, $data);
sqlsrv_execute($stmt);
}
while ($my_condition) {
$parameters[] = '(?, ?, ?)';
$data[] = value1;
$data[] = value2;
$data[] = value3;
if (count($parameters) % 25 == 0) {
//Flush every 25 records
$execute($parameters, $data);
$parameters = array();
$data = array();
}
}
if (!empty($parameters)) {
$execute($sql, $parameters, $data);
}
Either method will suffice. Do what you think fits your requirements best...
Why not just use "prepare once, execute multiple" method. I know you want it to either all fail or all work, but it's not exactly hard to handle that with transactions:
http://www.php.net/manual/en/pdo.begintransaction.php
http://www.php.net/manual/en/pdo.commit.php
http://www.php.net/manual/en/pdo.rollback.php

Categories