Dynamically bind values PDO with foreach() [duplicate] - php

This question already has answers here:
When to use single quotes, double quotes, and backticks in MySQL
(13 answers)
Closed 5 years ago.
I have a need to generate a MySQL query dynamically, and values of specific types may or may not appear in specific column types. Here is a case for a query generated for two x values and one y value, each of which must be present in either of respective sets of columns (please to not read it too close, since the query itself has been tested extensively and works alright if the proper parameters are inserted manually):
SELECT
*
FROM
TABLE
WHERE
(
/*start of block x0 */
(
(columm_x0 = ':value_type1_index1')
OR (column_x1 = ':value_type1_index1')
OR (column_x2 = ':value_type1_index1')
OR (column_x3 = ':value_type1_index1')
OR (column_x4 = ':value_type1_index1')
)
/* end of block 0*/
/*start of block x1 */
OR (
(columm_x0 = ':value_type1_index2')
OR (column_x1 = ':value_type1_index2')
OR (column_x2 = ':value_type1_index2')
OR (column_x3 = ':value_type1_index2')
OR (column_x4 = ':value_type1_index2')
)
/*end of block x1*/
/*start of block y1*/
AND (
(columm_y0 = ':value_type2_index1')
OR (column_y1 = ':value_type2_index1')
OR (column_y2 = ':value_type2_index1')
OR (column_y3 = ':value_type2_index1')
OR (column_y4 = ':value_type2_index1')
) /*end of block y1*/
)
This whole query is supplied to $query variable.
So each time we must search for values in all specific columns no matter what. The parameters themselves are supplied as array:
$values = Array ( [type1] => Array ( [0] => value1 [1] => value2 )[type2] => Array ( [0] => value3 ))
My PDO looks like the following:
try {
$connect = new PDO("mysql:host=".$db['server'].";dbname=".$db['db'], $db['mysql_login'], $db['mysql_pass'], array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));
$connect->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt=$connect->prepare($query);
foreach ($values as $type => $typevalue){
foreach ($typevalue as $element => $elementvalue){
$parameter = ":value_{$type}_index{$element}";
$stmt->bindValue($parameter, $elementvalue, PDO::PARAM_STR);//i think here is the problem!
echo "<br>$parameter = $elementvalue<br>"; //shows exactly correct parameter and value
}
}
if ($stmt->execute() AND $stmt->rowCount() > 0){
echo "success";
//do some stuff
} else {
echo "false".' '. $stmt->rowCount() . '<br>';
$stmt->debugDumpParams();
}
}
catch(PDOException $e) {
echo $e->getMessage();
}
The resulting query using pdo always returns 0 rows although the parameters replaced with values manually result in success.
As i said above, i don't think that bindValue() method allows variables in parameter.
Any help would be appreciated.

All i had to do was to remove the quotes around the parameters in query so this is correct:
(column_x1 = :value_type1_index2)
and this is NOT correct:
(column_x1 = ':value_type1_index2')

Related

Using bound parameters from two select statements and use them in one insert

I'm trying to figure out how to take bound parameter values from two different select statements and insert them into a new table.
My first select gets an array without issue. My second select gets a count, as well as a number 180 divided by the count, but uses returned values from the first select in it's where clause.
This all works perfect.
Now, based on each of the 2nd select's execution I want to insert the values from each select into one new table.
I'm binding the values from each select individually, but how can I bind the values from both selects to execute my insert on them?
$selectPLC = "
SELECT
sku_id, s.frm as frm, sg.code as code, s.matrl as matrl, s.color as color, cst
FROM plc;
";
try {
$PLCcheck = $MysqlConn->prepare($selectPLC);
$detailRslt = $PLCcheck->execute();
while ($PLCRow = $PLCcheck->fetch(PDO::FETCH_ASSOC)) {
print_r($PLCRow); //This prints the first array I need
$salesValues = [
":cst" => $PLCRow["cst"],
":frm" => $PLCRow["frm"],
":matrl" => $PLCRow["matrl"],
":color" => $PLCRow["color"]
];
$checkSales = "
SELECT
count(*) as salesCount,
180/count(*) as countDIV
FROM orders
WHERE cstnoc = :cst
AND frmc = :frm
AND covr1c = :matrl
AND colr1c = :color
AND date(substr(dateField1,1,4)||'-'||substr(dateField1,5,2)||'-'||substr(dateField1,7,2) ) between current_Date - 180 DAY AND current_Date
";
try{
$salesCheck = $DB2Conn->prepare($checkSales);
$salesResult = $salesCheck->execute($salesValues);
while ($salesRow = $salesCheck->fetch(PDO::FETCH_ASSOC)) {
print_r($salesRow); //This prints the 2nd array I need
$countValues = [
":salesCount" => $salesRow["salesCount"],
":countDiv" => $salesRow["countDiv"]
];
$insertSales = "
INSERT INTO metrics (cst, frm, matrl, color, salesCount, countDIV )
VALUES (
:cst, //from first array
:frm, //from first array
:matrl, //from first array
:color, //from first array
:salesCount, //from 2nd array
:countDiv //from 2nd array
)
";
$salesInsertPrep = $DB2Conn->prepare($insertSales);
$salesInsertExec = $salesInsertPrep->execute($countValues);
}
}catch(PDOException $ex) {
echo "QUERY TWO FAILED!: " .$ex->getMessage();
}
}
}catch(PDOException $ex) {
echo "QUERY ONE FAILED!: " .$ex->getMessage();
}
When your fetching the second array set of values, rather than add them to a new array, you can add them into the array of the first values, so instead of...
$countValues = [
":salesCount" => $salesRow["salesCount"],
":countDiv" => $salesRow["countDiv"]
];
use
$salesValues[":salesCount"] = $salesRow["salesCount"];
$salesValues[":countDiv"] = $salesRow["countDiv"];
and then...
$salesInsertExec = $salesInsertPrep->execute($salesValues);
The other thing is that you can prepare your insert once outside the loop and then execute it each time in the loop, so this would look like...
$insertSales = "
INSERT INTO metrics (cst, frm, matrl, color, salesCount, countDIV )
VALUES (
:cst, //from first array
:frm, //from first array
:matrl, //from first array
:color, //from first array
:salesCount, //from 2nd array
:countDiv //from 2nd array
)
";
$salesInsertPrep = $DB2Conn->prepare($insertSales);
while ($salesRow = $salesCheck->fetch(PDO::FETCH_ASSOC)) {
print_r($salesRow); //This prints the 2nd array I need
$salesValues[":salesCount"] = $salesRow["salesCount"];
$salesValues[":countDiv"] = $salesRow["countDiv"];
$salesInsertExec = $salesInsertPrep->execute($salesValues);
}

How to get product IDs from a '^' separated list?

I have a '^' separated list of product ID numbers, and I need to get just the product ID number, and then use it to query a SQL database. The product ID numbers are stored in the $_SESSION hash. For example:
SKUS: jpn18726^gr172645^123746^17246^eu186726^...
The code I can think of is something like this:
$prodmat = $_SESSION["product"];
if(preg_match("(\d+)(^\s*\d+)*", $prodmat) {
$stmt = "select shipcode from materials where material='???'";
}
Basically, I want to extract the product ID numbers from the '^' separated list, and then use the product ID numbers to query the DB.
Just do some explosions:
$prod_list = 'SKUS: jpn18726^gr172645^123746^17246^eu186726';
$list_parts = explode(':', $prod_list); // separate the text
$prods = explode('^', trim($list_parts[1])); // trim and put the list in an array
print_r($prods);
Result:
Array
(
[0] => jpn18726
[1] => gr172645
[2] => 123746
[3] => 17246
[4] => eu186726
)
Now you can loop through the array with your query.
foreach($prods as $product) {
$sql = "SELECT foo, bar, WHERE products WHERE id = ?";
// bind the current product
// do the query
}
You should be performing just one query if possible. If you are using mysqli, you can use the following code block, though I'll recommend pdo because it is easier when dealing with a variable number of placeholders.
This code does NOTHING to validate the input data. It assumes that your SESSION data is 100% trustworthy and reliably formatted. If you need to validate, then you will want regex to do the validating. ...perhaps something like ~^SKUS: [a-z\d]+(?:\^[a-z\d]+)*$~ if your ids only contain numbers and letters.
Code:
if (!empty($_SESSION["product"])) {
// $_SESSION["product"] = 'SKUS: jpn18726^gr172645^123746^17246^eu186726';
// "SKUS: " is fixed/constant, so just remove it by known substring position/length
$params = explode('^', substr($_SESSION["product"],6)); // trim off leading substring BEFORE exploding
$count = count($params);
$csph = implode(',', array_fill(0, $count, '?')); // comma-separated placeholders
if(!$stmt = $conn->prepare("SELECT `shipcode` FROM `materials` WHERE `material` IN ($csph);")){
echo "Syntax Error # prepare: " , $conn->error; // do not echo error on public site
}else{
array_unshift($params, str_repeat('s', $count)); // prepend the type values string
$ref = []; // add references
foreach ($params as $i => $v) {
$ref[$i] = &$params[$i]; // pass by reference as required/advised by the manual
}
call_user_func_array([$stmt, 'bind_param'], $ref);
if (!$stmt->execute()) {
echo "Error # bind_param/execute: " , $stmt->error; // do not echo error on public site
} elseif (!$stmt->bind_result($shipcode)) {
echo "Error # bind_result: " , $stmt->error; // do not echo error on public site
} else {
while ($stmt->fetch()) {
// do something with $shipcode
}
$stmt->close();
}
}
} else {
echo "Missing/Invalid product data";
}
If you need to identify your shipcodes with the corresponding id, then just add the material column to the SELECT clause and the bind_result() call.
All that said, if you can confidently validate/sanitize your SESSION data, you can avoid the convolution of a prepared statement and just write your SELECT query with IN in the WHERE clause like: WHERE materials IN (' . implode("','", $params) . ').

PDO / mySQL How to bind multiple values to store multiple rows

To store 6 invoce items from a website through a form method="post", I use several input elements like this
<input name="item[]" value="6 hour service"/>
<input name="item[]" value="1 days travel"/>
....
Serverside I copy $_POST['Item'] to an array like this
item[]= $_POST['Item'];
Then I can access item[] and it looks like this
[item] => Array
(
[0] => 6 hour service
[1] => 1 days travel
[2] => ....
)
Then this SQL statement follows
try {
$obj = $this->dbcon->prepare('INSERT INTO invoice_item
( ID, item)
VALUES(:ID,:item)');
for ($i=0;$i<6;$i++) {
if (!empty($item[0][$i])) {
$obj->bindValue(':ID', $this->dbcon->lastInsertId(), PDO::PARAM_INT);
$obj->bindValue(':item', $item[0][$i], PDO::PARAM_STR);
$succ = $obj->execute();
}
}
}
catch(PDOException $e) {
echo "Error: " . $e->getMessage();
}
This works fine for a fix amount of items.
But what is the correct way to access $_POST, bind it and to store it when the amount of items are not known ?
EDIT :
How to iterate over $_POST["Item"] directly ?
Check if $i is less than the number of items rather than less than a fixed value.
for ($i=0;$i<count($item[0]);$i++) {
if (!empty($item[0][$i])) {
alternatively you could use foreach:
foreach ($item[0] as $value) {
if (!empty($value)) {
First of all, you don't need to necessarily bind the ID, because it become from the db itself. Then, you can use an unique query.
Get the ID:
$id = $this->dbcon->lastInsertId();
Filter $_POST['Item'] to discard empty values:
$items = array_values( array_filter( $_POST['Item'] ) );
Create a string with your query:
$query = substr( "INSERT INTO invoice_item (ID, item) VALUES " . str_repeat( "($id, ?),", count($items) ), 0, -1);
Assuming a $_POST['Items'] with three not-empty values and id = 3, now $query is:
INSERT INTO invoice_item (ID, item) VALUES (3, ?),(3, ?),(3, ?)
Now you can prepare the query and executing it binding all parameters at once (using an array as ->execute argument, parameters are bind as strings):
$obj = $this->dbcon->prepare( $query );
$obj->execute( $items );

Insert result from DOMXpath into MySQL

Im using this DOMXpath query to retrieve some columns from another page.
$html = file_get_contents("http://localhost:8888/stockPrices.php");
libxml_use_internal_errors(true);
$doc = new \DOMDocument();
if($doc->loadHTML($html))
{
$result = new \DOMDocument();
$result->formatOutput = true;
$table = $result->appendChild($result->createElement("table"));
$thead = $table->appendChild($result->createElement("thead"));
$tbody = $table->appendChild($result->createElement("tbody"));
$table->setAttribute('class', 'table table-hover');
$xpath = new \DOMXPath($doc);
$newRow = $thead->appendChild($result->createElement("tr"));
foreach($xpath->query("//table[#id='kurstabell']/thead/tr/th[position()=2 or position()=3 or position()=8 or position()=9 or position()=10]") as $header)
{
$newRow->appendChild($result->createElement("th", trim($header->nodeValue)));
}
foreach($xpath->query("//table[#id='kurstabell']/tbody/tr") as $row)
{
$newRow = $tbody->appendChild($result->createElement("tr"));
foreach($xpath->query("./td[position()=2 or position()=3 or position()=8 or position()=9 or position()=10]", $row) as $cell)
{
$newRow->appendChild($result->createElement("td", trim(htmlentities($cell->nodeValue))));
}
}
echo $result->saveXML($result->documentElement);
}
This generates four columns, aktier, senaste, högst, lägst and omsatt. But i dont know how to insert this to a MySQL table. Im thinking to first generate a array of the result, like:
Array
(
[1] => stdClass Object
(
[aktie] => AAK AB
[senaste] => 634,50
[högst] => 638,50
[lägst] => 622,50
[omsatt] => 32 094 048
)
[2] => stdClass Object
(
[aktie] => ABB Ltd
[senaste] => 162,80
[högst] => 163,30
[lägst] => 161,90
[omsatt] => 167 481 268
)
(you get the hang of it..)
)
According to this image:
And then loop the array into the table. Something like this?
$sql = "INSERT INTO stock_list (`aktie`, `senaste`, `högst`, `lägst`, `omsatt`, `timestamp`) VALUES
(:aktie, :senaste, :högst, :lägst, :omsatt)";
$query = $database->prepare($sql);
foreach($data as $stock){
$query->execute(array(':aktie' => $stock->stock,
':senaste' => $stock->prevclose,
':högst' => $stock->high,
':lägst' => $stock->low,
':omsatt' => $stock->volume
));
}
My question:
How do i populate the array with data?
How do i loop the result in a mysql query?
Don't know if this is a work around. But it is currently doing what I'm asking for.
// build query...
$sql = "INSERT INTO stocks";
// columns to insert into...
$sql .="(`name`, `closing`, `high`, `low`, `turn`, `timestamp`)";
// implode values of $array...
// notice array_chunk, this functions splits a big array into multi.
$str = NULL;
foreach (array_chunk($a, 5) as $row) {
$str .= '("'. implode('","',$row).'",NOW()),';
}
// Remove last ',' (comma) from string
// We added commas in the previous step
$str = rtrim($str,',');
$sql .= 'VALUES '. $str ;
// execute query...
$app = new Connection();
$query = $app->getConnection()->prepare($sql);
$query->execute();
if ($query->rowCount() <= 0) {
echo "Something went wrong.";
return false;
}
return true;
My guess is that what you really want is something along the lines of:
$query = 'INSERT INTO stock_list
(`aktie`, `senaste`, `högst`, `lägst`, `omsatt`, `timestamp`)
VALUES
(:aktie, :senaste, :högst, :lägst, :omsatt, NOW())';
$stmt = $app->getConnection()->prepare($query);
foreach ($data as $stock) {
$stmt->execute(
[
':aktie' => $stock->aktie,
':senaste' => $stock->senaste,
':högst' => $stock->{'högst'},
':lägst' => $stock->{'lägst'},
':omsatt' => $stock->omsatt,
]
);
$stmt->closeCursor();//might be required depending on DB driver, not for MySQL, though
}
Note that I call NOW() in the query string, and I don't bind that SQL function call to the parameters I execute the prepared statement with. All in all though, a timestamp field is best set by the DB itself (with a DEFAULT CURRENT_TIMESTAMP in the field definition). Then you can just leave the timestamp field out of your INSERT query, and it'll be set correctly for you.
I've also changed the way you're using the objects stock. From the var_dump I can see the properties aren't called stock, high, low and all that. The problem is, some of these property names (lägst for example) are a bit dodgy. You'll probably have to access those using a string, which can be done, like I did, by writing $objVar->{'property name as string'}.
If I were you, though, I'd look into ways of changing what $data actually looks like, and change the property names if at all possible.

Problem with 2 simultaneous connection with PDO/Mysql

Here is my simplified code:
$connexion = new PDO(SQL_DSN,SQL_USERNAME,SQL_PASSWORD);
$connexion2 = new PDO(SQL_DSN2,SQL_USERNAME2,SQL_PASSWORD2);
[...]
$sqlIndex = "SELECT index_key,index_platforms_code
FROM index
WHERE index_missions_id = :mission";
$initFiches = $connexion->prepare($sqlIndex);
$initFiches->bindParam(":mission" , $_GET['mission']);
$initFiches->execute();
try
{
while ($fiche = $initFiches->fetch(PDO::FETCH_ASSOC))
{
print_r($fiche);
foreach ($structure['champs'] as $masterChamp)
{
//$stmt = $connexion2->prepare($masterChamp['sql']);
}
}
}
catch (exception $e)
{
echo "error".$e->getMessage();
}
My output:
Array
(
[index_key] => 1
[index_platforms_code] => 1
)
Array
(
[index_key] => 2
[index_platforms_code] => 2
)
Array
(
[index_key] => 3
[index_platforms_code] => 3
)
Array
(
[index_key] => 4
[index_platforms_code] => 4
)
All Right, but if I uncomment this line
$stmt = $connexion2->prepare($masterChamp['sql']);
in the foreach, this line broke the while above and here is the new output:
Array
(
[index_key] => 1
[index_platforms_code] => 1
)
Someone have an idea?
Here's a solution that doesn't involve 2 connections and that's more optimized.
$connexion = new PDO(SQL_DSN,SQL_USERNAME,SQL_PASSWORD);
$sqlIndex = "SELECT index_key,index_platforms_code
FROM index
WHERE index_missions_id = :mission";
$initFiches = $connexion->prepare($sqlIndex);
$initFiches->bindParam(":mission" , $_GET['mission'], PDO::PARAM_STR);
$initFiches->execute();
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt = $connexion->prepare("SELECT ...."); // Prepare outside of the loop, you don't have to prepare it X times, once is enough.
if(sizeof($data))
{
foreach($data as $row)
{
// Do your statement binding / executing.
}
}
Alternatively, it seems you might be able to do this with table joining rather than issuing a query for each row you get from the 1st table. Since you haven't posted the structure, I guess there's no much helping it at the moment.

Categories