Codigniter update every row in table with different ID - php

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);

Related

Search within array or compare values

To improve my php experiences I have written a small movie databse with the TMDB API.
Now I have data from the API and data from own codings. I have a database movieSeen where people can save movies they have already seen.
global $pdo;
$stmt = $pdo->prepare("SELECT * FROM movieSeen WHERE user_id = '" . $_SESSION['id'] . "'");
$stmt->execute();
foreach ($stmt as $row ) {
echo $row['movie_id'] . ', ';
} // outputs the IDs of the movies already seen. here: 157, 189, 298, 456
Now I have a API Call where I load all movies from a specific director.
$url = "https://api.themoviedb.org/3/person/" . $personID . "/movie_credits?api_key=" . $apiKey . "&" . $language . "";// path to your JSON file
$data = file_get_contents($url); // put the contents of the file into a variable
$personCareer = json_decode($data); // decode the JSON feed
foreach ($personCareer->crew as $showCrewDetails) { //output of the movies of a specific director
echo '<tr>';
echo '<td>' . $showCrewDetails->title . ' ' . $showCrewDetails->id . ' (' . substr($showCrewDetails->release_date, 0, 4) . ')</td>';
echo '<td><span class="badge badge-danger">Movie Seen</span></td>';
echo '<td>' . $showCrewDetails->job . '</td>';
echo '</tr>';
}
Every movie which has alredy be seen should be checked as seen in this list. So I have to check if the IDs from my database are included in the API call.
I think I have to use something like in_array or array_search to see if $showCrewDetails->id is equal to $row['movie_id'].
But I don't know how to exactly do it. I am thankful for every hint.
I would do something like this:
/**
* #param int $user_id DB user ID
* #param PDO $pdo
* #return int[] User movie IDs
*/
function getUserSeenMovieIds(int $user_id, PDO $pdo) : array {
$stmt = $pdo->prepare('SELECT movie_id FROM movieSeen WHERE user_id = :user_id');
$stmt->execute([':user_id' => $user_id]);
return $stmt->fetchAll(PDO::FETCH_COLUMN, 0) ?: [];
}
/**
* #return array Response from api.themoviedb.org/3/person/{int}/movie_credits
*/
function getMovieCredits(int $personID, string $apiKey, string $language) : array {
$query = http_build_query([
'api_key' => $apiKey,
'language' => $language,
]);
$url = 'https://api.themoviedb.org/3/person/' . $personID . '/movie_credits?' . $query;
$data = file_get_contents($url);
$credits = json_decode($data, true);
if (!is_array($credits)) {
throw new \RuntimeException("Failed decoding response $data from URL $url");
}
return $credits;
}
/**
* #param array $credits Response from api.themoviedb.org/3/person/{int}/movie_credits
* #param int[] $movie_ids
* #return array Crew details from the response filtered by the movie IDs
*/
function getMovieCrewDetails(array $credits, array $movie_ids) : array
{
$reference = array_flip($movie_ids);
$credits = [];
foreach ($credits['crew'] as $crew) {
$id = $crew['id'];
if (isset($reference[$id])) {
$credits[] = $crew;
}
}
return $credits;
}
////////////////////////////////////////////////////////////////////////////////
$movie_ids = getUserSeenMovieIds($user_id, $pdo);
$credits = getMovieCredits($personID, $apiKey, $language);
foreach (getMovieCrewDetails($credits, $movie_ids) as $crew) {
// TODO: Print $crew
}
The code above avoids nested loop (which would result in O(n*m) complexity) in getMovieCrewDetails by using a hash table $reference. Hash tables are O(1) average case complexity, so using them for moderate data sets is generally a good idea.
There is a number of other things to note:
do use PDO placeholders, since they free you from the need to escape the input;
do not use global, since you can easily avoid pollution of the global name space by using functions, classes and techniques like dependency injection;
always check for the values functions return; otherwise, you are risking to catch an unexpected runtime error or bug (I've put some error checking into the code, but you should really check every function call.)
if you have $showCrewDetails->id is multiple value.
use foreach
foreach ($showCrewDetails as $key => $val) {
$id_fromapi = $val->id;
$stmt = $pdo->prepare("SELECT *FROM movieSeen WHERE user_id ='".$id_fromapi ."'");
$stmt->execute();
}

HOW TO LOOP PHP'S PDO BIND PARAM

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();
}

Laravel - how to get the query with bind parameters?

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();

Mysqli bind param update

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();
}

PHP, Is using an object to build an SQL statement a bad practice?

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.

Categories