First off, i tried searching the internet for a solution. But i dont really know what to search for as i dont have the right words for it. Maybe someone could point me to an answer already given.
So here is my "problem":
I have 3 tables in my database: user, task, user_task.
user contains users ofcourse. task contains tasks. And user_task is a relation table. It contains an id of a task and an id of a user. (One task can have multiple users, thats why i do it this way.)
The thing i want to do is simple. I want to get a list of all tasks and for each task the users assigned to it.
Something like this:
Task id: 1, name: do dishes, users: bob, liam.
As far as i came i only got to print a task twice if there were 2 users assigned to it. I got so far, by using a mysql query. Not really CI specific.
The query returns a result() with 2 rows, 1 for the task with the user bob and 1 for the same task with the user liam.
array{
array{
task_id = "1",
name = "do dishes",
user = "bob"
}
array{
task_id = "1",
name = "do dishes",
user = "liam"
}
array{
task_id = "2",
name = "vacuum",
user = "liam"
}
array{
task_id = "3",
name = "Take out thrash",
user = "liam"
}
array{
task_id = "3",
name = "Take out thrash",
user = "bob"
}
}
What i want to get is a result with 1 row containing the task and within that row i want an array with each name of the users assigned to it.
array{
array{
task_id = "1",
name = "do dishes",
user = array( "bob", "liam" )
}
array{
task_id = "2",
name = "vacuum",
user = array( "liam" )
}
array{
task_id = "3",
name = "Take out thrash",
user = array( "liam", "bob" )
}
}
Is there any way to achieve this? Within CI and/or MySQL?
I hope you guys can help me, if i am a bit unclear feel free to ask some specifications.
And as i said: if there is already an answer anywhere, please point me to it!
Thanks in advance!
Cheers.
No, there isn't any option in CI Active Records to achieve that.
You have to loop your result.
<?php
$result = array(
array(
'task_id' => 1,
'name' => 'do dishes',
'user' => 'bob'
),
array(
'task_id' => 1,
'name' => 'do dishes',
'user' => 'liam'
),
array(
'task_id' => 2,
'name' => 'vacuum',
'user' => 'liam'
),
array(
'task_id' => 3,
'name' => 'Take out thrash',
'user' => 'liam'
),
array(
'task_id' => 3,
'name' => 'Take out thrash',
'user' => 'bob'
),
);
function get_arrayvalues_bykeyvalue($array, $key, $key2, $v2)
{
$ret = array();
foreach($array as $arr)
{
foreach($arr as $k => $v)
{
if($arr[$key2] == $v2)
{
if($k == $key)
$ret[] = $v;
}
}
}
$u = array_unique($ret);
return (sizeof($u) == 1) ? $u[0] : $u;
}
$res = array();
foreach($result as $arr)
{
foreach($arr as $k => $v)
{
if($k == 'user')
$res[$arr['task_id']][$k] = get_arrayvalues_bykeyvalue($result, $k, 'task_id', $arr['task_id']);
else
$res[$arr['task_id']][$k] = $v;
}
}
print_r($res);
Output:
Array
(
[1] => Array
(
[task_id] => 1
[name] => do dishes
[user] => Array
(
[0] => bob
[1] => liam
)
)
[2] => Array
(
[task_id] => 2
[name] => vacuum
[user] => liam
)
[3] => Array
(
[task_id] => 3
[name] => Take out thrash
[user] => Array
(
[0] => liam
[1] => bob
)
)
)
Demo:
http://3v4l.org/2E9nP
Lets assume your data has inside $results;
First make a function which will check task id already inside the new result array.if exists return the index else return flase
function get_index($array,$task_id)
{
foreach($array as $index=>$a)
{
if($a['task_id']==$task_id)
{
return $index;
}
}
return false;
}
Now write follwing code to get your desired result inside $new_result
$new_result=array();//this will contains your desired result
foreach($results as $key=>$result)
{
$index=get_index($new_result,$result['task_id']);
if($index===false)
{
$new_result[]=array('task_id'=>$result['task_id'],'name'=>$result['name'],'users'=>array($result['user']));
}
else
{
$new_result[$index]['users'][]=$result['user'];
}
}
print_r($new_result);//this will output your desired result.
I don't have your model, but how I see it, you can use group_concat
Model :
$this->db->select('t.id_task, t.name, GROUP_CONCAT(u.name SEPARATOR "-") as users')
->from('tasks t')
->join('users_task ut', 't.id_task = ut.id_taks')
->join('user u', 'u.id_user = ut.id_user')
->group_by(t.id_task);
$query = $this->db->get();
return $query->result();
/*Result expected :
Array
(
[1] => Array
(
[task_id] => 1
[name] => do dishes
[users] => "John-Peter"
)
[2] => Array
(
[task_id] => 2
[name] => vacuum
[users] => "Mary-Bob"
)
) */
After that, a simple explode should do the trick in your controller.
Controller :
$tasks = $this->my_model->getTasks();
foreach($tasks as $t)
{
$t->users = explode("-", $t->users);
}
Related
So I'm returning an array of arrays returned from an MySQLi query, as you can see it pulls distinct categories from my layers database and then returns each layer associated with that category.
The results are only returning some results and not all when the code using print_r returns all the layers.
Where am I going wrong?
https://api.adameastwood.com/v1/TEST/logger.php -> results of print_r and foreach loop
$conn = new mysqli($sqlParams['host'], $sqlParams['username'],
$sqlParams['password'], $sqlParams['database']);
$Query = "SELECT DISTINCT `Category` FROM `layers` WHERE `enabled` = 1
ORDER BY `Category` ASC";
$menuCategories = [];
if ($result = $conn->query($Query))
{
while ($categoryRow = $result->fetch_row()){
foreach($categoryRow as $key => $value)
{
$layers = $conn->query("SELECT * FROM `layers` WHERE enabled = 1 AND Category = '$value' ORDER BY LayerName ASC");
while ($layerDetails = $layers->fetch_array()){
$menuItems = array([
'LCategory' => $layerDetails['Category'],
'LayerCode' => $layerDetails['LayerCode'],
'LayerName' => $layerDetails['LayerName'],
]);
foreach($menuItems as $item){
print_r($item);
$item = array('LayerDetails' => $item);
array_push($menuCategories['menuItem'][$value], $item);
foreach($item as $items => $itemValue)
array_push($menuCategories['menuItem'][$value][$itemValue], $itemValue);
}
}
}
}
echo "\n\n";
Array
(
[LCategory] => Council Assets
[LayerCode] => RBC/AL
[LayerName] => Available Land
)
Array
(
[LCategory] => Council Assets
[LayerCode] => RBC/COL
[LayerName] => Council Owned Land
)
Array
(
[LCategory] => Planning
[LayerCode] => DC/CA
[LayerName] => Conservation Areas
)
Array
(
[LCategory] => Planning
[LayerCode] => DC/LB
[LayerName] => Listed Buildings
)
Array
(
[LCategory] => Planning
[LayerCode] => DC/PA
[LayerName] => Planning Applications
)
Array
(
[LCategory] => Planning
[LayerCode] => DC/TPO
[LayerName] => Tree Preservation Orders
)
{
"menuItem": {
"Council Assets": [
{
"LayerDetails": {
"LCategory": "Council Assets",
"LayerCode": "RBC\/COL",
"LayerName": "Council Owned Land"
}
}
],
"Planning": [
{
"LayerDetails": {
"LCategory": "Planning",
"LayerCode": "DC\/LB",
"LayerName": "Listed Buildings"
}
},
{
"LayerDetails": {
"LCategory": "Planning",
"LayerCode": "DC\/PA",
"LayerName": "Planning Applications"
}
},
{
"LayerDetails": {
"LCategory": "Planning",
"LayerCode": "DC\/TPO",
"LayerName": "Tree Preservation Orders"
}
}
]
}
}
Your code has lot of "not-good-practice" behavior - but let address your issue:
Notice that array-push raise warning when null is given and not array (and you never init empty array in key menuItem or the category names). However, it does create an array after the command is sent - therefor, only the first element is left out.
Change you array_push to [] as $menuCategories['menuItem'][$value][] = $item; to make it work as you intend (change both array_push)
I have a list of users in my mongodb database, which can then follow each other -pretty standard. Using php I want to check if a specific user is following another user. My data looks like this.
array (
'_id' => ObjectId("56759157e1095db549d63af1"),
'username' => 'Joe',
'following' =>
array (
0 =>
array (
'username' => 'John',
),
1 =>
array (
'username' => 'Tom',
),
),
)
array (
'_id' => ObjectId("56759132e1095de042d63af4"),
'username' => 'Tom',
'following' =>
array (
0 =>
array (
'username' => 'Joe',
),
1 =>
array (
'username' => 'John',
),
2 =>
array (
'username' => 'Sam',
),
),
)
I want a query that will check if Joe is following Sam (which he's not) - so it would produce no results. However, if I was to query the database to check if Tom was following Sam, then it would produce a result to indicate he is (because he is). Do you know how I would do this in PHP? I've experimented with Foreach loops, but I haven't been able to get the results I want.
Make it by DB query, by php it will take more resources
Still if you want by php you can make it so
$following=false;
foreach($data as $v) if ($v['username'] == 'Joe') {
foreach($v['following'] as $v1) if (in_array('Sam', $v1)) {
$following=true;
break 2;
}
}
echo $following;
Such queries are best done in SQL, but if you insist on a PHP-based solution I would suggest to turn the data structure into items keyed by name. Once you have that it is a piece of cake to find relationships:
function organisePersons($data) {
$persons = array();
foreach($data as $person) {
$list = array();
foreach($person["following"] as $following) {
$list[$following["username"]] = $following["username"];
}
$person["following"] = $list;
$person["followedBy"] = array();
$persons[$person["username"]] = $person;
}
// link back to get all "followedBy":
// You can skip this block if you don't need "followedBy":
foreach ($persons as $person) {
foreach($person["following"] as $following) {
echo $person["username"] . " f. $following<br>";
if (!isset($persons[$following])) {
$persons[$following] = array(
"_id" => null, // unknown
"username" => $following,
"following" => array(),
"followedBy" => array()
);
}
$persons[$following]["followedBy"][] = $person["username"];
}
}
// return the new structure
return $persons;
}
So first call the function above with the data you have:
$persons = organisePersons($data);
And then you can write this:
if (isset($persons["Joe"]["following"]["Sam"])) {
echo "Joe is following Sam"; // not in output
};
if (isset($persons["Tom"]["following"]["Sam"])) {
echo "Tom is following Sam"; // in output
};
But also:
echo "Tom is following " . implode($persons["Tom"]["following"], ", ");
// Output: Tom is following Joe, John, Sam
And even the reverse question "Tom is followed by who?":
echo "Tom is followed by " . implode($persons["Tom"]["followedBy"], ", ");
// Output: Tom is followed by Joe
$db = new PDO("mysql:host=$hostname;dbname=$database", $username, $password);
$items = 'SELECT items FROM menus';
$itemLink = 'SELECT itemLink FROM menus';
$itemQuery = $db->query($items);
$linkQuery = $db->query($itemLink);
$fetchItem = $itemQuery->fetch(PDO::FETCH_ASSOC);
$fetchLink = $linkQuery->fetch(PDO::FETCH_ASSOC);
$merged = array_merge($fetchItem,$fetchLink);
foreach($merged as $key=>$value){
echo "${key} => ${value} <br />";
}
This is what it looks like in the database:
items |itemLink
----------------------
Kill Bill|Kill Bill link
Preman |Preman link
So, the expected output, or at least what I thought must be this:
items => Kill Bill
items => Preman
itemLink => Kill Bill Link
itemLink => Preman Link
But the resulted output from the code is this:
items => Kill Bill
itemLink => Kill Bill Link
It's missing the other items and itemLink
So, how do I achieve the output that I want?
$fetchItem = $itemQuery->fetch(PDO::FETCH_ASSOC);
$fetchLink = $linkQuery->fetch(PDO::FETCH_ASSOC);
This only fetches the first row of each resultset. You need fetchAll:
$fetchItem = $itemQuery->fetchAll(PDO::FETCH_ASSOC);
$fetchLink = $linkQuery->fetchAll(PDO::FETCH_ASSOC);
and adjust the rest of your code.
foreach($merged as $entry) {
foreach( $entry as $key => $value ) {
echo "${key} => ${value} <br />";
}
}
EDIT:
The call of fetch only retrieved the first row of the resultset, whereas fetchAll parses the complete resultset into an Array. So the Objects look like this afterwards:
Array(
[0] => { 'items' => 'Kill Bill' },
[1] => { 'items' => 'Preman' }
)
Array(
[0] => { 'itemLink' => 'Kill Bill' },
[1] => { 'itemLink' => 'Preman' }
)
array_merge concatenate both arrays to the following:
Array(
[0] => { 'items' => 'Kill Bill' },
[1] => { 'items' => 'Preman' },
[2] => { 'itemLink' => 'Kill Bill' },
[3] => { 'itemLink' => 'Preman' }
)
So we now have a two dimensional array. To traverse the values we need first to select each $entry, which is done in the outer foreach and can afterwards access the key/value structure in the inner foreach.
As pointed out in the other comment: If you want to preserve the connection between itemsand itemLink, you should change the query in the first place to
SELECT items, itemLink FROM menus
You can use simple array_combine() function to do what you are trying to do now.
$merged = array_combine($fetchItem, $fetchLink);
This will make all the item from $fetchItem as keys to the item from $fetchLink.
Is there a better way of doing this PHP code? What I'm doing is looping through the array and replacing the "title" field if it's blank.
if($result)
{
$count = 0;
foreach($result as $result_row)
{
if( !$result_row["title"] )
{
$result[$count]["title"] = "untitled";
}
$count++;
}
}
Where $result is an array with data like this:
Array
(
[0] => Array
(
[title] => sdsdsdsd
[body] => ssdsd
)
[1] => Array
(
[title] => sdssdsfds
[body] => sdsdsd
)
)
I'm not an experienced PHP developer, but I guess that the way I've proposed above isn't the most efficient?
Thanks
if($result) {
foreach($result as $index=>$result_row) {
if( !$result_row["title"] ) {
$result[$index]["title"] = "untitled";
}
}
}
You don't need to count it. It's efficient.
if ($result)
{
foreach($result as &$result_row)
{
if(!$result_row['title'])
{
$result_row['title'] = 'untitled';
}
}
}
Also, you may want to use something other than a boolean cast to check the existence of a title in case some young punk director releases a movie called 0.
You could do something like if (trim($result_row['title']) == '')
Mixing in a little more to #Luke's answer...
if($result) {
foreach($result as &$result_row) { // <--- Add & here
if($result_row['title'] == '') {
$result_row['title'] = 'untitled';
}
}
}
The key is the & before $result_row in the foreach statement. This make it a foreach by reference. Without that, the value of $result_row is a copy, not the original. Your loop will finish and do all the processing but it won't be kept.
The only way to get more efficient is to look at where the data comes from. If you're retrieving it from a database, could you potentially save each record with an "untitled" value as the default so you don't need to go back and put in the value later?
Another alternative could be json_encode + str_replace() and then json_decode():
$data = array
(
0 => array
(
'title' => '',
'body' => 'empty',
),
1 => array
(
'title' => 'set',
'body' => 'not-empty',
),
);
$data = json_encode($data); // [{"title":"","body":"empty"},{"title":"set","body":"not-empty"}]
$data = json_decode(str_replace('"title":""', '"title":"untitled"', $data), true);
As a one-liner:
$data = json_decode(str_replace('"title":""', '"title":"untitled"', json_encode($data)), true);
Output:
Array
(
[0] => Array
(
[title] => untitled
[body] => empty
)
[1] => Array
(
[title] => set
[body] => not-empty
)
)
I'm not sure if this is more efficient (I doubt it, but you can benchmark it), but at least it's a different way of doing the same and should work fine - you have to care about multi-dimensional arrays if you use the title index elsewhere thought.
Perhaps array_walk_recursive:
<?php
$myArr = array (array("title" => "sdsdsdsd", "body" => "ssdsd"),
array("title" => "", "body" => "sdsdsd") );
array_walk_recursive($myArr, "convertTitle");
var_dump($myArr);
function convertTitle(&$item, $key) {
if ($key=='title' && empty($item)) {$item = "untitled";}
}
?>
If you want sweet and short, try this one
$result = array(
array(
'title' => 'foo',
'body' => 'bar'
),
array(
'body' => 'baz'
),
array(
'body' => 'qux'
),
);
foreach($result as &$entry) if (empty($entry['title'])) {
$entry['title'] = 'no val';
}
var_dump($records);
the empty() will do the job, see the doc http://www.php.net/manual/en/function.empty.php
I have the following code (I know that this code is not optimized but it's not for discussion):
function select_categories($cat_id)
{
$this->db = ORM::factory('category')
->where('parent', '=', $cat_id)
->find_all();
foreach ($this->db as $num => $category)
{
if($category->parent == 0)
{
$this->tmp[$category->parent][$category->id] = array();
}
else {
$this->tmp[$category->parent][$category->id] = array();
}
$this->select_categories($category->id);
}
return $this->tmp;
}
Function returns this array:
array(3) (
0 => array(2) (
1 => array(0)
2 => array(0)
)
2 => array(1) (
3 => array(0)
)
3 => array(2) (
4 => array(0)
5 => array(0)
)
)
But how should I change the code
else {
$this->tmp[$category->parent][$category->id] = array();
// ^^^^^^^^^^^^^^^^^^^^^^ (this bit)
}
To merge array[3] to array[2][3] for example (because array[3] is a subdirectory of array[2] and array[2] is a subdirectory of array[0][2]), so, I need to make this (when I don't know the level of subdirectories):
array (
0 => array (
1 => array
2 => array (
3 => array (
4 => array
5 => array
)
)
)
)
A long time ago I wrote some code to do this in PHP. It takes a list of entities (in your case, categories) and returns a structure where those entities are arranged in a tree. However, it uses associative arrays instead of objects; it assumes that the “parent” ID is stored in one of the associative array entries. I’m sure that you can adapt this to your needs.
function make_tree_structure ($nontree, $parent_field)
{
$parent_to_children = array();
$root_elements = array();
foreach ($nontree as $id => $elem) {
if (array_key_exists ($elem[$parent_field], $nontree))
$parent_to_children [ $elem[$parent_field] ][] = $id;
else
$root_elements[] = $id;
}
$result = array();
while (count ($root_elements)) {
$id = array_shift ($root_elements);
$result [ $id ] = make_tree_structure_recurse ($id, $parent_to_children, $nontree);
}
return $result;
}
function make_tree_structure_recurse ($id, &$parent_to_children, &$nontree)
{
$ret = $nontree [ $id ];
if (array_key_exists ($id, $parent_to_children)) {
$list_of_children = $parent_to_children [ $id ];
unset ($parent_to_children[$id]);
while (count ($list_of_children)) {
$child = array_shift ($list_of_children);
$ret['children'][$child] = make_tree_structure_recurse ($child, $parent_to_children, $nontree);
}
}
return $ret;
}
To see what this does, first try running it on a structure like this:
var $data = array (
0 => array('Name' => 'Kenny'),
1 => array('Name' => 'Lilo', 'Parent' => 0),
2 => array('Name' => 'Adrian', 'Parent' => 1)
3 => array('Name' => 'Mark', 'Parent' => 1)
);
var $tree = make_tree_structure($data, 'Parent');
If I’m not mistaken, you should get something like this out: (the “Parent” key would still be there, but I’m leaving it out for clarity)
array (
0 => array('Name' => 'Kenny', 'children' => array (
1 => array('Name' => 'Lilo', 'children' => array (
2 => array('Name' => 'Adrian')
3 => array('Name' => 'Mark')
)
)
)
Examine the code to see how it does this. Once you understand how this works, you can tweak it to work with your particular data.
Assuming you dont want any data/children tags in your array:
foreach ($this->db as $num => $category)
{
// save the data to the array
$this->tmp[$category->id] = array();
// save a reference to this item in the parent array
$this->tmp[$category->parent][$category->id] = &$this->tmp[$category->id];
$this->select_categories($category->id);
}
// the tree is at index $cat_id
return $this->tmp[$cat_id];
If you just need to retrieve the full tree out of the database, you can even simplify your query (get all records at once) and remove the recursive call in this function. You will need an extra check that will only set the $this->tmp[$catagory->id] when it does not exist and else it should merge the data with the existing data.