Dynamic prepared statements with sqlsrv (MSSQL) in PHP - php

Generating the statement elements
I am trying to create an MSSQL prepared statement in a generic way.
Basically, I loop trough the fields, and add them to the prepared SQL string and to the reference parameter (scroll down to see the code).
This results in (names removed):
Prepared query: :
INSERT INTO table
([field],[field],[field],[field],[field],[field],[field],[field],[field],[field],[field],[field],[field],[field],[field],VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
and
sPrepStatement:
return array(&$aLine[0],&$aLine[1],&$aLine[2],&$aLine[3],&$aLine[4],&$aLine[5],&$aLine[6],&$aLine[7],&$aLine[8],&$aLine[9],&$aLine[10],&$aLine[11],&$aLine[12],&$aLine[13],&$aLine[14]);
Preparing the statement
I've tried the following 4 approaches to get this to work with the sqlsrv_prepare statement:
$oSQLStmnt = sqlsrv_prepare($dbhandle, $sPreppedSQL, eval($sPrepStatement));
$fReturnPrepVals = function(){ return eval($sPrepStatement); } ;
$oSQLStmnt = sqlsrv_prepare($dbhandle, $sPreppedSQL, $fReturnPrepVals);
$oSQLStmnt = sqlsrv_prepare($dbhandle, $sPreppedSQL,$aPrepValues);
$oSQLStmnt = call_user_func_array('sqlsrv_prepare',array($dbhandle,$sPreppedSQL, eval($sPrepStatement)));
This either does not work, or inserts blanks into the database.
This is the loop that executes the SQL:
foreach ($aLines as $iLineNum => $sLine) {
$aLine = explode('|', $sLine);
print_r($aPrepValues);
print_r(eval($sPrepStatement));
sqlsrv_execute($oSQLStmnt);
}
The eval($sPrepStatement) works just fine, which makes sense. But I assume it is 'parsed too early' to work in the prepare statement. It should actually only be parsed as the query is executed, but I'm at a loss how to achieve that.
Code for the generation of sql statements:
<?php
//Setup the basic query
$sSql = "INSERT INTO " . $sQName . "\n";
$sFieldNames = "(";
$sValues = "VALUES(";
//This will be evalled to prepare the query, so inserting will be easy.
$sPrepStatement = "return array(";
//FieldIndex to keep track of the current column number
$iFI = 0;
$aLine = array();
foreach ($aSQLQueries[$sQName]['fields'] as $sFieldName => $sQ) {
$sFieldNames .= "[" . $sFieldName . "],";
$sValues .= "?,";
//Init the $aLine var to prevent errors
$aLine[$iFI] = '';
//This will be evalled, so no "" as that would parse it directly
//The values wil be passed by reference
$sPrepStatement .= '&$aLine[' . $iFI . '],';
//Different approach
$aPrepValues[] = &$aLine[$iFI];
$iFI++;
}
$sFieldNames = substr($sFieldNames, 0, -1) . ")";
$sValues = substr($sValues, 0, -1) . ")";
$sPrepStatement = substr($sPrepStatement, 0, -1) . ");";
$sPreppedSQL = $sSql . $sFieldNames . " " . $sValues;

I ended up making it work with PDO, only to find that it could not handle millions of imports at a time, so I eventually used CSV import

Related

PDO update same value [duplicate]

The code I've written so far works fine if there is only one named place holder for a prepared statement but if there are multiple conditions for a query, it doesn't return any results from the database.
For instance:
$query = array();
$query['columns'] = array('*');
$query['tables'] = array('esl_comments');
$query['where'] = array(
'esl_comments.commentVisible' => array('=', 'Y')
);
Works fine. But if I try:
$query = array();
$query['columns'] = array('*');
$query['tables'] = array('esl_comments');
$query['where'] = array(
'esl_comments.commentVisible' => array('=', 'Y'),
'esl_comments.commentID' => array('=', '1'),
);
(Note the additional commentID parameter) it fails to return anything despite there being data in the mySQL database that satisfies the conditions.
The PDO code i've written is:
$sql ='SELECT ';
foreach($query['columns'] as $column){ //What columnns do we want to fetch?
$sql.=$column . ", ";
}
$sql = rtrim($sql, " ,");
$sql .=' FROM '; //Which tables will we be accessing?
foreach($query['tables'] as $tables){
$sql.=$tables . ", ";
}
$sql = rtrim($sql, " ,"); //Get rid of the last comma
$sql .=' WHERE ';
if(array_key_exists('where', $query)) //check if a where clause was provided
{
$fieldnames = array_keys($query['where']);
$count = 0;
$size = sizeof($fieldnames);
$bindings = array();
foreach($query['where'] as $where){
$cleanPlaceholder = str_replace("_", "", $fieldnames[$count]);
$cleanPlaceholder = str_replace(".", "", $cleanPlaceholder);
$sql.=$fieldnames[$count].$where[0].":".$cleanPlaceholder." AND ";
$bindings[$cleanPlaceholder]=$where[1];
$count++;
}
$sql = substr($sql, 0, -5); //Remove the last AND
}
else{ //no where clause so set it to an always true check
$sql.='1=1';
$bindings=array('1'=>'1'); //Provide default bindings for the statement
}
$sql .= ';'; //Add the semi-colon to note the end of the query
echo $sql . "<br/><br/>";
// exit();
$stmt = $this->_connection->prepare($sql);
foreach($bindings as $placeholder=>$bound){
echo $placeholder . " - " . $bound."<br/>";
$stmt->bindParam($placeholder, $bound);
}
$result = $stmt->execute();
echo $stmt->rowCount() . " records<br/>";
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
I'm building queries dynamically and therefore I am cleaning the placeholders, by stripping them of periods and underscores - hence the use of the 'cleanPlaceholder' variable.
The query being generated looks like this:
SELECT * FROM esl_comments WHERE esl_comments.commentVisible=:eslcommentscommentVisible AND esl_comments.commentID=:eslcommentscommentID;
And the parameters being bound look like this:
eslcommentscommentVisible - Y
eslcommentscommentID - 1
bindParam Requires a reference
The problem is caused by the way you bind parameters in the foreach loop.
foreach($bindings as $placeholder=>$bound){
echo $placeholder . " - " . $bound."<br/>";
$stmt->bindParam($placeholder, $bound);
}
bindParam requires a reference. It binds the variable, not the value, to the statement. Since the variable in a foreach loop is reset at the start of each iteration, only the last reference to $bound is left intact, and you end up binding all your placeholders to it.
That's why your code works when $query['where'] contains only one entry, but fails when it contains more than one.
You can solve the problem in 2 ways:
Pass by reference
foreach($bindings as $placeholder => &$bound) { //pass $bound as a reference (&)
$stmt->bindParam($placeholder, $bound); // bind the variable to the statement
}
Pass by value
Use bindValue instead of bindParam:
foreach($bindings as $placeholder => $bound) {
$stmt->bindValue($placeholder, $bound); // bind the value to the statement
}

Dynamic "Where" clause in wpdb->prepare query

In the form, none of the inputs are mandatory. So, I want to have a dynamic "where" clause inside the wpdb query.
Presently this is the query:
$data = $wpdb->get_results($wpdb->prepare("SELECT * FROM
`wp_gj73yj2g8h_hills_school_data` where
`school_zipcode` = %d AND `school_type` = %s AND `school_rating` = %s
;",$selectedZip,$selectedType,$selectedRating));
if a user enters only school_zipcode then the where clause should have only "school_zipcode" column.
Same way for other combinations.
I would not make things complicated with dynamic where clauses... I would write PHP code which creates the query. For example...
NOTE!! THIS CODE IS NOT TESTED ON SERVER, IT'S JUST AN IDEA HOW TO SOLVE THE PROBLEM!
<?php
$where_query = array();
// Make sure to escape $_POST
if (!empty($_POST['school_zipcode')) {
$where_query[] = "school_zipcode='" . $_POST['school_zipcode'] . "'";
}
// Make sure to escape $_POST
if (!empty($_POST['school_type')) {
$where_query[] = "school_type='" . $_POST['school_type'] . "'";
}
// Should result in WHERE school_zipcode='123' AND school_type='text'
$where_query_text = " WHERE " . implode(' AND ', $where_query);
$data = $wpdb->get_results($wpdb->prepare("SELECT * FROM `wp_gj73yj2g8h_hills_school_data` " . $where_query_text . ";"));

Extracting an array into dynamic variables

I am trying to be lazy (or smart): I have 7 checkboxes which correlate with 7 columns in a MySQL table.
The checkboxes are posted in an array:
$can = $_POST['can'];
I've created the following loop to dump the variables for the MySQL insert:
for($i=1;$i<8;$i++){
if($can[$i] == "on"){
${"jto_can".$i} = 'Y';
}
else{
${"jto_can".$i} = 'N';
}
}
print_r($jto_can1.$jto_can2.$jto_can3.$jto_can4.$jto_can5.$jto_can6.$jto_can7);
This correctly outputs:
YYNYYYY
However, when I attempt to use those variables in my MySQL update, it doesn't accept the changes.
mysqli_query($db, "UPDATE jto SET jto_can1 = '$jto_can1', jto_can2 = '$jto_can2', jto_can3 = '$jto_can3', jto_can4 = '$jto_can4', jto_can5 = '$jto_can5', jto_can6 = '$jto_can6', jto_can7 = '$jto_can7' WHERE jto_id = '$id'")or die(mysqli_error($db));
Can anyone explain why the print_r displays the variables whereas MySQL update does not?
Stick with the array, and form the query dynamically:
$sql = 'UPDATE jto SET ';
$cols = array();
foreach( range( 1, 7) as $i) {
$value = $_POST['can'][$i] == 'on' ? 'Y' : 'N'; // Error check here, $_POST['can'] might not exist or be an array
$cols[] = 'jto_can' . $i . ' = "' . $value . '"';
}
$sql .= implode( ', ', $cols) . ' WHERE jto_id = "' . $id . '"';
Now do a var_dump( $sql); to see your new SQL statement.
this is not a mysql problem. mysql will only see what you put into that string. e.g. dump out the query string BEFORE you do mysql_query. I'm guessing you're doing this query somewhere else and have run into scoping problems. And yes, this is lazy. No it's not "smart". you're just making MORE work for yourself. What's wrong with doing
INSERT ... VALUES jto_can1=$can[0], jto_can2=$can[1], etc...

FOR looping while grabbing part of the data from $_POST

It has been awhile since I have messed with for loops and was wondering if this is possible:
Here is what I have:
$mn = $_POST['mnpoints'];
$mi = $_POST['mipoints'];
$in = $_POST['inpoints'];
$wi = $_POST['wipoints'];
These grab numbers from a form once the submit button is clicked it will the use the following code to update the MySQL database
for ($i = 0; $i <=4; $i++ )
{
mysql_query("UPDATE " . $db_table . " SET `score` = `score` + <VARIABLE HERE> WHERE id=$i") or die (mysql_error());
}
What would be the best way to accomplish this? I can always change the variables to be $m1 - $m4 and the just use $m$i
Just wondering if there is another way or a better way
Thanks
Put your variables into an array:
$values = array($mn, $mi, $in, $wi);
Then iterate over it using foreach, or index it using $i.
Note: Your code is vulnerable to SQL injection attacks. I strongly suggest you read up on mysqli, prepared statements etc.:
http://php.net/manual/en/book.mysqli.php
The mysql interface is officially deprecated and should not be used anymore.
How does 'mnpoints' and 'inpoints' etc... relate to "score"?If you're just looking at dumping all those values and adding them togetherm, then:
$score = $mn + $mi + $in + $wi;
$sql = "UPDATE ... SET score=score+$score";
function bulkUpdateSingleColumn($table, $id_column, $update_column, array &$idstovals){
$sql = "update $table set $update_column = CASE $id_column ";
foreach($idstovals as $id=>$val){
$sql .= " WHEN '$id' THEN '$val' \n";
}
$sql .= " END
WHERE $id_column in (" . implode(',', array_keys($idstovals)) . ")";
//debugging info
echo '<small>'.$sql.'</small>';
$idstovals=array();
db_query($sql);
done();
}

a reusable query with modifications?

i'm using this mysql query alongwith php to search for multiple keywords:
$query = "SELECT cQuotes, vAuthor, cArabic, vReference FROM ".$table." WHERE (";
$countFields = count($arrayFields);
while ($a < $countFields)
{
while ($b < $countSearch)
{
$query = $query."$arrayFields[$a] LIKE '%$arraySearch[$b]%'";
$b++;
if ($b < $countSearch)
{
$query = $query." AND ";
}
}
$b = 0;
$a++;
if ($a < $countFields)
{
$query = $query.") OR (";
}
}
$query = $query.")";
$result = mysql_query($query, $conn)
i'd like to reuse this query with a few modifications to it (for instance, the WHERE clause remains the same, while i query the number of rows using COUNT), but it doesn't seem practical to repeat the code again for a few additions. any suggestions?
I don't understand exactly what you're doing since there's code missing, but I'd suggest the following:
Don't use while with arrays; use foreach it's much more compact and that's what it was made for.
Don't concatenate strings manually, use implode()
Don't add complexity to your SQL to count result; use MYSQL's FOUND_ROWS() instead.
On a somewhat unrelated note I'd suggest upgrading from PHP's mysql library to mysqli. It allows multiple queries, which will make your life easier.
You could pull that code out into a separate function, then send it a parameter telling the function what version of the query you want. The function would then construct the query and return the string. I also think prepared statements might be beneficial to you.
You could try using a query builder library or ORM, especially if this problem is happening repeatedly. They allow you to create SQL functionally. I would suggest using Doctrine or Sqloo (spoiler alert: I'm the creator of Sqloo). Since you can use them to functionally create SQL, you can even pass partial queries around since they object, to allow for a very high reuse of code.
A few examples for Doctrine and Sqloo.
<?php
$table = "myTable";
$justCount = true;
$requiredFields = array('cQuotes', 'vAuthor', 'cArabic', 'vReference');
$arrayFields = array('cQuotes','vAuthor');
$arraySearch = array('blah','foo','bar');
///////////////
$selectWhat = $justCount ? "COUNT(*)" : implode(',', $requiredFields);
$wherePart = array();
foreach($arraySearch as $search)
{
$subWherePart = array();
foreach($arrayFields as $field)
{
$subWherePart[] = $field . " LIKE '%" . $search ."%'";
}
$wherePart[] = "(" . implode(" AND ", $subWherePart) . ")";
}
$query = "SELECT " . $selectWhat . " FROM " . $table
. " WHERE " . implode(" OR ", $wherePart);
?>
don't forget to filter input search words to avoid SQL Injection.

Categories