Multidimensional array without repeating - php

I have two MySQL tables, one is called "version" and the other is called "lines" inside one DB called "project".
The "version" table consist of:
id (PRIMARY / AI) = int
version = string
Rows:
# | version
------------
0 | 100
1 | 200
3 | 400
The "lines" table consist of:
id (PRIMARY / AI) = int
lines = string
version_id = string (ID from table version)
Rows:
# | line | version_id
--------------------------
0 | line #1 | 0
1 | line #2 | 0
2 | line #3 | 1
3 | line #4 | 0
4 | line #5 | 1
How can I create multidimensional array to output an example JSON (pseudo)
"full" =>
"version" => "100"
"id" => "0", (version id table)
"line" =>
"row_0" => "line #1", (from lines table)
"row_1" => "line #2",
"row_2" => "line #4",
"version" => "200"
"id" => "1",
"line" =>
"row_0" => "line #3",
"row_1" => "line #5",
"version" => "300"
"id" => "3",
"line" => "EMPTY" (no lines for this version)
]
I rewrote the code a couple of times but I can't make it work. Either I stuck or I finish in infinite loop of errors. This is what I got for now:
function returnJson() {
$db = DB::query("SELECT * FROM version");
foreach ($db as $row) {
$i++;
$lines = DB::Query("SELECT * FROM lines WHERE version_id=%i", $row['id']);
// approach to nested array?
}
}
I'm using MeekroDB so any approach to MySQL is offset. You can write an example in PDO if you are more familiar with it.

I assume that the array you want would look like this in php:
"full" =>
"100" => array (
"versionId" => "0", (version id table)
"line" =>
"row_0" => "line #1", (
"row_1" => "line #2",
"row_2" => "line #4"
)
, "200" => array (
"versionId" => "1",
"line" => array (
"row_0" => "line #3",
"row_1" => "line #5" )
)
, "300" => array (
"versionId" => "3",
"line" => array()
)
]
Use a JOIN
SELECT v.id AS versionId, v.version l.id as linesId, l.lines
FROM version v
INNER JOIN lines l ON v.id = l.version_id
And then a loop with some if statement to build the array
$versions = array();
foreach($db as $row) {
if (!isset($versions[$db["version"]]))
$versions[$db["version"]] = array (
"versionId" => $db["versionId"],
"line" => array()
);
if (!empty($db["lines"]))
$versions[$db["version"]][lines"][] = $db["lines"];
}

Try the accepted answer in this SO post which also deals with nested JSON data.
Also you may want to reduce your SQL to below and just use one loop instead of 2 nested loops as in the SO post above.
SELECT *
FROM version
INNER
JOIN lines
ON version.id = lines.version_id
Hope this helps.

Related

Add non existings columns to mysql query result

I have a PHP variable $members and it looks like this
$members = [
[
"name" => "John",
"age" => 23
],
[
"name" => "Sasha",
"age" => 24
],
[
"name" => "Asmita",
"age" => 23
]
];
Now my application stores rates based on the age of people so there is a rates table with the following structure
id
age
price
1
23
200
2
24
230
3
25
250
So, I am looping through all members and get this final query
SELECT * FROM rates WHERE age =23 OR age=24 OR age = 23
It returns me the following result
id
age
price
1
23
200
3
24
230
I want to know if there is some way I can add the member to the result set so it returns results for all 3 members like this using MYSQL itself
id
member
age
price
1
John
23
200
2
Sasha
24
230
1
Asmita
23
200
SQL cannot read your PHP variable. It can only read data from tables in the database, or literal values in the query. So if your data is only in the PHP variable $members (i.e. not stored in the database), this will get tricky.
The easiest solution is to query your rates table into another PHP variable, and then merge them. You can limit the result to the age values in your input data.
<?php
# form input from $_POST
$members = [
[
"name" => "John",
"age" => 23
],
[
"name" => "Sasha",
"age" => 24
],
[
"name" => "Asmita",
"age" => 23
]
];
# make list of ages appearing in the input
$ages = array_map(function($m) { return($m['age']); }, $members);
# list of '?' placeholders, equal in length to the number of ages
$placeholders = implode(',', array_fill(1, count($ages), '?'));
# connect to database
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', '');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
# fetch the rates for all specified ages
$stmt = $pdo->prepare("SELECT age, price from rates WHERE age IN ($placeholders)");
$stmt->execute($ages);
$ratesResult = $stmt->fetchAll();
# transform the result into an associative array, mapping age to price
$rates = [];
foreach ($ratesResult as $r) {
$rates[$r['age']] = $r['price'];
}
# merge the rates into the original array of input data
foreach ($members as &$m) {
$m['price'] = $rates[$m['age']];
}
print_r($members);
Output:
Array
(
[0] => Array
(
[name] => John
[age] => 23
[price] => 200.00
)
[1] => Array
(
[name] => Sasha
[age] => 24
[price] => 230.00
)
[2] => Array
(
[name] => Asmita
[age] => 23
[price] => 200.00
)
)
This should do what you want;
$members = [
[
"name" => "John",
"age" => 23
],
[
"name" => "Sasha",
"age" => 24
],
[
"name" => "Asmita",
"age" => 23
]
];
$ages = collect($members)->pluck('age')->unique();
$rates = Rate::whereIn('age',$ages)->get();
$merged = collect($members)->map(function($member) use($rates) {
$member['price'] = $rates->where('age', $member['age'])->first()->price;
return $member;
});
Gets the unique ages, then queries the database for the rates, then maps over the input array, adding in the price to each member

How to execute that in 1 query?

I have 2 tables customer and orders:
customers:
| custID | Name | Age |
|--------|-------|-----|
| 1 | Peter | 23 |
| 2 | Julie | 34 |
| 3 | Tom | 45 |
orders:
| custID | product | color |
|--------|---------|-------|
| 1 | shirt | blue |
| 1 | jacket | black |
| 2 | jacket | green |
| 3 | hat | grey |
| 3 | shirt | white |
I now want to get all customers and their orders, ordered as a list. So something like that:
Array
(
[0] => Array
(
[ID] => 1
[name] => Peter
[age] => 23
[orders] => Array
(
[0] => Array
(
[product] => shirt
[color] => blue
)
[1] => Array
(
[product] => jacket
[color] => black
)
)
)
[1] => Array
(
[ID] => 2
[name] => Julie
[age] => 34
[orders] => Array
(
[0] => Array
(
[product] => jacket
[color] => green
)
)
)
[2] => Array
(
[ID] => 3
[name] => Tom
[age] => 45
[orders] => Array
(
[0] => Array
(
[product] => hat
[color] => grey
)
[1] => Array
(
[product] => shirt
[color] => white
)
)
)
)
When I do:
SELECT name, age, product, color
FROM `customers`, `orders`
where `customers`.`id` = `orders`.id
group by name
I get:
| name | age | product | color |
|-------|-----|---------|-------|
| Peter | 23 | jacket | green |
| Julie | 34 | shirt | blue |
| Tom | 45 | hat | grey |
Is this even possible with only one query?
You could simply make the query below:
SELECT *
FROM customers
JOIN orders
USING custID
GROUP BY Name
ORDER BY custID ASC;
A couple of steps here...
First, you should run the following query:
SELECT
`customers`.`id`,
`customers`.`name`,
`customers`.`age`,
`orders`.`product`,
`orders`.`color`
FROM `customers`, `orders`
where `customers`.`id` = `orders`.`id`
order by `customers`.`id`
Which should give you de-normalized tabular data that looks something like this:
$array = array(
array("id" => 1, "name" => "Peter", "age" => 23, "product" => "shirt", "color" => "blue"),
array("id" => 1, "name" => "Peter", "age" => 23, "product" => "jacket", "color" => "black"),
array("id" => 2, "name" => "Julie", "age" => 34, "product" => "jacket", "color" => "green"),
array("id" => 3, "name" => "Tom", "age" => 45, "product" => "hat", "color" => "grey"),
array("id" => 3, "name" => "Tom", "age" => 45, "product" => "shirt", "color" => "white")
);
You can then transform the that data into your desired format as follows:
$transformed = array();
$i = 0;
while ($i < count($array)) {
$id = $array[$i]["id"];
$name = $array[$i]["name"];
$age = $array[$i]["age"];
$products = array();
while ($i < count($array) && $id == $array[$i]["id"]) {
array_push($products, array("product" => $array[$i]["product"], "color" => $array[$i]["color"]));
$i++;
}
array_push($transformed, array("id" => $id, "name" => $name, "age" => $age, "products" => $products));
}
http://sandbox.onlinephpfunctions.com/code/6fe856e1f71f699e84215b6f66d25589f71e255e
i think you should user INNER JOIN:
SELECT * FROM customers AS c INNER JOIN orders AS o ON c.custID = o.custID GROUP BY c.custID ORDER BY c.custID ASC;
There is no point in trying to achieve the desired outcome with single query. With the first query get the list of customers. Then perform a second query, which will load all the orders for the customers from the first query. And in a loop match orders to respective customers.
EDIT: something like this
$result = [];
$customers = $pdo->query("SELECT * FROM `customers`")->fetchAll();
foreach ($customers as $c) {
$result[$c['id']] = $c;
}
$orders = $pdo->query("SELECT * FROM `orders` WHERE `custID` IN (".implode(', ', array_keys($result).")");
foreach ($orders as $o) {
if (!isset($result[$o['custId']]['orders']))
$result[$o['custId']]['orders'] = [];
$result[$o['custId']]['orders'][] = $o;
}
Your sql SELECT name, age, product, color FROMcustomers,orderswherecustomers.id=orders.id group by name is fine, just add the customer id and order id also in the sql. Then, in PHP, as you iterate the result set, populate the customer info first (id, name, age) with customer id as the key and name, age as value as an array. Likewise, populate the order for that customer in the key 'orders' with the order id as key and value as an array (product, color).
Once you have the array populated, iterate over that array and keep putting things into a new array since the output you want is an array with sequential keys (0, 1, 2 etc) instead of customer id.
$initialList = array();
while($row = $result->fetch_assoc()) {
if(!array_key_exists($row['customer_id'], $initialList)) {
$initialList[$row['customer_id']] = array(
'name' => $row['name'],
'age' => $row['age'],
'orders' => array()
);
}
$initialList[$row['customer_id']]['orders'][] = array(
'product' => $row['product'],
'color' => $row['color']
);
}
$finalList = array();
foreach($initialList as $customerId => $customer) {
$customer['ID'] = $customerId;
$finalList[] = $customer;
}
//to print and check the array
print_r($finalList);
try this:
SELECT c.custID,c.name, c.age, group_concat(CONCAT_WS(':',o.product,o.color)SEPARATOR ',') as productos
FROM customers AS c
INNER JOIN orders AS o ON c.custID = o.custID
GROUP BY c.custID;
You just have to parse the products.
$array=array();
while($r = $res->fetch_assoc()) {
$id=$row['custID'];
$name=$row['name'];
$age=$row['age'];
$productos=$row['productos'];
$productsSeparats = explode(',',$productos);
$orders=array();
foreach ($productsSeparats as $value) {
$ar=explode(':',$value);
array_push($orders, array("product" => $ar[0], "color" => $ar[1]));
}
array_push($array, array("id" => $id, "name" => $name, "age" => $age, "orders" => $orders));
}

Calculate values of deep nested multi dimensional arrays in reverse

I need to calculate reward for employees in my company.
I have this structure (for example):
And in PHP it represents the following array:
<?php
$structure = [
"A" => [
"B" => [
"E" => [
"M" => null
],
"F" => [
"N" => [
"T" => null
],
"O" => null
],
"G" => [
"P" => null,
"Q" => [
"U" => null,
"V" => [
"X" => null,
"Y" => [
"3" => [
"4" => [
"4" => null,
"6" => null,
"7" => [
"8" => null
],
]
]
],
"Z" => null
]
]
],
"H" => null
],
"C" => [
"I" => null,
"J" => [
"R" => null
],
"K" => [
"S" => [
"W" => [
"1" => null,
"2" => null,
]
]
]
],
"D" => [
"L" => null
]
]
];
I need to calculate the reward for each employee. The end subordinates have reward only from their own work. But the seniors who have other subordinates have reward from their own work + works their subordinates.
For example:
Person A has own reward 10.
Person D has own reward 20.
Person L has own reward 15.
In the final,
Person L has final reward 15 (is final).
Person D has final reward 20 + 15 = 35 (D + L).
Person A has final reward 10 + 35 (A + D).
The calculation must be carried out below, however, the network can be arbitrarily deep. Calculating I would like to split into several parts. (For performance reasons)
The spider, which revises the structure to the appropriate format.
Calculate reward for each node.
Send information about each node via email.
I do not know how to proceed across the structure. Or to reorganize the structure to undergo easier. Can you think of anything?
I am grateful for you. Thanks!
#Martin
// EDIT: raw database
| id | parent | name
---------------------
| 1 | null | Martin
| 2 | null | Peter
| 3 | 1 | John
| 4 | 3 | Jack
// EDIT: new data structure:
[
"A" => [
"points" => 20,
"childs" => [
"B" => [
"points" => 10,
"childs" => [
"C" => [
"points" => 50,
"childs" => null
]
]
],
"D" => [
"points" => 30,
"childs" => [
"4" => [
"points" => 40,
"childs" => null
]
]
]
]
]
]
This can be done by traversing through the structure recursively from the inside-out, and stores the rewards for each employee it can find in a flattened 2D array.
It is dependent on each leaf of the structure having a starting value, as it needs a base value to calculate back up the tree on.
Using RecursiveIteratorIterator with the CHILD_FIRST flag allows you to loop through the array 'backwards' which is what we want in this case as that is where the starting rewards are. As we go through the tree, we obtain the subordinates rewards, add it to the current employee, and continue .. so by the time we get back to the top of the structure, we have calculated all employees.
Storing the result in a flattened array is then much easier to use and manipulate further along your logic.
Assumed starting structure:
// Each leaf has a value (random for example sake)
$structure = [
"A" => [
"B" => [
"E" => [
"M" => 10
],
"F" => [
"N" => [
"T" => 15
],
"O" => 5
],
"G" => [
"P" => 40,
"Q" => [
"U" => 30,
"V" => [
"X" => 35,
"Y" => [
"3" => [
"4" => [
"5" => 5,
"6" => 10,
"7" => [
"8" => 20
],
]
]
],
"Z" => 30
]
]
],
"H" => 15
],
"C" => [
"I" => 25,
"J" => [
"R" => 25
],
"K" => [
"S" => [
"W" => [
"1" => 40,
"2" => 50,
]
]
]
],
"D" => [
"L" => 15
]
]
];
Calculation:
// Iterate through the structure from the outside-in (child/leaves first)
$data = new RecursiveArrayIterator($structure);
$dataIt = new RecursiveIteratorIterator($data, RecursiveIteratorIterator::CHILD_FIRST);
$rewards = [];
foreach ($dataIt as $value) {
$subKeys = [];
$rewards[$dataIt->key()] ?? $rewards[$dataIt->key()] = 0; // Suppress any undefined index errors
if (is_array($value)) {
$subKeys = array_keys($value);
// traverse through all branches to obtain the existing reward values for subordinates
array_walk_recursive($value, function($reward, $person) use (&$subKeys) {
$subKeys[] = $person;
});
$subKeys = array_unique($subKeys);
foreach ($subKeys as $employee) {
$rewards[$dataIt->key()] += $rewards[$employee];
}
} else {
$rewards[$dataIt->key()] += $value;
}
}
print_r($rewards);
Return/final array:
Array
(
[M] => 10
[E] => 10
[T] => 15
[N] => 15
[O] => 5
[F] => 35
[P] => 40
[U] => 30
[X] => 35
[4] => 60
[6] => 10
[8] => 20
[7] => 20
[3] => 90
[Y] => 180
[Z] => 30
[V] => 335
[Q] => 520
[G] => 745
[H] => 15
[B] => 1060
[I] => 25
[R] => 25
[J] => 25
[1] => 40
[2] => 50
[W] => 90
[S] => 180
[K] => 270
[C] => 435
[L] => 15
[D] => 15
[A] => 1935
)
To get the number of employees under each node, you can do that easily in one line using count:
$employeesPersonA = count($structure['A'], COUNT_RECURSIVE); // 33
$employeesPersonC = count($structure['A']['C'], COUNT_RECURSIVE); // 8
$employeesPersonK = count($structure['A']['C']['K'], COUNT_RECURSIVE); // 4
EDIT:
For your database structure, you cannot get a multi dimensional result set out of the database so your only option there is to go through the result set and build your structure in PHP from that.
Here's one example, simplified to consider only nodes, 'A','B','C',D', & 'L'...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(user CHAR(1) NOT NULL PRIMARY KEY
,lft INT NOT NULL
,rgt INT NOT NULL
);
INSERT INTO my_table VALUES
('A',1,10),
('B',2,3),
('C',4,5),
('D',6,9),
('L',7,8);
SELECT y.user
, GROUP_CONCAT(x.user ORDER BY x.lft) nodes
FROM my_table x
JOIN my_table y
ON x.lft BETWEEN y.lft AND y.rgt
GROUP
BY y.user
ORDER
BY y.lft;
+------+-----------+
| user | nodes |
+------+-----------+
| A | A,B,C,D,L |
| B | B |
| C | C |
| D | D,L |
| L | L |
+------+-----------+

Start the loop from the first after few iterations in foreach PHP

For example, I have 7 channels and 4 groups. This should be distributed in the following way:
Channel 1 - Group 1
Channel 2 - Group 2
Channel 3 - Group 3
Channel 4 - Group 4
Channel 5 - Group 1
Channel 6 - Group 2
Channel 7 - Group 3
How can I distribute in This way?
I used forloop as following
$groups = //array outputs the following
Array
(
[0] => Array
(
[id] => 10
[email] => susannec#methodfuel.com
)
[1] => Array
(
[id] => 17
[email] => air#tel.com
)
[2] => Array
(
[id] => 34
[email] => venu#methodfuel.com
)
[3] => Array
(
[id] => 62
[email] => varadaraj999#gmail.com
)
)
Now,
I am having a query
foreach ($query as $key => $value) {
$usrSql = "UPDATE issues SET user_id = ".$usersAssigned[$key]['id']." WHERE id =".$value['id'];
// DB updating process
}
For 4 iterations it is working fine, from 5th iteration I am getting an error:
Undefined offset: 4
How do I solve this problem? After four iteration The loop should start from first.
Update: You will have to use an external variable to keep track of the current index of the group element being accessed and reset it to zero when it has gotten to the end of the group.
Is this what you are looking for?
$channels = array("Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7");
$group_id = 0;
$groups = array( array("id"=>3, "email"=>"email_1"), array("id"=>10, "email"=>"email_2"), array("id"=>6, "email"=>"email_3"), array("id"=>71, "email"=>"email_4"));
foreach($channels as $channel)
{
echo $channel." - Group ".$groups[$group_id]["id"]."\n"; // print out the channel and the group id
$group_id ++;
if($group_id > 0 && $group_id % (count($groups)) == 0)
$group_id = 0;
}

unable to merge variables with array_merge: undefined index

I am trying to combine two variables that retrieves information from MySQL.
I have been suggested to use array_merge(), and it seems to work for the most part. I keep getting Warning: Illegal string offset after all the results has been returned from the database. Interesting is that the first 8 (the query has a LIMIT of 8) are error clean, after the 8 results has been printed, then a huge list appears with that error.
query
articleClass.php
public function latestArticles()
{
$sth = $this->db->prepare("SELECT * FROM articles
WHERE article_uid = article_uid
ORDER BY article_uid DESC LIMIT 8");
$sth->execute();
$row = $sth->fetchAll();
return $row;
}
public function articleTags()
{
$sth = $this->db->prepare("SELECT a.*, b.*
FROM articles a, article_tags b
WHERE b.tag_id = a.article_uid
");
$sth->execute();
$row = $sth->fetchAll();
return $row;
}
printing code
index.php
include 'libraries/articleClass/articleClass.php';
$articleClass = new articleClass();
$latestArticles = $articleClass->latestArticles();
$articleTags = $articleClass->articleTags();
foreach(array_merge($latestArticles, $articleTags) as $data)
{
$first_uid = $data['article_uid'];
$first_image = $data['article_image'];
$first_title = $data['article_title'];
$first_content = $data['article_content'];
$first_created = gmdate("d M Y", $data['article_created']);
$first_tags = $data['tag_name'];
echo '
<article>
<img src="path-to-image/'.$first_image.'"/>
<h1>'.$first_title.'</h1>
<p>'.$first_content.'</p>
<ul>
<li>'.$first_tags.'</li>
</ul>
</article>
';
}
Once index.php is loaded, 8 articles are printed on the page as they should, but I get :
Notice: Undefined index: tag_name in /var/www/new-design/index.php on line 74
Trial & (mostly) Failures
If I change public function article_tags to Fetch instead of FetchAll I get these errors:
Warning: array_merge(): Argument #2 is not an array in /var/www/new-design/index.php on line 67
Warning: Invalid argument supplied for foreach() in /var/www/new-design/index.php on line 67
I am unable to figure out how to succeed with this, any leads would be great. I've been at it since morning!
UPDATE
article_tags table
+--------------------------------+
| tag_id | article_id | tag_name |
+--------------------------------+
| 1 | 8 | awesome |
| 2 | 8 | sweet |
| 3 | 8 | gross |
+--------------------------------+
there is only one article_id corresponding to the articles that are being called, yet this article still receives undefined index: tag_name of course because in the array_merge they haven't been merged at all.
What happens here is that
array_merge($latestArticles, $articleTags)
simply appends one array to the other, meaning:
The first 8 entries in the resulting array are your articles (these have the "tag_name" field set).
The rest is filled with the contents of $articleTags (those don't have "tag_name" field - causing your error).
Here's some code to illustrate that (Indices 0 to 2 are your articles while 3 to 5 are your tags, notice how indices 3-5 of the resulting array don't have the tag_name field):
$latestArticles = array(
array("article_title" => "Some Title 1", "tag_name" => "SomeTag"),
array("article_title" => "Some Title 2", "tag_name" => "SomeOtherTag"),
array("article_title" => "Some Title 3", "tag_name" => "SomeTag"),
);
$articleTags = array(
array("name" => "SomeTag", "somefield" => "foo", "otherfield" => "bar"),
array("name" => "SomeTagOtherTag", "somefield" => "baz", "otherfield" => "test"),
array("name" => "YetAnotherTag", "somefield" => "test2", "otherfield" => "test3")
);
$result = array_merge($latestArticles, $articleTags);
print_r($result);
/** Resulting Array:
Array
(
[0] => Array
(
[article_title] => Some Title 1
[tag_name] => SomeTag
)
[1] => Array
(
[article_title] => Some Title 2
[tag_name] => SomeOtherTag
)
[2] => Array
(
[article_title] => Some Title 3
[tag_name] => SomeTag
)
[3] => Array
(
[name] => SomeTag
[somefield] => foo
[otherfield] => bar
)
[4] => Array
(
[name] => SomeTagOtherTag
[somefield] => baz
[otherfield] => test
)
[5] => Array
(
[name] => YetAnotherTag
[somefield] => test2
[otherfield] => test3
)
)
*/

Categories