In CodeIgniter I'm trying to create a function. Need create array who will delete rows in db like:
$selected_items_by_id = array('1','2','3','4',); // --<<<Need Create This
$this->db->where_in('id', $selected_items_by_id);
$this->db->delete('mytable');
mytabe DB Structure of:
ID | NAME | PARENT_ID
1 Item1 0 // First Root item
2 Item2 1 // First Root sub item
3 Item3 2 // First Root sub sub item
4 Item4 3 // First Root sub sub sub item
5 Item5 0 // Second Root item
Items layout:
Item1
+Item2
++Item3
+++Item4
Item5
Here I'm getting the needed item id (from select box):
$id = $this->input->post('delete_menu_item');
Logic:
If item parent_id == 0, then item is root.
If Item is root item, in array will be only root item id
In array need $id and all $id subs (if they exist)
UPDATE
I try to make recursive function outside from CI.
Search function - to separate the necessary subaray:
function search($array, $key, $value)
{
$results = array();
if (is_array($array))
{
if (isset($array[$key]) && $array[$key] == $value)
$results[] = $array;
foreach ($array as $subarray)
$results = array_merge($results, search($subarray, $key, $value));
}
return $results;
}
I'm using this array, based on DB entries:
$array = array(
array('id' => '1', 'name' => 'Item1', 'parent_id' => '0'),
array('id' => '2', 'name' => 'Item2', 'parent_id' => '1'),
array('id' => '3', 'name' => 'Item3', 'parent_id' => '2'),
array('id' => '4', 'name' => 'Item4', 'parent_id' => '3'),
array('id' => '5', 'name' => 'Item5', 'parent_id' => '0'),
);
Recursive function:
function build_array($array,$id, $final = NULL){
$data = search($array, 'id', $id);
foreach ($data as $item):
if ($item['parent_id'] == 0){
$final[] = $item['id'];
}
else {
$parent_id = $item['parent_id'];
$final[] = $item['id'];
$final[] = $parent_id;
build_array($array, $parent_id, $final);
// Here go recursive
}
endforeach;
return $final;
}
$result = build_array($array,1);
var_dump($result);
What should be the function recursively?
If I understand what you are asking, you'll need to handle each item iteratively (in a loop).
So first you get an item, then you see if it has a parent - if so, get it's parent and start again, etc. This is one of the few cases where recursive functions can really come in handy.
You just build your final arrays as you go, until you run out of items - being aware that if you aren't careful you will get an infinite loop as you pull the same items over and over again.
Without more information on what you have to input and what you want to get as output, this is about as helpful as anyone can be. For more help you'll need to clarify what you want as output, code you've tried so far and what it's doing that you don't want, etc.
Ok. I will use the sample array you put together, only slightly different: Since ids are unique, there's no reason why they shouldn't serve as keys. Like so:
$array = array(
'1'=>array('name' => 'Item1', 'parent_id' => '0'),
'2'=>array('name' => 'Item2', 'parent_id' => '1'),
'3'=>array('name' => 'Item3', 'parent_id' => '2'),
'4'=>array('name' => 'Item4', 'parent_id' => '3'),
'5'=>array('name' => 'Item5', 'parent_id' => '0'),
);
Add the following methods to your controller/model/lib/whatever:
function children_of($arr,$id)
{
$r=array();
foreach($arr as $key=>$entry)
if($entry['parent_id']==$id) $r[]=$key;
return $r;
}
function subtree_array($arr,$id)
{
if(isset($arr[$id]))
{
$r=array($id);
foreach($this->children_of($arr,$id) as $child)
$r=array_merge($r,$this->subtree_array($arr,$child));
return $r;
}
else
return array(0); // (0) comes handy in SQL IN operations
}
children_of() returns the immediate children of $id in $arr.
subtree_array() returns an array including $id and all of its descendants in $arr. If $id is not a key of $arr, it returns array(0). That's because you say you want to use it in an SQL query, and a where clause like where xyz in () would be bogus, while where xyz in (0) wouldn't, and also would always return false since no item has a zero id (it seems to be reserved to denote root nodes).
So, the usage might be something like:
$id=$this->input->post('delete_menu_item');
$selected_items_by_id=subtree_array($aray,$id);
I tried to approach the task from the other side.
Using cascade delete, and everything is going sql side:
CREATE TABLE `navigation` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL COLLATE 'utf8_general_ci',
`url` VARCHAR(50) NOT NULL COLLATE 'utf8_unicode_ci',
`position` MEDIUMINT(8) NOT NULL DEFAULT '100',
`parent_id` INT(11) NOT NULL,
PRIMARY KEY (`id`),
INDEX `parent_id` (`parent_id`),
CONSTRAINT `FK1` FOREIGN KEY (`parent_id`) REFERENCES `navigation` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB;
Thank you for your suggestions.
Related
I have questions model in laravel that has a database structure like this:
question_id | is_active
------------+-----------
1 | Y
2 | Y
3 | N
... | ...
I have an input in form of an array that i want to validate with questions model, this array will contain key that act as the question_id and the value as the question answer, that looks like this:
$QnAs = [
'1' => '3',
'2' => '5',
'4' => '1',
'6' => '4',
'7' => '2',
'8' => '0',
]
The validation system will required all the Active (is_active == 'Y') questions that present in the array as question_id as the key of array to represent it and the value of each array is 1, 2, 3, or 4.
I can achieve that by looping through every question that active like this:
$collections = Questions::where('is_active','Y')->get();
foreach($collections as $collection){
if(!array_key_exists($collection->question_id,$QnAs)){
return false; // doesn't find the question id in array key input
} elseif(!in_array($QnAs[$collection->$question_id],['1','2','3','4'])){
return false; // input value for this array does not match with required value
}
}
the problem with this code is , when i process 15k++ data it will take a long time too process because it has to loop through every model collection , is there any other approach that can simplify the code and the process ?
Your code wont work . Beacause return will immediately end the execution of current function. So the checking will be done only with the first value in collections and it will return the result based on the first element in the array $collections.
you can change the code as following :
$collections = Questions::where('is_active','Y')->get();
$result = true;
foreach($collections as $collection)
{
if(!array_key_exists($collection->question_id,$QnAs))
{
$result = false; // doesn't find the question id in array key input
}
}
return $result;
The above code will work
You can research more for greater performance
The problem is you are getting all of the active questions,
so the point is to limit the datas,
Firstly, add composite index on question_id and is_active
$table->index(['question_id', 'is_active']);
And try this code:
$QnAs = [
'1' => '3',
'2' => '5',
'4' => '1',
'6' => '4',
'7' => '2',
'8' => '0',
];
// filter elements allowed:
$valid_QnAs = array();
foreach($QnAs as $k => $v) {
if (in_array($v, [1,2,3,4])) {
$valid_QnAs[$k] = $v;
}
}
$not_in_active = Questions::where('is_active','Y')
->whereNotIn('question_id', array_keys($valid_QnAs))
->exists();
if ($not_in_active) return false;
I am using ThingEngineer/PHP-MySQLi-Database-Class and I am trying to perform a multiple insert while using OnDuplicate. The goal is to insert a new product record if the 'sku' does not already exist. If the 'sku' does exist then the 'name' should be updated instead of creating a new entry.
MySQL Schema:
CREATE TABLE `products` (
`product_pk` bigint(9) NOT NULL,
`product_id` int(20) UNSIGNED NOT NULL,
`name` varchar(255) NOT NULL,
`sku` varchar(16) NOT NULL,
`category` int(10) DEFAULT NULL,
`last_update` timestamp NOT NULL ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
ALTER TABLE `products`
ADD PRIMARY KEY (`product_pk`),
ADD UNIQUE KEY `sku` (`sku`);
ALTER TABLE `products`
MODIFY `product_pk` bigint(9) NOT NULL AUTO_INCREMENT;
PHP:
$sDate = date("Y-m-d H:i:s");
$lastid = $db->rawQuery('SELECT MAX( product_id ) AS max FROM products');
(!$lastid || !isset($lastid[0]['max'])) ? $pid = 0 : $pid = $lastid[0]['max']++;
foreach ($data as $item){
if (isset($item['sku']) && !null == $item['sku']){
$prod[$pid]['product_id'] = $pid;
$prod[$pid]['sku'] = $item['sku'];
$prod[$pid]['name'] = substr($item['product-name'],0,255);
$prod[$pid]['last_update'] = $sDate;
$pid++;
}
}
$utfEncodedArray =encodeArray($prod, 'UTF-8');
$db->onDuplicate('name', 'product_pk');
$db->insertMulti('products', $utfEncodedArray);
function encodeArray($array, $type)
{
foreach($array as $key => $value)
{
if (is_array($value)){ $array[$key] = encodeArray($value, $type);}else{ $array[$key] = mb_convert_encoding($value, $type);}
}
return $array;
}
The error I receive is:
Uncaught mysqli_sql_exception: Duplicate entry 'ABC123' for key 'sku'
Here is a sample of the array $utfEncodedArray used on the insertMulti call:
Array(
[1] => Array
(
[product_id] => 1
[sku] => ABC123
[name] => product1
[last_update] => 2018-09-08 18:55:20
)
[2] => Array
(
[product_id] => 2
[sku] => ABC124
[name] => product2
[last_update] => 2018-09-08 18:55:20
)
)
Steps I have tried so far:
Dropped the 'products' table and created it again. Multiple times.
Tried using 'sku' instead of 'product_pk' in the onDuplicate call.
Tried multiple collation types
Tried using unique key on both 'sku' and 'product_id'
When I attempted this method all entries were inserted correctly but when running it again it generated duplicates instead of updating the existing row. Not sure how this happened seeing as both 'sku' and 'product_id' are unique.
The $prod array currently contains the same values. So every time I run this I would expect to see the 'last_updated' column to be updated every time after the initial inserts.
This is my first experience using onDuplicate and despite hours of searching and reading docs I am still lost. I was trying to let the db class handle the multiple insert from the array but I am not against trying raw queries while iterating over my array of products instead.
Of course as soon as I posted this I find the issue...
Found a fork of the database class which resolved issues with insertMulti while using onDuplicate:
should fix insertMulti() if onDuplicate() is set
I have a problem with a recursive function that takes too many resources to display a child-parent relation in a dropdown list. For example:
home
-menu 1
-menu 2
home 1
-menu 3
-menu 4
I've written some code for a recursive call to the database each time, so that's the reason why my code takes so many resources to run.
Below is my code:
--call recursive
$tmp = $this->get_nav_by_parent(0);
$a_sel = array('' => '-Select-');
$a_sel_cat = array('home' => 'home');
$this->get_child_nav_cat($tmp, 0, $a_sel);
--
public function get_nav_by_parent($parent) {
$all_nav = $this->db
->select('id, title, parent')
->where('parent',$parent)
->order_by('position')
->get('navigation_links')
->result_array();
$a_tmp = array();
foreach($all_nav as $item)
{
if($parent != 0){
$item['title'] = '--' . $item['title'];
}
$a_tmp[] = $item;
}
return $a_tmp;
}
-- Recursive function
public function get_child_nav_cat($a_data, $parent, &$a_sel) {
foreach($a_data as $item) {
$a_sel[$item['page_slug_key']] = $item['title'];
$atmp = $this->get_nav_by_parent($item['id']);
$this->get_child_nav_cat($atmp, $item['id'], $a_sel);
}
return $a_sel;
}
Please give me suggestions for the best solution to display the data as child-parent relationship in select box.
Thanks in advance!
Best way to display parent child relationship is mentain parent and child flag in Database instead of
fetching value using loop.
In your case Home, Home 1 is parent flag and menus belong on child flag.
fetch data from db and your loop look like this:-
$arr = array(0 => array('name' => 'home','parent' => 0),
1 => array('name' => 'menu 1 ','parent' => 1),
2 => array('name' => 'menu 2 ','parent' => 1),
3 => array('name' => 'home 1','parent' => 0),
4 => array('name' => 'menu 3 ','parent' => 2),
5 => array('name' => 'menu 4','parent' => 2)
);
$dd_html = '<select>';
foreach($arr as $k => $v){
if($v['parent'] == 0 )
$dd_html .='<option>'.$v['name'].'</option>';
else
$dd_html .='<option>--'.$v['name'].'</option>';
}
$dd_html .= '</select>';
echo $dd_html;
Output :-
home
-menu 1
-menu 2
home 1
-menu 3
-menu 4
Set ParentID=0 for detecting root item
then execute this:
SELECT * FROM table ORDER BY ParentID, ID
Then iterate through result and when ParentID changed, create new level.
Please I have a table with three columns :
id (this is the id of my table)
parent (this is the parent)
position (and this is the position)
And I have this array $_arr['menu'] :
array (size=3)
13 =>
array (size=1)
'parent' => string '0' (length=1)
14 =>
array (size=1)
'parent' => string '13' (length=2)
25 =>
array (size=1)
'parent' => string '0' (length=1)
I want to update may table with those values in the array $_arr['menu'].
I imagine to do something like but in one query (perhaps using case in) :
UPDATE table SET position = 1, parent = 0 WHERE id = 13;
UPDATE table SET position = 2, parent = 13 WHERE id = 14;
UPDATE table SET position = 3, parent = 0 WHERE id = 25;
Please masters, how to get those values from that array and how to do the update !
Thanks a lot
I'm not sure I got your question, but I'll write my snippet. (I don't remember php syntax well and it's not tested, but I think it shows what I want to say)
try {
$db->beginTransaction();
$increment = 0;
foreach ($arr as $id => $innerArray) {
$db->query("UPDATE table SET position = ".(++$increment).", parent = ".$innerArray['parent']." WHERE id =".$id);
}
$db->commit();
} catch (Exception $e) {
$db->rollback();
}
I have an array which contains data from two or more database queries.
The merging needs to be done by a specified column (date in this case) and the resultant rows must contain all associative keys from the original array.
$array = [
[
['date' => '2011-01-17', 'col-1' => 58, 'col-2' => 54],
['date' => '2011-01-19', 'col-1' => 50, 'col-2' => 61],
['date' => '2011-01-20', 'col-1' => 44, 'col-2' => 22],
['date' => null, 'col-1' => 448, 'col-2' => 196],
],
[
['date' => '2011-01-17', 'col-3' => 1489],
['date' => '2011-01-18', 'col-3' => 1534],
['date' => null, 'col-3' => 1534],
]
];
I'd like to merge data that is related by the date value, make sure that every row contains all possible columns, and is sorted by date with the empty-dated row occurring last.
Desired output:
array (
0 =>
array (
'date' => '2011-01-17',
'col-1' => 58,
'col-2' => 54,
'col-3' => 1489,
),
1 =>
array (
'date' => '2011-01-18',
'col-1' => NULL,
'col-2' => NULL,
'col-3' => 1534,
),
2 =>
array (
'date' => '2011-01-19',
'col-1' => 50,
'col-2' => 61,
'col-3' => NULL,
),
3 =>
array (
'date' => '2011-01-20',
'col-1' => 44,
'col-2' => 22,
'col-3' => NULL,
),
4 =>
array (
'date' => NULL,
'col-1' => 448,
'col-2' => 196,
'col-3' => 1534,
),
)
Additional notes:
The input array may have a count greater than 2.
Subarray may have differring counts and are not relatable by their index.
First, I'm going to assume your data is sorted by date, since you can do it in SQL and it's sorted in your example. You need to walk through all of your sets at the same time, merging as you go.
$merged = array();
$i_first = 0;
$i_second = 0;
$count_first = count($data[0]);
$cound_second = count($data[1]);
while ($i_first < $count_first || $i_second < $count_second)
{
// this comparison depends on what your merge_by is
$diff = strtotime($data[$i_first]['date']) - strtotime($data[$i_second]['date']);
if ($diff == 0)
{
$merged[] = array_merge($data[$i_first], $data[$i_second]);
$i_first++;
$i_second++;
}
elseif ($diff < 0) // first date is earlier
{
$i_first++;
}
else // second date earlier
{
$i_second++;
}
}
Now your $merged array should have what you want. Note that this solution assumes you don't have duplicate date rows in one chunk, or it would overwrite the existing results. Also, you could expand to have more than two data sets if you want.
Populate an array of default values to ensure that rows will contain null values for all expected columns by using two rounds of a flattening technique then mapping all keys to null values for later use.
Use nested loops to access and push rows of data into the result array. Use temporary first level keys to identify unique date groups. To aid in the sorting of rows later, use the Elvis operator (?:) to fallback to the literal string 'last' when defining the first level key -- all other date values will be alphabetically sorted before this key.
While iterating, if the current row contains a date which has not been encountered before, overwrite the default values with its values and push that data into that group's row. If the current row contains a date which has been encountered before, overwrite the cached group data with the new data.
After looping, sort the array by its first level keys.
To remove the temporary first level keys, call array_values().
Code: (Demo)
$defaults = array_map(
fn() => null,
array_merge(...array_merge(...$array))
);
$result = [];
foreach ($array as $set) {
foreach ($set as $row) {
$key = $row['date'] ?: 'last';
$result[$key] = array_replace($result[$key] ?? $defaults, $row);
}
}
ksort($result);
var_export(array_values($result));