PHP Mysql Updating positions and parents of an Id in a table - php

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

Related

Laravel Validate and match array variable with Model Collection

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;

How to LIMIT a Query which is already been selected?

I am trying to LIMIT a Query which is already been selected. I know I can directly do it in my query select, but due to some logics of the code, if I do that way I have to run the query selection twice which (I believe) will increase the computational time!
So, what I am trying is this:
$query1 = $mysqli->query("SELECT * FROM tableName WHERE someconditions");
then, I need to re-select a sub-selection of the $query1 for my Pagination. What I am trying to do is something like this;
$query2 = LIMIT($query1, $limit_bigin, $limit_end);
where $limit_bigin, $limit_end provide the LIMITING range (start and end respectively).
Could someone please let me know how I could do this?
P.S. I know I can do it directly by:
$query1 = $mysqli->query("SELECT * FROM tableName WHERE someconditions");
$query2 = $mysqli->query("SELECT * FROM tableName WHERE someConditions LIMIT $limit_bigin, $limit_end");
But this is running the query twice and slows down the process (I must run the first query without limits due to some logics of the program)
EDIT 1
As per the answers I tried using array_slice in PHP. BUT, since Query is an object it doesn't give the results that was expected. A NULL is resulted form
array_slice($query1, $start, $length, FALSE)
If you have already carried out the query and the result set has been returned to your PHP, you can not then LIMIT it. As you state, then running a second SQL execution of a subpart of the same query is wasteful and repetative.
Don't
Repeat
Yourself.
DRY.
As I said above, repetition causes issues with maintainability as you can easily forget about repetition, tweaking one SQL and forgetting to tweak the other.
Don't
Repeat
Yourself.
DRY.
Use PHP instead
Once you have executed the query, the result set is then passed back to PHP.
So assuming you have a $var with the contents of you SQL query, then you simply need to select the valid rows from the $var, not from the database [again].
You can do this using PHP numerous Array functions. Particularly array_slice().
So;
$query1 = $mysqli->query("SELECT * FROM tableName WHERE someconditions");
Now, to select the second page, say for example rows 10 to 20:
$query2 = array_slice($query1, (10-1), 10 );
This wil then "slice" the part of the array you want. Remember that the array counts will start at zero so to grab row 10 (of an index starting at 1, Typical of a MySQL Auto Increment Primary Key), then you will need to select X number of rows from row (10-1) .
Please also read the manual for PHP array_slice().
Further
As referenced in comments, there is no guarentee that your SQL will return the same values each time in the same order, so it is highly recommended to order your results:
$query1 = $mysqli->query("SELECT * FROM tableName
WHERE someconditions ORDER BY primary_key_column");
Example Data:
$query1 = $mysqli->query("SELECT * FROM tableName WHERE someconditions ORDER BY id");
/***
$query1 = array {
0 => array { 'id' => 1, 'user' => "Jim", 'colour' => "Blue" },
1 => array { 'id' => 2, 'user' => "Bob", 'colour' => "Green" },
2 => array { 'id' => 3, 'user' => "Tom", 'colour' => "Orange" },
3 => array { 'id' => 4, 'user' => "Tim", 'colour' => "Yellow" },
4 => array { 'id' => 5, 'user' => "Lee", 'colour' => "Red" },
5 => array { 'id' => 6, 'user' => "Amy", 'colour' => "Black" }
}
***/
$page = 2;
$size = 3; // number per page.
$start = ($page - 1) * $size; //page number x number per page.
// Select the second page of 3 results.
$query2 = array_slice($query1, $start, $size , FALSE);
/***
$query2 = array {
0 => array { 'id' => 4, 'user' => "Tim", 'colour' => "Yellow" },
1 => array { 'id' => 5, 'user' => "Lee", 'colour' => "Red" },
2 => array { 'id' => 6, 'user' => "Amy", 'colour' => "Black" }
}
***/
You can then use these in a foreach or other standard array manipulation technique.

Array joining using an id as reference

I have 2 tables. The first table is an equipment table. A query from this table looks like this:
id name user_id
-----------------------
1 equip1 1001
2 equip2 1002
The seconde table is an users table. A query from this table looks like this:
id username
--------------
1001 user1
1002 user2
I want to achieve something like this:
id name user_id username
-----------------------------------
1 equip1 1001 user1
2 equip2 1002 user2
Is there a way to join both arrays like doing a join query? I can't use JOIN in my query, because the tables are on different databases (I know there is a way to do JOIN on different databases, but I'm not allowed to use that).
EDIT:
I'm adding the structure of these arrays.
$equipment = array(
[0] => array(
['id'] => 1,
['name'] => 'equip1',
['user_id'] => 1001
),
[1] => array(
['id'] => 2,
['name'] => 'equip2',
['user_id'] => 1002
)
);
$users= array(
[0] => array(
['id'] => 1001,
['username'] => 'user1'
),
[1] => array(
['id'] => 1002,
['username'] => 'user2'
)
);
You would likely have to join the queries yourself. I don't believe there is a built in function (not counting walk or map with a callback). This is what I would do
//empty array for indexing users under their id for faster loopups
$users = array();
//loop over the users result
foreach($usersResult as $row){
//index users under their id.
$users[$row['id']] = $row['username'];
}
//now loop over the equipment to join the arrays together
foreach($equipmentResult as $key=>$row){
//add the username column
$row['username'] = isset($users[$row['user_id']])?$users[$row['user_id']]:null;
//save back into the equipment row
$equipmentResult[$key] = $row;
}
//display
print_r($equipmentResult);
This could easily be turned into a function where you pass arguments that would build the "ON" portion for the column names.
Edit: Made it a function.
<?php
/**
* Joins two arrays as if they were joined in a query
* #param Array $arrayA The base (left) array to join into
* #param Array $arrayB The right array to join into A
* #param String $colA The column name to join on for arrayA
* #param String $colB [optional] The column name to join on for arrayB. If
* blank, then it is assumed the same column name as colA
* #param boolean $leftJoin [optional] Should this be a left join and include rows
* from A where no value exists in B?
* #return void
*/
function array_join($arrayA, $arrayB, $colA, $colB=null, $leftJoin=false){
//if no value was passed for colB, assume it is the same value as colA
if(is_null($colB)){
$colB = $colA;
}
//output data
$out = array();
//create an index for array B for faster lookups
$idxB = array();
$colsB = array();
foreach($arrayB as $row){
//get the value from B
$valB = $row[$colB];
//if the column doesn't exist in the index, add it
if(!isset($idxB[$colB])){
$idxB[$colB] = array();
}
//index the value
$idxB[$valB][] = $row;
//store the known column to an array for use below
if(empty($colsB)){
$colsB = array_keys($row);
}
}
//loop over array A
foreach($arrayA as $rowA){
//get the value for the column
$valA = $rowA[$colA];
//does the value from A exist in B
$rowB = isset($idxB[$valA])?$idxB[$valA]:null;
//join the rows
//add blank columns if left join
if($leftJoin && is_null($rowB)){
$rowBJoin = array_combine($colsB, array_fill(0, count($colsB), null));
//add the row to our output
$out[] = $rowA + $rowBJoin;
} else {
//inner join or value is not null
//loop over all the rows from the B index that we are joining on
foreach($rowB as $rowBJoin){
//add the row to our output
$out[] = $rowA + $rowBJoin;
}
}
}
return $out;
}

PHP/mySQL: Import data and store in hierarchical nested set for use with jsTree

I'm using jsTree to view hierarchical data that is stored in a mySQL database as a nested set (left, right, level, etc.). This is working fine, but I need to allow users to import data by uploading a CSV file. When they do so, any existing data in the table will be removed so I don't have to worry about updating the left/right fields.
The data they will be uploading will be in this format:
"Code","Title"
"100","Unit 100"
"200","Unit 200"
"101","Task 101: This is a task"
"102","Task 102: Another task"
"201","Task 201: Yet another"
"300","Unit 300"
"301","Task 301: Another one"
Everything will be a child of a main "Group" that is a level 1 node. All of the "codes" divisible by 100 (ie. 100, 200, 300) will be level 2 (parent nodes.. children of "Group"). All others will be level 3 (child) nodes of their respective parent nodes (ie. 101 and 102 are children of 100, 201 is a child of 200, etc.)
The resulting table in mySQL should look like this:
id parent_id position left right level title
1 0 0 1 18 0 ROOT
2 1 0 2 17 1 Group
3 2 0 3 8 2 Unit 100
4 2 1 9 12 2 Unit 200
5 3 0 4 5 3 Task 101: This is a task
6 3 1 6 7 3 Task 102: Another task
7 4 0 10 11 3 Task 201: Yet another
8 2 2 13 16 2 Unit 300
9 8 0 14 15 3 Task 301: Another one
The tree would then look like this:
My question is: using PHP, what is the best method to accomplish this? I already have code in place that pulls the data contained in the uploaded CSV file and stores it in an array, but I'm not sure what the logic to convert this to a nested set should look like.
Right now, the data is stored in a 2-dimensional array called $data (in the format $data[$col][$row]):
$data[0][0] = "Code";
$data[0][1] = "100";
$data[0][2] = "200";
$data[0][3] = "101";
$data[0][4] = "102";
$data[0][5] = "201";
$data[0][6] = "300";
$data[0][7] = "301";
$data[1][0] = "Title";
$data[1][1] = "Unit 100";
$data[1][2] = "Unit 200";
$data[1][3] = "Task 101: This is a task";
$data[1][4] = "Task 102: Another task";
$data[1][5] = "Task 201: Yet another";
$data[1][6] = "Unit 300";
$data[1][7] = "Task 301: Another one";
Array ( [0] => Array ( [0] => Code [1] => 100 [2] => 200 [3] => 101 [4] => 102 [5] => 201 [6] => 300 [7] => 301 ) [1] => Array ( [0] => Title [1] => Unit 100 [2] => Unit 200 [3] => Task 101: This is a task [4] => Task 102: Another task [5] => Task 201: Yet another [6] => Unit 300 [7] => Task 301: Another one ) )
Any help would be very much appreciated. I now have the parent_id, position, and level being calculated correctly... I just need to figure out the left/right part. Here is the code I'm currently using (thanks for getting me started Matteo):
$rows = array();
// insert ROOT row
$rows[] = array(
'id' => 1,
'parent_id' => 0,
'position' => 0,
'left' => 1,
'right' => 10000, // just a guess, will need updated later
'level' => 0,
'title' => 'ROOT',
);
echo "<br>";
print_r($rows[0]);
// insert group row
$rows[] = array(
'id' => 2,
'parent_id' => 1,
'position' => 0,
'left' => 2,
'right' => 9999, // just a guess, will need updated later
'level' => 1,
'title' => 'Group',
);
echo "<br>";
print_r($rows[1]);
// next ID to be used
$id = 3;
// keep track of code => ID correspondence
$map = array();
// parse data
for ($i = 1, $c = count($data[0]); $i < $c; ++$i) {
// save ID in the map
$map[$data[0][$i]] = $id;
// initialize the current row
$row = array(
'id' => $id,
'parent_id' => 1,
'position' => 0,
'left' => 0,
'right' => 0,
'level' => 1,
'title' => $data[1][$i],
);
// if the code is multiple of 100
if ($data[0][$i] % 100 == 0) {
$row['parent_id'] = 2;
$row['level'] = 2;
$row['position'] = (floor($data[0][$i] / 100)) - 1;
} else {
// get parent id from map
$row['parent_id'] = $map[floor($data[0][$i] / 100) * 100];
$row['level'] = 3;
$row['position'] = $data[0][$i] % 100;
}
// add the row
$rows[] = $row;
++$id;
echo "<br>";
print_r($row);
}
Given your $data array, you could parse it like this:
// this will contain all the rows to be inserted in your DB
$rows = array();
// insert ROOT row
$rows[0] = array(
'id' => 1,
'parent_id' => 0,
'position' => 0,
'level' => 0,
'left' => 1,
'right' => 10000,
'title' => 'ROOT',
);
// insert group row
$rows[1] = array(
'id' => 2,
'parent_id' => 1,
'position' => 0,
'level' => 1,
'left' => 2,
'right' => 9999,
'title' => 'Group',
);
// keep trace of code => ID correspondence
$map = array();
// next ID to be used
$id = 3;
// keep father => sons relationship
$tree = array();
// keep trace of code => row index correspondence
$indexes = array();
// next row index
$index = 2;
// parse your data
for ($i = 1, $c = count($data[0]); $i < $c; ++$i) {
// current code
$code = $data[0][$i];
// save ID in the map
$map[$code] = $id;
// update the indexes map
$indexes[$code] = $index;
// prepare the current row
$row = array(
'id' => $id,
'title' => $data[1][$i],
)
// get the value of code mod 100
$mod = $code % 100;
// if the code is multiple of 100
if ($mod == 0) {
// the parent_id is 2
$row['parent_id'] = 2;
// it is level two
$row['level'] = 2;
// compute position
$row['position'] = floor($code / 100) - 1;
}
else {
// get the parent code
$parent = floor($code / 100) * 100;
// get parent id from map using parent code
$row['parent_id'] = $map[$parent];
// it is level three
$row['level'] = 3;
// save position
$row['position'] = $mod;
// save in relationship tree
$tree[$parent][] = $code;
}
// add the row
$rows[$index] = $row;
// prepare next id
++$id;
// update row index
++$index;
}
// sort the relationship tree base on the parent code (key)
ksort($tree, SORT_NUMERIC);
// next left value
$left = 3;
// now, using the relationship tree, assign left and right
foreach ($tree as $parent => $sons) {
// calculate parent left value
$parentLeft = $left;
// prepare next left value
++$left;
// to be sure that the sons are in order
sort($sons, SORT_NUMERIC);
// assign values to sons
foreach ($sons as $son) {
// index in the rows array
$index = $indexes[$son];
// set left value
$rows[$index]['left'] = $left;
// set right value
$rows[$index]['right'] = $left + 1;
// increment left value
$left += 2;
}
// calculate parent right value
$parentRight = $left;
// prepare next left value
++$left;
// index of parent in the rows array
$index = $indexes[$parent];
// set the values
$rows[$index]['left'] = $parentLeft;
$rows[$index]['right'] = $parentRight;
}
// update group row right value
$rows[1]['right'] = $left;
// prepare next left value
++$left;
// update root row right value
$rows[0]['right'] = $left;
At this point, you can insert all the rows one at a time.
EDIT: now the script should handle all the required values correctly.
I would use Doctrine2 with a Nested Set Extension. You could use a nice and convenient API and don't have to worry about the nested set implementation:
See
http://www.gediminasm.org/article/tree-nestedset-behavior-extension-for-doctrine-2
or http://wildlyinaccurate.com/simple-nested-sets-in-doctrine-2
There are several extensions on github. Actually, i don't know which one is best.
https://github.com/l3pp4rd/DoctrineExtensions
https://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior
https://github.com/blt04/doctrine2-nestedset
List item
If your data is flat, you could parse for keywords like 'Unit' or 'Task' to arrange your elements to the needed hierarchical order.

Is there any way to check if a key exists in a nested array in PHP?

My result set come from a group/count query:
SELECT m.type, COUNT(m.type)
FROM sent_message m
WHERE m.user_id = :user_id
AND (m.sent_at >= :start_date OR m.sent_at <= :end_date)
GROUP BY m.type
And it's in the form of
an array nested into another array:
array (size=2)
0 =>
array (size=2)
'type' => string 'sms' (length=3)
'count' => string '1' (length=1)
1 =>
array (size=2)
'type' => string 'email' (length=5)
'count' => string '9' (length=1)
Is there any way to easily check if a given type index exists without looping? For avoiding this:
$resultSet = $repository->getAllCount(); // Returns the nested array
$viewBag = array('sent_sms_count' => 0, 'sent_email_count' => 0); // Init
foreach($resultSet as $result) :
if('sms' == strtolower($result['type'])
$viewBag['sent_sms_count'] = $result['count'];
if('email' == strtolower($result['type'])
$viewBag['sent_email_count'] = $result['count'];
endforeach;
There's no way I'm aware of without looping. But you can re-index the SQL results into a format you like:
foreach ($resultSet as $result) {
$viewBag['sent_' . strtolower($result['type']) . '_count'] = $result['count'];
}

Categories