Im current creating my own query builder now and Im stuck with PDO's prepared statement. Isn't it possible to loop the the PDO's BindParam. I did it using foreach() but it's not working it only on works on the last data that the loop executed.
$sql = "SELECT * FROM users WHERE id = :a OR fname = :b";
$array = array(":a"=>"10002345", "Josh");
$stmt = $conn->prepare($sql);
foreach($array as $key => $value ) {
$stmt->bindParam($key, $value);
}
$stmt->execute();
it only binds the last data executed by the loop.
It is better to use ? placeholders in a query and pass array of data to execute:
$sql = "SELECT * FROM users WHERE id = ? OR fname = ?";
$array = array("10002345", "Josh"); // you don't even need keys here
$stmt = $conn->prepare($sql);
$stmt->execute($array);
Only just stumbled across this, but just for future reference...
Firstly, I'll work on the assumption that your example was supposed to read $array = array(":a"=>"10002345", ":b"=>"Josh");, as there would be an issue even if your :b key was absent.
In this bit:
foreach($array as $key => $value ) {
$stmt->bindParam($key, $value);
}
You haven't 'passed by reference'. The $value should be amended to &$value
foreach($array as $key => &$value ) {
$stmt->bindParam($key, $value);
}
This is because the bindParam method signature requires the value to be a variable reference:
public function bindParam ($parameter, &$variable, $data_type = PDO::PARAM_STR, $length = null, $driver_options = null) {}
(note the & before $variable).
The end result of your original query (sans &) is that all :params would be set to the value that is in the last iteration of $value in your original loop.
So, the result of
$sql = "SELECT * FROM users WHERE id = :a OR fname = :b";
$array = array(":a"=>"10002345", ":b"=>"Josh");
$stmt = $conn->prepare($sql);
foreach($array as $key => $value ) {
$stmt->bindParam($key, $value);
}
$stmt->execute();
Would be SELECT * FROM users WHERE id = 'Josh' OR fname = 'Josh'
Using named parameters (:param) has advantages over positional params (?), so it's worth reserving that option for prepared statements, as opposed to the accepted answer of "it's better to use ? placeholders", which is not the case.
In my database abstraction layer I use the following utility functions:
/**
* getFieldList return the list with or without PK column
* #param bool $withID - true when including parameter
*/
static protected function getFieldList( $withID = false )
{
if( $withID )
$result = '`' . static::getTableName( ) . '`' .
'.`' . static::getPrimaryKeyName( ) . '`, ';
else
$result = '';
return $result .= '`' . static::getTableName( ) . '`.' .
'`' . implode( '`, `'.static::getTableName( ) . '`.`', static::getFieldNames( ) ) . '`';
}
/**
* getFieldPlaceholders -
* #return string - all PDO place holders prefixed :
*/
static protected function getFieldPlacholders( )
{
return ':' . implode( ',:', static::getFieldNames( ) );
}
/**
* getUpdateList - SQL updates section
* #return string
*/
static private function getUpdateList( )
{
$result = array( );
foreach( static::getFieldNames( ) as $field ) {
if( $field === static::getPrimaryKeyName() ) continue;
$result[] = '`' . $field . '`=:' . $field;
}
return implode( ',', $result );
}
/**
* Bind the fields to PDO placeholdes
* #param PDOStatement $stmt statement that the fields are bound to
* #return void
*/
protected function bindFields( $stmt )
{
foreach( array_keys($this->fields) as $field ) {
if( $field === static::getPrimaryKeyName() ) continue;
$stmt->bindParam( ':' . $field, $this->fields[$field] );
// echo $field . '->' . $this->fields[$field] . '<br>';
}
}
/**
* Bind the fields to the placeholders
* #param PDOStatement $stmt - that the fields are bind to
* #return void
*/
protected function bindColumns( $stmt, $withID = false )
{
if( $withID )
$stmt->bindColumn( static::getPrimaryKeyName(), $this->ID );
foreach( static::getFieldNames() as $fieldname )
{
$stmt->bindColumn( $fieldname, $this->fields[$fieldname] );
}
}
/**
* parseResultset
* Set the values of the select results, resets dirty (object is in sync)
* #param mixed[] $result - associative array
*/
protected function parseResultset( $result )
{
foreach( $result as $field=> $value ) {
if( $field === static::getPrimaryKeyName() )
$this->ID = $value;
$this->fields[$field] = $value;
}
$this->dirty = array();
}
Related
I can get the not-bind query on with this way :
\DB::enableQueryLog();
$items = OrderItem::where('name', '=', 'test')->get();
$log = \DB::getQueryLog();
print_r($log);
Output is :
(
[0] => Array
(
[query] => select * from "order_items" where "order_items"."name" = ? and "order_items"."deleted_at" is null
[bindings] => Array
(
[0] => test
)
[time] => 0.07
)
)
But what I really need is bind query like this :
select * from "order_items" where "order_items"."name" = 'test' and "order_items"."deleted_at" is null
I know I can do this with raw PHP but is there any solution in laravel core?
Actually I've created one function within helpers.php for same. You can also use same function within your helpers.php file
if (! function_exists('ql'))
{
/**
* Get Query Log
*
* #return array of queries
*/
function ql()
{
$log = \DB::getQueryLog();
$pdo = \DB::connection()->getPdo();
foreach ($log as &$l)
{
$bindings = $l['bindings'];
if (!empty($bindings))
{
foreach ($bindings as $key => $binding)
{
// This regex matches placeholders only, not the question marks,
// nested in quotes, while we iterate through the bindings
// and substitute placeholders by suitable values.
$regex = is_numeric($key)
? "/\?(?=(?:[^'\\\']*'[^'\\\']*')*[^'\\\']*$)/"
: "/:{$key}(?=(?:[^'\\\']*'[^'\\\']*')*[^'\\\']*$)/";
$l['query'] = preg_replace($regex, $pdo->quote($binding), $l['query'], 1);
}
}
}
return $log;
}
}
if (! function_exists('qldd'))
{
/**
* Get Query Log then Dump and Die
*
* #return array of queries
*/
function qldd()
{
dd(ql());
}
}
if (! function_exists('qld'))
{
/**
* Get Query Log then Dump
*
* #return array of queries
*/
function qld()
{
dump(ql());
}
}
Simply place these three functions within your helpers.php file and you can use same as follows:
$items = OrderItem::where('name', '=', 'test')->get();
qldd(); //for dump and die
or you can use
qld(); // for dump only
Here I extended the answer of #blaz
In app\Providers\AppServiceProvider.php
Add this on boot() method
if (env('APP_DEBUG')) {
DB::listen(function($query) {
File::append(
storage_path('/logs/query.log'),
self::queryLog($query->sql, $query->bindings) . "\n\n"
);
});
}
and also added a private method
private function queryLog($sql, $binds)
{
$result = "";
$sql_chunks = explode('?', $sql);
foreach ($sql_chunks as $key => $sql_chunk) {
if (isset($binds[$key])) {
$result .= $sql_chunk . '"' . $binds[$key] . '"';
}
}
$result .= $sql_chunks[count($sql_chunks) -1];
return $result;
}
Yeah, you're right :/
This is a highly requested feature, and i have no idea why its not a part of the framework yet...
This is not the most elegant solution, but you can do something like this:
function getPureSql($sql, $binds) {
$result = "";
$sql_chunks = explode('?', $sql);
foreach ($sql_chunks as $key => $sql_chunk) {
if (isset($binds[$key])) {
$result .= $sql_chunk . '"' . $binds[$key] . '"';
}
}
return $result;
}
$query = OrderItem::where('name', '=', 'test');
$pure_sql_query = getPureSql($query->toSql(), $query->getBindings());
// Or like this:
$data = OrderItem::where('name', '=', 'test')->get();
$log = DB::getQueryLog();
$log = end($log);
$pure_sql_query = getPureSql($log['query'], $log['bindings']);
You can do that with:
OrderItem::where('name', '=', 'test')->toSql();
I have a configuration table in a database, using CI3.
Each row has a unique ID, as well as a type_name and type_desc.
So as each row has a unique ID, how can I update the entire table using one update_batch query?
I obviously don't want to run a query on each row, so I'm guessing i need to build an array with the values, but how can do this with the where clause somehow within the array?! Confused.
I basically need to do this:
UPDATE check_types
SET type_name = POSTED_TYPE_NAME, type_desc = POSTED_TYPE_DESC
WHERE check_type_id = ???? POSTED_ID?????
The only way I know of to do this is to extend the db driver.
In the core folder create file with name MY_DB_mysqli_driver.php. file name assumes that the subclass_prefix defined in config.php is MY_
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class MY_DB_mysqli_driver extends CI_DB_mysqli_driver
{
final public function __construct($params)
{
parent::__construct($params);
}
/**
* Insert_On_Duplicate_Key_Update_Batch
*
* Compiles batch insert strings and runs the queries
*
* #param string $table Table to insert into
* #param array $set An associative array of insert values
* #param bool $escape Whether to escape values and identifiers
* #return int Number of rows inserted or FALSE on failure
*/
public function insert_on_duplicate_update_batch($table = '', $set = NULL, $escape = NULL)
{
if ($set !== NULL)
{
$this->set_insert_batch($set, '', $escape);
}
if (count($this->qb_set) === 0)
{
// No valid data array. Folds in cases where keys and values did not match up
return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE;
}
if ($table === '')
{
if (!isset($this->qb_from[0]))
{
return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE;
}
$table = $this->qb_from[0];
}
// Batch this baby
$affected_rows = 0;
for ($i = 0, $total = count($this->qb_set); $i < $total; $i += 100)
{
$this->query($this->_insert_on_duplicate_key_update_batch($this->protect_identifiers($table, TRUE, $escape, FALSE), $this->qb_keys, array_slice($this->qb_set, $i, 100)));
$affected_rows += $this->affected_rows();
}
$this->_reset_write();
return $affected_rows;
}
/**
* Insert on duplicate key update batch statement
*
* Generates a platform-specific insert string from the supplied data
*
* #param string $table Table name
* #param array $keys INSERT keys
* #param array $values INSERT values
* #return string
*/
private function _insert_on_duplicate_key_update_batch($table, $keys, $values)
{
foreach ($keys as $num => $key)
{
$update_fields[] = $key . '= VALUES(' . $key . ')';
}
return "INSERT INTO " . $table . " (" . implode(', ', $keys) . ") VALUES " . implode(', ', $values) . " ON DUPLICATE KEY UPDATE " . implode(', ', $update_fields);
}
}
You would use it like this:
$sql_array = array();
foreach ($data as $row)
{
$arr = array(
'check_type_id' => $row['check_type_id'],
'type_name' => $row['type_name'],
'type_desc' => $row['type_desc']
);
$sql_array[] = $arr;
}
$this->db->insert_on_duplicate_update_batch('check_types', $sql_array);
I just started creating mysqli bind_param update function. My insert function works fine, but there i get error - Warning: mysqli_stmt::bind_param(): Number of elements in type definition string doesn't match number of bind variables. I dont get where is the problem.
My array = Array ( [bday_month] => 9 [bday_day] => 7 [bday_year] => 2003 [id] => 2 )
public function update($table, $data) {
if (empty($table) || empty($data)) {
return false;
}
$array_slice = array_slice($data, 0, count($data)-1);
$fields = implode(' = ?, ', array_keys($array_slice)) . ' = ?';
$stmt = $this->db->prepare("UPDATE `{$table}` SET {$fields} WHERE `id` = ?");
call_user_func_array(array($stmt, 'bind_param'), $this->refValues($data));
$stmt->execute();
}
Solved. I forget insert types.
i have this one worked for me you could see it
/**
* update
* #author Alaa M. Jaddou
* #param string $table A name of table to insert into
* #param string $data An associative array
* #param string $where the WHERE query part
*/
public function update($table, $data, $where)
{
ksort($data);
$fieldDetails = NULL;
foreach($data as $key=> $value) {
$fieldDetails .= "`$key`= ?,";
}
$fieldDetails = rtrim($fieldDetails, ',');
$sth = $this->prepare("UPDATE $table SET $fieldDetails WHERE $where");
$values = array();
foreach ($data as $key => $value) {
$values = implode(', ', $value);
}
$sth->bind_param($values);
return $sth->execute();
}
Is using an object to build up SQL Statements overkill?
Is defining a bare String enough for a SQL statement?
This is my PHP and SQL code:
class SQLStatement {
private $table;
private $sql;
const INNER_JOIN = 'INNER JOIN';
const LEFT_JOIN = 'LEFT JOIN';
const RIGHT_JOIN = 'RIGHT JOIN';
const OUTER_JOIN = 'OUTER JOIN';
public function __construct($table) {
$this->setTable($table);
}
public function setTable($table) {
$this->table = $table;
}
public function buildFromString($string) {
$this->sql = $sql;
}
public function select(array $columns) {
$sql = 'SELECT ';
$columns = implode(',', $columns);
$sql .= $columns;
$sql .= " FROM $this->table";
$this->sql = $sql;
return $this;
}
/**
* Setting up INSERT sql statement
*
* #param array $records The records to insert.The array keys must be the columns, and the array values must be the new values
* #return object Return this object back for method chaining
*/
public function insert(array $records) {
$columns = array_keys($records);
$values = array_values($records);
$values = array_map('quote',$values);
$sql = 'INSERT INTO ';
$sql .= $this->table . '('. implode(',', $columns) . ')';
$sql .= ' VALUES ' . '(' . implode(',', $values) . ')';
$this->sql = $sql;
return $this;
}
/**
* Setting up UPDATE sql statement
*
* #param array $records The records to update. The array keys must be the columns, and the array values must be the new records
* #return object Return this object back for method chaining
*/
public function update(array $records, $where) {
$sql = 'UPDATE ' . $this->table . ' SET ';
$data = array();
foreach ($records as $column => $record) {
$data[] = $column . '=' . quote($record);
}
$sql .= implode(', ', $data);
$this->sql = $sql;
return $this;
}
/**
* Setting up DELETE sql statement
* #return object Return this object back for method chaining
*/
public function delete($where=null) {
$sql = 'DELETE FROM ' . $this->table;
$this->sql = $sql;
if (isset($where)) {
$this->where($where);
}
return $this;
}
/**
* Setting up WHERE clause with equality condition. (Currently only support AND logic)
* #param array $equalityExpression Conditional equality expression. The key is the column, while the value is the conditional values
* #return object Return this object back for method chaining
*
*/
public function where(array $equalityExpression) {
if (!isset($this->sql)) {
throw new BadMethodCallException('You must use SELECT, INSERT, UPDATE, or DELETE first before where clause');
}
$where = ' WHERE ';
$conditions = array();
foreach ($equalityExpression as $column => $value) {
if (is_array($value)) {
$value = array_map('quote', $value);
$conditions[] = "$column IN (" . implode(',', $value) . ')';
}
else {
$value = quote($value);
$conditions[] = "$column = $value";
}
}
$where .= implode(' AND ', $conditions);
$this->sql .= $where;
return $this;
}
/**
* Setting up WHERE clause with expression (Currently only support AND logic)
* #param array $expression Conditional expression. The key is the operator, the value is array with key being the column and the value being the conditional value
* #return object Return this object back for method chaining
*/
public function advancedWhere(array $expression) {
if (!isset($this->sql)) {
throw new BadMethodCallException('You must use SELECT, INSERT, UPDATE, or DELETE first before where clause');
}
if (!is_array(reset($expression))) {
throw new InvalidArgumentException('Invalid format of expression');
}
$where = ' WHERE ';
$conditions = array();
foreach ($expression as $operator => $record) {
foreach ($record as $column => $value) {
$conditions[] = $column . ' ' . $operator . ' ' . quote($value);
}
}
$where .= implode(' AND ', $conditions);
$this->sql .= $where;
return $this;
}
/**
* Setting up join clause (INNER JOIN, LEFT JOIN, RIGHT JOIN, or OUTER JOIN)
* #param array $tables <p>Tables to join as the key and the conditions array as the value (Currently only support ON logic)</p>
* <p>Array example : array('post'=>array('postid'=>'post.id'))</p>
* #param string $mode The mode of join. It can be INNER JOIN, LEFT JOIN, RIGHT JOIN, or OUTER JOIN
* #return object Return this object back for method chaining
*/
public function join(array $joins, $mode = self::INNER_JOIN) {
if (!isset($this->sql) && strpos($this->sql, 'SELECT')) {
throw new BadMethodCallException('You must have SELECT clause before joining another table');
}
if (!is_array(reset($joins))) {
throw new InvalidArgumentException('Invalid format of the join array.');
}
$Conditions = array();
foreach ($joins as $table => $conditions) {
$join = ' ' . $mode . ' ' . $table . ' ON ';
foreach ($conditions as $tbl1 => $tbl2) {
$Conditions[] = $tbl1 . ' = ' . $tbl2;
}
$join .= implode(' AND ', $Conditions);
}
$this->sql .= $join;
return $this;
}
/**
* Setting up GROUP BY clause
* #param array $columns The columns you want to group by
* #param string $sort The type of sort, ascending is the default
* #return object Return this object back for method chaining
*/
public function groupBy(array $columns, $sort = 'ASC') {
if (!isset($this->sql)) {
throw new BadMethodCallException('You must use SELECT, INSERT, UPDATE, or DELETE first before group by clause');
}
$groupBy = ' GROUP BY ' . implode(',', $columns) . ' ' . $sort;
$this->sql .= $groupBy;
return $this;
}
/**
* Setting up HAVING clause with expression
* #param expression $expression Conditional expression. The key is the operator, the value is an array with key being the column and the value being the conditional value
* #return object Return this object back for method chaining
*/
public function having($expression) {
if (!isset($this->sql) && strpos($this->sql, 'GROUP BY') === FALSE) {
throw new BadMethodCallException('You must have SELECT, INSERT, UPDATE, or DELETE and have GROUP BY clause first before where clause');
}
if (!is_array(reset($expression))) {
throw new InvalidArgumentException('Invalid format of expression');
}
$having = ' HAVING ';
$conditions = array();
foreach ($expression as $operator => $record) {
foreach ($record as $column => $value) {
$conditions[] = $column . ' ' . $operator . ' ' . $value;
}
}
$having .= implode(' AND ', $conditions);
$this->sql .= $having;
return $this;
}
/**
* Return the SQL statement if this object is supposed to be string
* #return string The sql statement
*/
public function __toString() {
return $this->sql;
}
}
The disadvantage I found is when I need to use prepared statement since prepared statements contains placeholders.
Should I add a feature to my SQL Statement Object to do prepared statements? When should using prepared statements good practice?
It's not overkill or overengineering to use objects or prepared statements, they are good practice.
The goal is to make something versatile and re-usable, and it looks like you are heading on the right track.
However, many people have done this already, and you may be better off using an existing solution.
Some of the top ones are:
Doctrine
Propel
and I used to personally use:
Idiorm
And if I am not mistaken, all three are built on PDO, and use prepared statements.
If you wanted to make your own solution for this, PDO and prepared statements are a very good idea, if not a must.
Is there any way to accomplish the following in Wordpress with $wpdb->insert or
$wpdb->query($wpdb->prepare)):
INSERT into TABLE (column1, column2, column3)
VALUES
('value1', 'value2', 'value3'),
('otherval1', 'otherval2', 'otherval3'),
('anotherval1', 'anotherval2', 'anotherval3')
...etc
OK, I figured it out!
Setup arrays for Actual Values, and Placeholders
$values = array();
$place_holders = array();
the initial Query:
$query = "INSERT INTO orders (order_id, product_id, quantity) VALUES ";
Then loop through the the values you're looking to add, and insert them in the appropriate arrays:
foreach ( $_POST as $key => $value ) {
array_push( $values, $value, $order_id );
$place_holders[] = "('%d', '%d')" /* In my case, i know they will always be integers */
}
Then add these bits to the initial query:
$query .= implode( ', ', $place_holders );
$wpdb->query( $wpdb->prepare( "$query ", $values ) );
You can also use this way to build the query:
$values = array();
// We're preparing each DB item on it's own. Makes the code cleaner.
foreach ( $items as $key => $value ) {
$values[] = $wpdb->prepare( "(%d,%d)", $key, $value );
}
$query = "INSERT INTO orders (order_id, product_id, quantity) VALUES ";
$query .= implode( ",\n", $values );
I have came across with this problem and decided to build a more improved function by using accepted answer too:
/**
* A method for inserting multiple rows into the specified table
*
* Usage Example:
*
* $insert_arrays = array();
* foreach($assets as $asset) {
*
* $insert_arrays[] = array(
* 'type' => "multiple_row_insert",
* 'status' => 1,
* 'name'=>$asset,
* 'added_date' => current_time( 'mysql' ),
* 'last_update' => current_time( 'mysql' ));
*
* }
*
* wp_insert_rows($insert_arrays);
*
*
* #param array $row_arrays
* #param string $wp_table_name
* #return false|int
*
* #author Ugur Mirza ZEYREK
* #source http://stackoverflow.com/a/12374838/1194797
*/
function wp_insert_rows($row_arrays = array(), $wp_table_name) {
global $wpdb;
$wp_table_name = esc_sql($wp_table_name);
// Setup arrays for Actual Values, and Placeholders
$values = array();
$place_holders = array();
$query = "";
$query_columns = "";
$query .= "INSERT INTO {$wp_table_name} (";
foreach($row_arrays as $count => $row_array)
{
foreach($row_array as $key => $value) {
if($count == 0) {
if($query_columns) {
$query_columns .= ",".$key."";
} else {
$query_columns .= "".$key."";
}
}
$values[] = $value;
if(is_numeric($value)) {
if(isset($place_holders[$count])) {
$place_holders[$count] .= ", '%d'";
} else {
$place_holders[$count] .= "( '%d'";
}
} else {
if(isset($place_holders[$count])) {
$place_holders[$count] .= ", '%s'";
} else {
$place_holders[$count] .= "( '%s'";
}
}
}
// mind closing the GAP
$place_holders[$count] .= ")";
}
$query .= " $query_columns ) VALUES ";
$query .= implode(', ', $place_holders);
if($wpdb->query($wpdb->prepare($query, $values))){
return true;
} else {
return false;
}
}
Source: https://github.com/mirzazeyrek/wp-multiple-insert
In addition to inserting multiple rows using $wpdb, if you ever need to update existing rows following snippet should be helpful.
This is updated snippet of what #philipp provided above.
$values = array();
// We're preparing each DB item on it's own. Makes the code cleaner.
foreach ( $items as $key => $value ) {
$values[] = $wpdb->prepare( "(%d,%d)", $key, $value );
}
$values = implode( ",\n", $values );
$query = "INSERT INTO orders (order_id, product_id, quantity) VALUES {$values} ON DUPLICATE KEY UPDATE `quantity` = VALUES(quantity)";
This is a bit late, but you could also do it like this.
global $wpdb;
$table_name = $wpdb->prefix . 'your_table';
foreach ($your_array as $key => $value) {
$result = $wpdb->insert(
$table_name,
array(
'colname_1' => $value[0],
'colname_2' => $value[1],
'colname_3' => $value[2],
)
);
}
if (!$result) {
print 'There was a error';
}
not very nice, but if you know what you are doing:
require_once('wp-load.php');
mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
#mysql_select_db(DB_NAME) or die();
mysql_query("INSERT into TABLE ('column1', 'column2', 'column3') VALUES
('value1', 'value2', 'value3'),
('otherval1', 'otherval2', 'otherval3'),
('anotherval1', 'anotherval2', 'anotherval3)");