I have the following code,
This is where i process the textfield input type wherein i type in the ingredient of an recipe.
The proper format of how one should input the ingredient is like this.
5-ml milk, 4-ml water, 2-pcs carrot
There are always delimiter in between since i have different columns for quantity, unit of measurement and the name.
I have no problem with the separation. I have another table. Which is named ingredients.
This is where i populate different ingredients like milk and put in what food group they belong and their nutritional info(what it gives off, like calcium).
Anyhow, I get a database error, Somehow, when i try to insert after all that processing, both the type and nutrition are null. I've thought about it, maybe my query was wrong? but it didnt give off any warning or notice. Though it only gave me notice/warning that both the values are undefined when i tried to declare them at array $comp which are the values to be inserted.
I get this error,
Error Number: 1048
Column 'nutrition' cannot be null
INSERT INTO component (componentname, quantity, unit,
nutrition, type) VALUES ('milk', '5', 'ml', NULL, NULL)
Filename: C:\www\KG\system\database\DB_driver.php
Line Number: 330
The code:
function insertrecipe()
{
$ingredients = $this->input->post('ingredients');
$recipename = $this->input->post('recipename');
$recipe = array(
'recipename' => $recipename,
'description' => $this->input->post('description'),
'instruction' => $this->input->post('instructions'),
'serving' => $this->input->post('serving'),
);
$this->db->insert('recipe', $recipe);
$this->ingredient($ingredients,$recipename);
}
function ingredient($ingredients,$recipename)
{
$ids = array();
$first_slice = explode(',',$ingredients);
$secondslice = $this->second_slice($first_slice);
foreach($secondslice as $qty => $iname)
{
$arr = explode('-',$qty);
$third_slice[$arr[1]] = $arr[0];
$sql = $this->db
->select('nutrition,group')
->where('ingname', $iname)
->from('ingredients')
->get()
->result_array();
$result = $this->db->query($sql);
foreach($result->result_array() as $row)
{
$ingredient_type = $this->get_ingredient_type($row['group']);
}
foreach($third_slice as $measure => $num)
{
$comp = array(
'componentname' => $iname,
'quantity' => $num,
'unit' => $measure,
'nutrition' => $nutri,
'type' => $ingredient_type
);
if($insert2 = $this->db->insert('component',$comp))
{
$latest_id = $this->db->insert_id();
$ids[] = $latest_id;
}
}
}
}
function second_slice($data)
{
foreach($data as $key => $value)
{
$ingredient = explode(' ', trim($value));
$second_slice[$ingredient[0]] = $ingredient[1];
}
return $second_slice;
}
function get_ingredient_type($data)
{
//foreach($data->result_array() as $row)
//{
if($data['group'] == "vegetable")
{
$type = 1;
}
else if($data['group'] == "fruit")
{
$type = 2;
}
else if($data['group'] == "dairy")
{
$type = 3;
}
else if($data['group'] == "seafood")
{
$type = 4;
}
else
{
$type = 5;
}
//}
return $type;
}
The database table of component has the following columns.
componentid componentname quantity unit nutrition type
nutrition is varchar unit is int. I guess they're not null or cols that dont accept null values.
I've separated different foreach loops into functions. I originally thought the error was because i had 3-5 for each loop within that function alone. so i decided to separate them into functions.
Related
public function add_employee($input)
{
$key_array = null;
$value_array = null;
$bind_array = null;
foreach ($input as $column => $value) {
if ($value) {
#$bind_array => ?, ?, ?;
#$value_array => [$value1, $value2, $value3];
#$key_array => column1, column2, column3;
}
}
$sql = "INSERT INTO ol_employee ($key_array) VALUES ($bind_array)";
$this->db->query($sql, $value_array);
}
Refer to comment in the function, how to achieve that output?
the idea is, from the input POST i get which over 27 fields, i just want to fill in into the $sql query i prepared as you can see. I don't think writing each table column manually is a good way.
im using Codeigniter 4 php framework + postgresql.
According to CodeIgniter 4 documentation, you can do this inside your loop for each employee:
$data = [
'title' => $title,
'name' => $name,
'date' => $date
];
$db->table('mytable')->insert($data);
You can use array and implode function first make array of key_array, value_array and bind_array then use implode() and use in sql like following
public function add_employee($input)
{
$key_array = array();
$value_array = array();
$bind_array = array();
foreach ($input as $column => $value) {
if ($value) {
$bind_array[] = '?';
$value_array[] = $value;
$key_array[] = $column;
}
}
$binds = implode(",",$bind_array);
$keys = implode(",",$key_array);
$values = implode(",",$value_array);
$sql = "INSERT INTO ol_employee ($keys) VALUES ($binds)";
$this->db->query($sql,$values);
}
by the insight of Alex Granados, this is the query i use:
public function add_employee($input)
{
foreach ($input as $column => $value) {
if ($value) {
$data[$column] = $value;
}
}
$this->db->table('ol_employee')->insert($data);
}
this will eliminate null field and regardless how many field, still working good.
as long the input name field from form is same as db column. Else, need to do some changes on that.
Thanks guys.
I use CodeIgniter, and when an insert_batch does not fully work (number of items inserted different from the number of items given), I have to do the inserts again, using insert ignore to maximize the number that goes through the process without having errors for existing ones.
When I use this method, the kind of data I'm inserting does not need strict compliance between the number of items given, and the number put in the database. Maximize is the way.
What would be the correct way of a) using insert_batch as much as possible b) when it fails, using a workaround, while minimizing the number of unnecessary requests?
Thanks
The Correct way of inserting data using insert_batch is :
CI_Controller :
public function add_monthly_record()
{
$date = $this->input->post('date');
$due_date = $this->input->post('due_date');
$billing_date = $this->input->post('billing_date');
$total_area = $this->input->post('total_area');
$comp_id = $this->input->post('comp_id');
$unit_id = $this->input->post('unit_id');
$percent = $this->input->post('percent');
$unit_consumed = $this->input->post('unit_consumed');
$per_unit = $this->input->post('per_unit');
$actual_amount = $this->input->post('actual_amount');
$subsidies_from_itb = $this->input->post('subsidies_from_itb');
$subsidies = $this->input->post('subsidies');
$data = array();
foreach ($unit_id as $id => $name) {
$data[] = array(
'date' => $date,
'comp_id' => $comp_id,
'due_date' => $due_date,
'billing_date' => $billing_date,
'total_area' => $total_area,
'unit_id' => $unit_id[$id],
'percent' =>$percent[$id],
'unit_consumed' => $unit_consumed[$id],
'per_unit' => $per_unit[$id],
'actual_amount' => $actual_amount[$id],
'subsidies_from_itb' => $subsidies_from_itb[$id],
'subsidies' => $subsidies[$id],
);
};
$result = $this->Companies_records->add_monthly_record($data);
//return from model
$total_affected_rows = $result[1];
$first_insert_id = $result[0];
//using last id
if ($total_affected_rows) {
$count = $total_affected_rows - 1;
for ($x = 0; $x <= $count; $x++) {
$id = $first_insert_id + $x;
$invoice = 'EBR' . date('m') . '/' . date('y') . '/' . str_pad($id, 6, '0', STR_PAD_LEFT);
$field = array(
'invoice_no' => $invoice,
);
$this->Companies_records->add_monthly_record_update($field,$id);
}
}
echo json_encode($result);
}
CI_Model :
public function add_monthly_record($data)
{
$this->db->insert_batch('monthly_record', $data);
$first_insert_id = $this->db->insert_id();
$total_affected_rows = $this->db->affected_rows();
return [$first_insert_id, $total_affected_rows];
}
AS #q81 mentioned, you would split the batches (as you see fit or depending on system resources) like this:
$insert_batch = array();
$maximum_items = 100;
$i = 1;
while ($condition == true) {
// code to add data into $insert_batch
// ...
// insert the batch every n items
if ($i == $maximum_items) {
$this->db->insert_batch('table', $insert_batch); // insert the batch
$insert_batch = array(); // empty batch array
$i = 0;
}
$i++;
}
// the last $insert_batch
if ($insert_batch) {
$this->db->insert_batch('table', $insert_batch);
}
Edit:
while insert batch already splits the batches, the reason why you have "number of items inserted different from the number of items given" might be because the allowed memory size is reached. this happened to me too many times.
I'm getting null values after I run the DBEscape($data) function that is for SQL injection protection. Can someone help?
My inputs are all multiple arrays, ex: name="quote[][dt_flight]", name="quote[][acft]", etc.
Method is POST.
function DBEscape($data){
$link = DBConect();
if(!is_array($data)){
$data = mysqli_real_escape_string($link,$data);
}
else {
$arr = $data;
foreach ($arr as $key => $value){
$key = mysqli_real_escape_string($link, $key);
$value = mysqli_real_escape_string($link, $value);
$data[$key] = $value;
}
}
DBClose($link);
return $data;
}
function DBCreate($table, array $data, $insertId = false){
$table = DB_PREFIX.'_'.$table;
$data = DBEscape($data);
var_dump($data);
$fields = implode(", ", array_keys($data));
$values = "'".implode("', '", $data)."'";
$query = "INSERT INTO {$table} ({$fields}) VALUES ({$values});";
var_dump($query);
return DBExecute($query, $insertId);
}
if(isset($_POST["quote"]) && is_array($_POST["quote"])){
foreach($_POST["quote"]["dt_flight"] as $key => $text_field){
$last_id = DBCreate('quote',$_POST['quote'],true);
$i++;
}
}
The connection works since it is inserting the rows into the tables. I used vardump before and after the DBEscape to figure out that it is deleting the values, the keys are fine.
PS: The proposed answer is for a single variable not an array.
As you can see in your var_dump-result, the data you sent to DBCreate and thus to DBEscape looks like
array(
'dt_flight' => array(0 => '2018-06-13'),
'acft' => array(0 => 'VQ-BFD',
// and so on
)
Therfore the data you sent to
// $value = array(0 => '2018-06-13') here
$value = mysqli_real_escape_string($link, $value);
And well, mysqli_real_escape_string doesn't like arrays very much, thus will return NULL and thus inserting empty data in your table.
You most likely want to resolve this error within your foreach($_POST["quote"]["dt_flight"]) loop, since I suppose you sent multiple flight-data:
foreach($_POST["quote"]["dt_flight"] as $key => $text_field) {
// $key would be 0, for $_POST["quote"]["dt_flight"][0] = '2018-06-13'
$keyData = [];
foreach($_POST["quote"] as $field => $allFieldValues) {
// Walk over every field, and add the value for the same $key
if (is_array($data) && isset($allFieldValues[$key])) {
// Would add for example $keyData['acft'] = $_POST['quote']['acft'][0] = 'VQ-BFD';
$keyData[$field] = $allFieldValues[$key];
}
}
var_dump($keyData);
// Would look like array(
// 'dt-flight' => '2018-06-13',
// 'acft' => 'VQ-BFD',
// and so on
// )
$last_id = DBCreate('quote',$keyData,true);
$i++;
}
Although this is not part of your question, I really suggest you also take care of my comment on your question about mysqli_real_escape_string not being a safe way to escape column-names (or table-names and so on). For example with following solution:
function DBCreate($table, array $data, $insertId = false) {
// For each table the known columns
$columns = array( 'quote' => array('dt_flight', 'acft', '...') );
// Verify valid table given
if (!isset($columns[$table])) {
throw new InvalidArgumentException('No such table: ' . $table);
}
// Remove everything from data where the key is not in $columns[$table]
// = Remove everything where the column-name is non-existing or even an attempt to hack your system
$data = array_intersect_key($data, array_fill_keys($columns[$table], null));
if (!count($data)) {
throw new InvalidArgumentException('No (valid) data given at all');
}
// Next, continue with your implementation
}
I have a table employees, which contains columns : employee_id, name, employee_manager_id.
employee_manager_id references to employee_id. It's a hierarchal data.
I have this output using PHP but couldn't achieve it using only one mySQL query. As of now, I need to process the data in PHP using recursive function so i can achieve this kind output.
Sample Array Output
0 => (
employee_id => 2,
name => Jerald,
employee_manager_id => 1,
depth => 1
),
1 => (
employee_id => 3,
name => Mark,
employee_manager_id => 2,
depth => 2
),
2 => (
employee_id => 6,
name => Cyrus,
employee_manager_id => 3,
depth => 3
),
3 => (
employee_id => 4,
name => Gerby,
employee_manager_id => 2,
depth => 2
)
As of now, this is my recursive function in PHP to achieve the output above.
function get_employees_by_hierarchy( $_employee_id = 0, $_depth = 0, $_org_array = array() ) {
if ( $this->org_depth < $_depth ) {
$this->org_depth = $_depth;
}
$_depth++;
$_query = "SELECT * FROM employees WHERE ";
if ( !$_employee_id ) {
$_query .= "employee_manager_id IS NULL OR employee_manager_id = 0";
}
else {
$_query .= "employee_manager_id = " . $this->dbh->quoteSmart( $_employee_id );
}
$_result = $this->query( $_query );
while ( $_row = $_result->fetchRow() ) {
$_row['depth'] = $_depth;
array_push( $_org_array, $_row );
$_org_array = $this->get_employees_by_hierarchy(
$_row['employee_id'],
$_depth,
$_org_array
);
}
return $_org_array;
}
My question is, is there anyway so I can achieve the array output i want using just one mysql query?
If not possible in mysql query, is there anymore to optimize in my current code?
Any help would greatly be appreciated.
Thanks
You can try a nested set a.k.a. celko tree but insert and delete is very expensive. There is also closures and path enumeration (materialized path) but I'm not an expert. MySql doesn't support recursive queries.
I don't think you can get the depth with your current model without doing any processing on the results, but you don't need to make multiple queries.
Assuming $employees is the list of employees indexed by employee_id, you could do something like this:
function set_employee_depth(&$employees, $id) {
if (!isset($employees[$id]['depth'])) {
$employee_manager_id = (int) $employees[$id]['employee_manager_id'];
if (!$employee_manager_id) {
$employees[$id]['depth'] = 0;
} elseif ($employee_manager_id !== $id) {
$employees[$id]['depth'] = 1 + set_employee_depth($employees, $employee_manager_id);
} else {
throw new \Exception('Employee cannot be its own manager!');
}
}
return $employees[$id]['depth'];
}
foreach ($employees as $id => $employee) {
set_employee_depth($employees, $id);
}
So, your table is composed of 3 columns (employee_id, name, employee_manager_id). employee_manager_id is a self reference to employee_id. You want to construct an array with all records, adding an extra field called depth which represents the distance of said employee to the "big boss", with only one query to the database. Is that correct? I'm also assuming that the database structure can't be changed.
If these assumptions are correct, this is a basic HIERARCHICAL/TREE data structure and thus, you have a couple of ways you can tackle this problem.
First Script
The first script runs the results array sequentially, finding the main node / trunk (the big boss) first and then adding it's children, then it's grandchildren and so on. Each time a node is "sorted", it will be removed from the cycle until no nodes are left. It assumes that:
There are no Orphan records (employees with invalid managers_ids)
There are no Circular References, either simple (A is manager of B and B is manager of A) or complex (*A manager of B, B manager of C and C manager of A)
Each path (from the main node to last node) can have an infinite number of nodes
$results are produced by running a simple query SELECT * FROM employees ORDER BY employee_manager_id
Code:
$finalArray = array();
$limit = count($results);
while (count($results) > 0) {
$results[0]['cnt'] = isset($results[0]['cnt']) ? $results[0]['cnt']++ : 0; // set num of times each element was already visited
if ($results[0]['cnt'] === $limit) { //prevent an infinite cycle
break;
}
$manId = $results[0]['manager_id'];
if ($manId === null) {
$results[0]['depth'] = 0;
} else if ( ($key = searchForId($manId, $finalArray)) !== null ) {
$results[0]['depth'] = $finalArray[$key]['depth'] + 1; //use the depth of parent to calculate its own
} else {
$results[] = $results[0]; //parent was not visited yet so we add it to the end of array
array_shift($results);
continue;
}
unset($results[0]['cnt']);
$finalArray[] = array_shift($results);
}
function searchForId($id, $array) {
foreach ($array as $key => $val) {
if ($val['id'] === $id) {
return $key;
}
}
return null;
}
This script is pretty straightforward. It only runs one query to the DB. In best case scenario, it will only traverse the array once. In worse case scenario, it will visit each element count(array) - 1, which can be slow with big arrays. However, since the results are pre-sorted, best case scenario will probably be more common.
Second Script
The second script builds an actual tree of elements. It's a bit more complex but achieves similar results. Also, the Depth is calculated dynamically.
class Employee {
public $id;
public $name;
public $manager;
public function __construct($id, $name, Employee $manager = null) {
$this->id = $id;
$this->name = $name;
$this->manager = $manager;
}
public function setManager(Employee $manager) {
$this->manager = $manager;
}
public function getDepth() {
if ($this->manager === null) {
return 0;
} else {
return $this->manager->getDepth() + 1;
}
}
}
$finalArray = array();
$paths = array();
foreach ($results as $r) {
$finalArray[(int) $r['id']] = new Employee((int)$r['id'], $r['name']);
if ($r['manager_id'] !== null) {
$paths[(int) $r['id']] = (int) $r['manager_id'];
}
}
foreach ($paths as $k => $v) {
if (isset($finalArray[$k]) && isset($finalArray[$v])) {
$finalArray[$k]->setManager($finalArray[$v]);
}
}
Here is a link for answer
Here is full code for making tree structure for hierarchy management using php and mysql.
I would like to insert some records into my DB table and using the insertgetid feature, return those results to my blade view.
Controller
$grids[] = array();
foreach($c as $key) {
$grids[] = DB::table('infile')->insertGetId(
array( 'href' => $key,
'creator_id' => $id,
'random' => substr(str_shuffle("aBcEeFgHiJkLmNoPqRstUvWxYz0123456789"),0, 9))
);
}
$name[] = array();
foreach($grids as $id){
$name = DB::table('infile')->where('id', '=', $id)->first();
}
return View::make('Home')->withName($name);
Blade View
#if(isset($name) && $name != '')
{{dd($name)}}
#endif
I'm getting this error
ErrorException
preg_replace(): Parameter mismatch, pattern is a string while replacement is an array
You can use whereIn to make exact query. between should work, but it's error prone, since there might be another row inserted in the meantime:
$ids = [];
foreach (..)
{
$ids[] = DB::table('infile')->insertGetId(...);
}
$data = DB::table('infile')->whereIn('id', $ids)->get();
I ended up using a different approach
I found the max id before I performed the insert and then found the max id after the insert and then used a wherebetween to grab the data.
$max = DB::table('infile')->max('id');
foreach($c as $key) {
DB::table('infile')->insertGetId(
array(
'href' => $key,
'creator_id' => $id,
'random' => substr(str_shuffle("aBcEeFgHiJkLmNoPqRstUvWxYz0123456789"),0, 9)
)
);
}
$max2 = DB::table('infile')->max('id');
$data = DB::table('infile')->whereBetween('id', array($max, $max2))->get();
$id = DB::table('user')->insertGetId(['name'=>"test"]);