For reasons that should be obvious, this is murder to search for...
How do I do this in PDO:
SELECT thing FROM things WHERE thing_uid IN ( ... )
My particular use case is a string built by exploding an array taken from a form with several dozen checkboxes. In standard MySQL this is very easy...
$thingString = implode("', '", $thingArray);
$q = "SELECT thing FROM things WHERE thing_uid IN ('$thingString')";
but I want that to benefit from PDO's anti-injection protection... bound params and all that. So how can I do it?
Create an array of as many ? as you have values, and throw that into the query.
$placeholders = array_fill(0, count($thingArray), '?');
$sql = "SELECT thing FROM things WHERE thing_uid IN (" . implode(',', $placeholders) . ")";
Related
I have a table with hundreds of columns. The table structure is out of my control (controlled by a third party). The table also has horrendous field names with spaces, single quotes, etc. and so do the table values. The table is updated once per hour via cron. The cron job truncates and rebuilds the table each time. I also keep an archive table of that table, that I use a REPLACE INTO statement to update or insert as required.
My challenge - I prefer not to have to explicitly define all 350 field names and values, and do so again in my REPLACE INTO statement as this will take a very long time and will require maintenance if the table changes. I would much rather use arrays. Here is what is not working but hopefully gives an idea of the goal (I realize this is deprecated MySQL but it is what it is for a variety of reasons):
$listings = mysql_query("SELECT * FROM current.table");
while ($listing = mysql_fetch_assoc($listings)){
//prepare variables
$fields = array_keys($listing);
$fields = implode('`, `', $fields);
$fields = "`$fields`";
$values = array_values($listing);
$values = implode("`, `", $values);
$values = "`$values`";
mysql_query('REPLACE INTO archive.table ($fields) VALUES ($values)');
}
Posting as a community wiki, no rep should come from this since it did solve the OP's question (as per suggested in comments).
"Aha! Erroneous single quotes on the mysql_query statement was the culprit. I also did mysql_real_escape_string on $values and used single quotes instead of ticks. Worked like a charm. Thank you! Final answer: – Tavish"
Use mysql_error() on the queries. What you posted seems legit code, however values needs to be quoted ' and escaped for possible injection, not ticked.
While using double quotes " for the second query's encapsulation.
mysql_query("REPLACE INTO archive.table ($fields) VALUES ($values)");
As well as other suggestions given.
OP's final code (taken from comments):
while ($listing = mysql_fetch_assoc($listings)){
$fields = array_keys($listing);
$fields = implode(', ', $fields);
$fields = "$fields";
$values = array_values($listing);
$values = implode(", ", $values);
$values = mysql_real_escape_string($values);
$values = str_replace("`","'",$values);
$values = "'$values'";
mysql_query("REPLACE INTO archive.table ($fields) VALUES ($values)");
}
I ran into the following question while writing a PHP script. I need to store the first two integers from an array of variable lenght into a database table, remove them and repeat this until the array is empty. I could do it with a while loop, but I read that you should avoid writing SQL statements inside a loop because of the performance hit.
A simpliefied example:
while(count($array) > 0){
if ($sql = $db_connect->prepare("INSERT INTO table (number1, number2) VALUES (?,?)")){
$sql->bind_param('ii',$array[0],$array[1]);
$sql->execute();
$sql->close();
}
array_shift($array);
array_shift($array);
}
Is this the best way, and if not, what's a better approach?
You can do something like this, which is way faster aswell:
Psuedo code:
$stack = array();
while(count($array) > 0){
array_push($stack, "(" . $array[0] . ", " . $array[1] . ")");
array_shift($array);
array_shift($array);
}
if ($sql = $db_connect->prepare("INSERT INTO table (number1, number2)
VALUES " . implode(',', $stack))){
$sql->execute();
$sql->close();
}
The only issue here is that it's not a "MySQL Safe" insert, you will need to fix that!
This will generate and Array that holds the values. Within 1 query it will insert all values at once, where you need less MySQL time.
Whether you run them one by one or in an array, an INSERT statement is not going to make a noticeable performance hit, from my experience.
The database connection is only opened once, so it is not a huge issue. I guess if you are doing some insane amount of queries, it could be.
I think as long as your loop condition is safe ( will break in time ) and you got something from it .. it's ok
You would be better off writing a bulk insert statement, less hits on mysql
$sql = "INSERT INTO table(number1, number2) VALUES";
$params = array();
foreach( $array as $item ) {
$sql .= "(?,?),\n";
$params[] = $item;
}
$sql = rtrim( $sql, ",\n" ) . ';';
$sql = $db_connect->prepare( $sql );
foreach( $params as $param ) {
$sql->bind_param( 'ii', $param[ 0 ], $param[ 1 ] );
}
$sql->execute();
$sql->close();
In ColdFusion you can put your loop inside the query instead of the other way around. I'm not a php programmer but my general belief is that most things that can be done in language a can also be done in language b. This code shows the concept. You should be able to figure out a php version.
<cfquery>
insert into mytable
(field1, field2)
select null, null
from SomeSmallTable
where 1=2
<cfloop from="1' to="#arrayLen(myArray)#" index="i">
select <cfqueryparam value="myArray[i][1]
, <cfqueryparam value="myArray[i][]
from SomeSmallTable
</cfloop>
</cfquery>
When I've looked at this approach myself, I've found it to be faster than query inside loop with oracle and sql server. I found it to be slower with redbrick.
There is a limitation with this approach. Sql server has a maximum number of parameters it will accept and a maximum query length. Other db engines might as well, I've just not discovered them yet.
What I'm trying to do is go from a search URL such as this:
search.php?president=Roosevelt,+F.&congress=&nomination_received_by_senate=&state=CT
To a MySQL query like this:
SELECT `name` FROM `nominations` WHERE president=`Roosevelt, F.` AND state=`CT`
I have some code that strips any empty values from the URL, so I have an array as such:
Array ( [president] => Roosevelt, F. [state] => CT )
Going from this to the SQL query is what is giving me trouble. I was hoping there might be some simple means (either by some variation of PHP's join() or http_build_query()) to build the query, but nothing seems to work how it needs to and I'm pretty lost for ideas even after searching.
Not sure if it would require some messy loops, if there is a simple means, or if the way I'm going about trying to accomplish my goal is wrong, but I was hoping someone might be able to help out. Thanks in advance!
Edit: To clarify, sometimes the inputs could be empty (as in the case here, congress and nomination_received_by_senate), and I'm hoping to accommodate this in the solution. And yes, I intend to implement means to avoid SQL injection. I have only laid out the basics of my plan hoping for some insight on my methods.
You could build up your query string like this if your GET params match your db fields:
$field_array = array('president', 'congress', 'nomination_received_by_senate', 'state');
$query = 'SELECT `name` FROM `nominations` WHERE ';
$conditions = array();
foreach($field_array as $field) {
$value = $_GET[$field];
if(empty($value)) continue;
$condition = mysql_real_escape_string($field) . '` = ';
$quote = '';
if(!is_numeric($value)) $quote = '"';
$condition .= $quote . mysql_real_escape_string($value) . $quote;
$conditions[] = $condition;
}
$query .= implode(' AND ', $conditions) . ';';
//perform query here...
To access the $_GET variables you could just do $_GET["president"] and $_GET["state"] and get the information. Make sure you sanitize the input.
$president = sanitize($_GET["president"]);
$state = sanitize($_GET["state"]);
$result = mysql_query("SELECT name FROM nominations WHERE president='".$president."' AND state='".$state"'");
Sanitize would be a function like mysql_real_escape_string() or your own function to clean up the input. Make sure you check if the $_GET variables are set using the isset() function.
User writes a series of tags (, separated) and posts the form.
I build an array containing the tags and delete dupes with array_unique() php function.
I'm thinking of doing:
go through the array with foreach($newarray as $item) { ... }
check each $item for existence in the tags mySQL table
if item does not exists, insert into tags table
Is there a FASTER or MORE OPTIMUM way for doing this?
I'm hoping that people can comment on this method. Intuition tells me its probably not the best way to go about things, but what do you all think?
Instead of making an extra call to the DB to find duplicates, just add a unique constraint in the DB so that tags cannot be inserted twice. Then use INSERT IGNORE... when you add new tags. This method will ignore the errors caused by duplications while at the same time inserting the new items.
You can call implode( ',' $array ) then use the IN SQL construct to check if there are existing rows in the db.
Example:
<?php
$arr = ...
$sql = 'SELECT COUNT(*) FROM table WHERE field IN ( ' . implode( ',', $arr ) . ' );';
$result = $db->query( $sql );
?>
$tags = array('foo', 'bar');
// Escape each tag string
$tags = array_map('mysql_real_escape_string', $tags, array($sqlCon));
$tagList = '"' . implode('", "', $tags) . '"';
$qs = 'SELECT id FROM tag_list WHERE tag_name IN (' . $tagList . ')';
I don´t know if it's faster, but you can use mysql's IN. I guess you'd have to try.
You could build up a big query and do it in one shot, instead of many little queries:
SELECT DISTINCT tag
FROM my_tags
WHERE tag in ( INPUT_TAGS )
Just build up INPUT_TAGS as a comma-separated list, and make sure you properly escape each element to prevent SQL injection attacks.
In PHP, I want to insert into a database using data contained in a associative array of field/value pairs.
Example:
$_fields = array('field1'=>'value1','field2'=>'value2','field3'=>'value3');
The resulting SQL insert should look as follows:
INSERT INTO table (field1,field2,field3) VALUES ('value1','value2','value3');
I have come up with the following PHP one-liner:
mysql_query("INSERT INTO table (".implode(',',array_keys($_fields)).") VALUES (".implode(',',array_values($_fields)).")");
It separates the keys and values of the the associative array and implodes to generate a comma-separated string . The problem is that it does not escape or quote the values that were inserted into the database. To illustrate the danger, Imagine if $_fields contained the following:
$_fields = array('field1'=>"naustyvalue); drop table members; --");
The following SQL would be generated:
INSERT INTO table (field1) VALUES (naustyvalue); drop table members; --;
Luckily, multiple queries are not supported, nevertheless quoting and escaping are essential to prevent SQL injection vulnerabilities.
How do you write your PHP Mysql Inserts?
Note: PDO or mysqli prepared queries aren't currently an option for me because the codebase already uses mysql extensively - a change is planned but it'd take alot of resources to convert?
The only thing i would change would be to use sprintf for readability purposes
$sql = sprintf(
'INSERT INTO table (%s) VALUES ("%s")',
implode(',',array_keys($_fields)),
implode('","',array_values($_fields))
);
mysql_query($sql);
and make sure the values are escaped.
Nothing wrong with that. I do the same.
But make sure you mysql_escape() and quote the values you stick in the query, otherwise you're looking at SQL injection vulnerability.
Alternately, you could use parametrized queries, in which case you can practically pass the array in itself, instead of building a query string.
The best practice is either to use an ORM (Doctrine 2.0), an ActiveRecord implementation (Doctrine 1.0, RedBean), or a TableGateway pattern implementation (Zend_Db_Table, Propel). These tools will make your life a lot easier, and handle a lot of the heavy lifting for you, and can help protect you from SQL injections.
Other than that, there's nothing inherently wrong with what you're doing, you just might want to abstract it away into a class or a function, so that you can repeat the functionality in different places.
Using the sprintf trick mentioned by Galen in a previous answer, I have come up with the following code:
$escapedfieldValues = array_map(create_function('$e', 'return mysql_real_escape_string(((get_magic_quotes_gpc()) ? stripslashes($e) : $e));'), array_values($_fields));
$sql = sprintf('INSERT INTO table (%s) VALUES ("%s")', implode(',',array_keys($_fields)), implode('"," ',$escapedfieldValues));
mysql_query($sql);
It generates a escaped and quoted insert. It also copes independent of whether magic_quotes_gpc is on or off. The code could be nicer if I used new PHP v5.3.0 anonymous functions but I need it to run on older PHP installations.
This code is a bit longer that the original (and slower) but it is more secure.
I use this to retrieve the VALUES part of the INSERT.
But it might be an absurd way to do things. Comments/suggestions are welcome.
function arrayToSqlValues($array)
{
$sql = "";
foreach($array as $val)
{
//adding value
if($val === NULL)
$sql .= "NULL";
else
/*
useless piece of code see comments
if($val === FALSE)
$sql .= "FALSE";
else
*/
$sql .= "'" . addslashes($val) . "'";
$sql .= ", ";
};
return "VALUES(" . rtrim($sql, " ,") . ")";
}
There is a problem with NULL (in the accepted answer) values being converted to empty string "". So this is fix, NULL becomes NULL without quotes:
function implode_sql_values($vals)
{
$s = '';
foreach ($vals as $v)
$s .= ','.(($v===NULL)?'NULL':'"'.mysql_real_escape_string($v).'"');
return substr($s, 1);
}
Usage:
implode_sql_values(array_values( array('id'=>1, 'nick'=>'bla', 'fbid'=>NULL) ));
// =='"1","bla",NULL'
If you want to enhance your approach and add the possibility for input validation and sanitation, you might want to do this:
function insertarray($table, $arr){
foreach($arr as $k => $v){
$col[] = sanitize($k);
$val[] = "'".sanitize($v)."'";
}
query('INSERT INTO '.sanitize($table).' ('.implode(', ', $col).') VALUES ('.implode(', ', $val).')' );
}