Aggregating an array and displaying the aggregation result alongside the detail - php

I have a $department variable, which is an instance of a Department class.
The Department class has a property called $employees that contains an array of Employee instances related to that department (retrieved from the database).
The Employee class has a properties called $salary_amount and $job_type_id. The $employees array elements are ordered together by $job_type_id.
For a specified department, I'm outputting a list of employees in that department like so:
Employees for IT Department
----------------------------
<?php foreach($department->employees as $employee): ?>
<?php echo $employee->name; ?>
<?php echo $employee->salary_amount; ?>
<?php endforeach; ?>
What's an efficient way to manipulate $department->employees so I can display a sum-total of the $salary_amount for each $job_type_id alongside the list of employees?
Employees for IT Department
----------------------------
## Help Desk ##
Joseph Lazar
$47,000
John Q. Smith
$15,444
--> Total Salary for Help Desk: $62,444
## Database Administrator ##
Piet Pietersen
$55,120
Ivan Horvat
$63,750
--> Total Salary for Help Desk: $118,870
Current Approach
Currently, $employees is being populated by a single SQL query, ordering the results how I want. I'm then running a separate SQL query to get an array of $salary sum-totals for each $job_type_id using a technique from another question.
However, the resultset from this 2nd query is stored in a different variable $subtotals. Therefore, I still have the problem of efficiently getting the Sum-Total to display "in-context" with the list of employees.
I happen to be using Yii framework, however that's probably irrelevant unless I should think about or approach this type of problem from a totally different perspective? Please tell me in the comments.
Update 1
Here's the current code I'm using, however it feels messy and like there could be a better way. As mentioned above, the aggregation is done by a separate SQL, however I'm open to alternatives.
<?php
$employee_key = 0;
$employees_count = sizeof($department->employees);
$type_sum_key = 0;
$current_type_in_focus = null;
$next_type_in_focus = null;
foreach($department->employees as $employee)
{
$current_type_in_focus = $employee->job_type_id;
$next_type_in_focus = (isset($department->employees[$employee_key+1])) ? $department->employees[$employee_key+1]->job_type_id : null;
# output individual employee data
echo $employee->name;
echo $employee->salary_amount;
# output aggregated total for job type
if ( $current_type_in_focus !== $next_type_in_focus || $employee_key+1 == $employees_count )
{
echo '--> Total Salary for ' . $employee->jobType->name . ': ' . $department->subtotals[$type_sum_key]->total;
$type_sum_key++;
}
$income_key++;
}
?>

One approach would be to use while loops. For example (for clarity the following code sample does not format the output, also it outputs job_type_id rather than a description as that data is not shown in the question):
$employees = $department->employees;
$employee = current($employees);
while($employee)
{
$job_type_id = $employee->job_type_id;
$total_salary = 0;
echo "## Job Type $job_type_id ##";
while ($employee && $employee->job_type_id == $job_type_id)
{
echo $employee->name;
echo $employee->salary_amount;
$total_salary += $employee->salary_amount;
$employee = next ($employees);
}
echo "-->Total Salary for Job Type $job_type_id: $total_salary";
}

Related

Output by Group - maybe foreach?

I am on a project where I put JSON data from a external API to the MySQL database. This works fine.
The output with GROUPING and ORDER BY is not really how I want it
What I got so far :
SELECT *, GROUP_CONCAT(DISTINCT (task))
FROM time_summary
GROUP BY projectName
ORDER BY clientName
The table looks like this:
id clientName project task
1 firm One online banner
2 firm One print folder
3 firm Two water with gas
4 firm One online website
5 firm Two water with gas
6 firm Two water with gas
Output is:
firmOne
online
banner, website
print
folder
Now the Question:
The grouped tasks are seperated by commas.
I have a while loop output - but I need each task in a new line (grouped by the project) like
firmOne
online
banner website
print
folder
PHP Code is like this one:
$new = 1;
$last_client = 'initial';
while($row = _mysql_fetch_array($sql)) {
if($last_client != $row['clientName'] && $last_client != 'initial') {
echo '<div class="cb h20"></div>';
$new = 1;
}
if($new == 1) {
echo '<span class="yellow-small">' . $row["clientName"] . '</span>';
$new = 0;
}
?>
<b><?=$row['projectName'];?></b><br>
<?=$row['GROUP_CONCAT(DISTINCT (task))'];?>
}
How can I achieve this?
Aggregating the tasks into CSV only to then split them apart again in your PHP code does not sound like a good option to me. Would the following simple query not give you the data you want:
SELECT DISTINCT
clientName,
project,
task
FROM time_summary
ORDER BY
clientName,
project,
task
This query would report, for each client and project, only the distinct list of tasks. Now you can iterate over this result set in your PHP code to generate the output you want:
$client = null;
$project = null;
while ($row = _mysql_fetch_array($sql)) {
$curr_client = $row['clientName'];
if ($curr_client != $client) {
echo $curr_client."<br>";
$client = $curr_client;
}
$curr_project = $row['projectName'];
if ($curr_project != $project) {
echo " ".$curr_project."<br>";
$project = $curr_project;
}
echo " ".$row['task']."<br>";
}
Output:
As a disclaimer, I focused on the query and presentation logic needed to get the display you wanted, not on the PHP code. That might be the topic of another discussion altogether.
Here is a link to a small PHP demo which shows that the presentation logic is reliable:
Rextester
If you simply add the line
$holder = str_replace(",", "<br>", $row['GROUP_CONCAT(DISTINCT (taskName))']);
Right before your
<?=$row['GROUP_CONCAT(DISTINCT (taskName))'];?>
And then replace your line
<?=$row['GROUP_CONCAT(DISTINCT (taskName))'];?>
with
<?=$holder;?>
This replaces the comma with a <br> and will force the second value to the next line.

PHP: Linking two tables to show data correctly using MySQL

I have two tables connected in a different way than I want them, and I will try my best to explain what I am searching for, together with what I have accomplished myself so far.
What I am searching for:
First of all, I have a table objects_price and a table materials. These tables are shown beneath:
objects_price:
materials:
Okay so I have some objects which can be a chair, table or whatever furniture you can imaging. All objects are connected to a third table with information about the object. Each object requires some sort of material in order to be crafted. As you can see in the objects_price table, the object with an id of 1 requires 47 of the material equal to 1. Which is Oak Log as you can see in the materials table.
The object with an id of 2 requires two materials, material 2 and 3. and it requires 20 of Stone and 30 of Birch log
To make everything a little more clear, heres an image displaying what I just explained:
What I have so far is two queries where one of them doesn't do what I want it to, especially because I am using an array to display the materials and the amount but what my code does is that it displays all of the materials in all of the objects, it looks like this right now:
The problem lies in this query and the way it creates an array:
$query = 'SELECT objects_price.object_id, materials.material_id, materials.name, material_amount
FROM objects_price
LEFT JOIN materials USING(material_id)';
$result = $mysqli->query($query);
$arr = array();
while($row = $result->fetch_assoc()) {
$arr[$row['name']][] = $row['material_amount'];
}
together with the way I provide the objects with their corresponding materials:
foreach($arr as $name => $objects) {
?>
<p><?php echo $name; ?><i style="float:right;"><?php foreach($objects as $material_amount) { echo $material_amount; } ?></i></p>
<?php
}
I hope I made it clear and please tell me if you want me to provide more code, more images or explanation or anything like that. Any help, advice or suggestions are highly appreciated. Thanks for your time.
while($row = $result->fetch_assoc()) {
$arr[$row['object_id']][$row['name']] = $row['material_amount'];
}
....
foreach ($arr as $key){
$material_array = $key;
foreach ($material_array as $k => $v){
echo "<p>$k | $v</p>";
}
It will display material name and amount correctly, but there are no names of objects themselves.
}

Displaying a multi-table mysql query

This is going to be really hard to explain but I'll attempt to do my best without confusing you all. I have the following table design;
Here is my mysqlFiddle to demonstrate the issue: http://sqlfiddle.com/#!2/1363c/1
As you can see, the tables work fine and the query is displaying what shelter has what service but my issue comes when I attempt to display this information on my web. I'm currently using the following code;
<?php
echo '<p>', "<strong>Shelter id</strong>: " . $query['shelter_id'].'</p>';
echo '<p>', "<strong>Shelter name</strong>: " . $query['shelter_name'].'</p>';
echo '<p>', "<strong>Street</strong>: " . $query['street'].'</p>';
echo '<p>', "<strong>City</strong>: " . $query['city'].'</p>';
echo '<p>', "<strong>Postcode</strong>: " . $query['postcode'].'</p>';
echo '<p>', "<strong>Contact Number</strong>: " . $query['phone_number'].'</p>';
echo '<p>', "<strong>Location</strong> : " . $query['location_name'].'</p>';
?>
but with the query I'm using, Its not going going to dupe all the information into one row it is, its going to create multiple row's for the service_name, I think.
Is it possible for me to create a query that will return all service's the shelter can do on one row?
Edit: Will i have to do 2 separate query's? One to get the generic shelter information and then another query to return the services into an array and then just run through them on the page?
how about using GROUP_CONCAT? What it does is it combines the values into comma separated value. And you can later use Explode() in PHP.
SELECT shelter_name,
GROUP_CONCAT(services.service_name) service_name
FROM shelter
INNER JOIN shelter_service
ON shelter.shelter_id= shelter_service.shelter_id
INNER JOIN services
ON shelter_service.service_id = services.service_id
GROUP BY shelter.shelter_id, shelter_name
ORDER BY shelter_name
SQLFiddle Demo
GROUP_CONCAT() use only if you have a limited number of rows (10-100), if the data is larger will slow your query a lot.
2 queries (one for types one for data)
keep this query and modify the result (my favorite way).
On server or JS loop trough your data and restructure it (add 1 extra dimension)
var new_data = {};
for(var i in data)//data is your $query
{
var row = data[i];
//create another layer of the array (make it in 2 dimensions)
if (typeof(new_data[row['Service_name']) == 'undefined)
new_data['Service_name'] = [];
//add only this kind of type data
new_data['Service_name'].push(data);
}
//now you can remove the data and use new_data
.4. Make sure the query is primarly sorted by Service name, and when you loop at display (echo) you show the current service name.
$current_name = '';
for(...)
//if we passed to a new service in the loop
if ($query['service_name'] != $current_name) {
echo '<h3>'.$query['service_name'].'</h3>;//display the new service
$current_name = $query['service_name'];
}

PHP - Nested List Broken into Even Columns (fix and updates)

I have a follow-up to a previous thread/question that I hope can be solved by relatively small updates to this existing code. In the other thread/question, I pretty much solved a need for a nested unordered list. I needed the nested unordered list to be broken up into columns based on the number of topics.
For example, if a database query resulted in 6 topics and a user specified 2 columns for the layout, each column would have 3 topics (and the related news items below it).
For example, if a database query resulted in 24 topics and a user specified 4 columns for the layout, each column would have 6 topics (and the related news items below it).
The previous question is called PHP - Simple Nested Unordered List (UL) Array.
The provided solution works pretty well, but it doesn't always divide
correctly. For example, when $columns = 4, it only divides the
columns into 3 groups. The code is below.
Another issue that I'd like to solve was brought to my attention by
the gentleman who answered the question. Rather than putting
everything into memory, and then iterating a second time to print it
out, I would like to run two queries: one to find the number of
unique TopicNames and one to find the number of total items in the
list.
One last thing I'd like to solve is to have a duplicate set of
code with an update that breaks the nested unordered list into columns
based on the number of news items (rather than categories). So, this
would probably involve just swapping a few variables for this second
set of code.
So, I was hoping to solve three issues:
1.) Fix the division problem when relying on the number of categories (unordered list broken up into columns based on number of topics)
2.) Reshape the PHP code to run two queries: one to find the number of unique TopicNames and one to find the number of total items in the list
3.) Create a duplicate set of PHP code that works to rely on the number of news items rather than the categories (unordered list broken up into columns based on number of news items)
Could anyone provide an update or point me in the right direction? Much appreciated!
$columns = // user specified;
$result = mysql_query("SELECT * FROM News");
$num_articles = 0;
// $dataset will contain array( 'Topic1' => array('News 1', 'News2'), ... )
$dataset = array();
while($row = mysql_fetch_array($result)) {
if (!$row['TopicID']) {
$row['TopicName'] = 'Sort Me';
}
$dataset[$row['TopicName']][] = $row['NewsID'];
$num_articles++;
}
$num_topics = count($dataset);
// naive topics to column allocation
$topics_per_column = ceil($num_topics / $columns);
$i = 0; // keeps track of number of topics printed
$c = 1; // keeps track of columns printed
foreach($dataset as $topic => $items){
if($i % $topics_per_columnn == 0){
if($i > 0){
echo '</ul></div>';
}
echo '<div class="Columns' . $columns . 'Group' . $c . '"><ul>';
$c++;
}
echo '<li>' . $topic . '</li>';
// this lists the articles under this topic
echo '<ul>';
foreach($items as $article){
echo '<li>' . $article . '</li>';
}
echo '</ul>';
$i++;
}
if($i > 0){
// saw at least one topic, need to close the list.
echo '</ul></div>';
}
UPDATE 12/19/2011: Separating Data Handling from Output Logic (for the "The X topics per column variant"):
Hi Hakre: I've sketched out the structure of my output, but am struggling with weaving the two new functions with the old data handling. Should the code below work?
/* Data Handling */
$columns = // user specified;
$result = mysql_query("SELECT * FROM News LEFT JOIN Topics on Topics.TopicID = New.FK_TopicID WHERE News.FK_UserID = $_SESSION[user_id] ORDER BY TopicSort, TopicName ASC, TopicSort, NewsTitle");
$num_articles = 0;
// $dataset will contain array( 'Topic1' => array('News 1', 'News2'), ... )
$dataset = array();
while($row = mysql_fetch_array($result)) {
if (!$row['TopicID']) {
$row['TopicName'] = 'Sort Me';
}
$dataset[$row['TopicName']][] = $row['NewsID'];
$num_articles++;
}
/* Output Logic */
function render_list($title, array $entries)
{
echo '<ul><li>', $title, '<ul>';
foreach($entries as $entry)
{
echo '<li>', $entry['NewsID'], '</li>';
}
echo '</ul></li></ul>;
}
function render_column(array $topics)
{
echo '<div class="column">';
foreach($topics as $topic)
{
render_list($topic['title'], $topic['entries']);
}
echo '</div>';
}
You have not shown in your both questions what the database table is, so I can not specifically answer it, but will outline my suggestion.
You can make use of aggregation functions in mysql to obtain your news entries ordered and grouped by topics incl. their count. You can do two queries to obtain counts first, that depends a bit how you'd like to deal with your data.
In any case, using the mysql_... functions, all data you selected from the database will be in memory (even twice due to internals). So having another array as in your previous question should not hurt much thanks to copy on write optimization in PHP. Only a small overhead effectively.
Next to that before you take care of the actual output, you should get your data in order so that you don't need to mix data handling and output logic. Mixing does make things more complicated hence harder to solve. For example if you put your output into simple functions, this gets more easy:
function render_list($title, array $entries)
{
echo '<ul><li>', $title, '<ul>';
foreach($entries as $entry)
{
echo '<li>', $entry['NewsID'], '</li>';
}
echo '</ul></li></ul>;
}
function render_column(array $topics)
{
echo '<div class="column">';
foreach($topics as $topic)
{
render_list($topic['title'], $topic['entries']);
}
echo '</div>';
}
This already solves your output problem, so we don't need to care about it any longer. We just need to care about what to feed into these functions as parameters.
The X topics per column variant:
With this variant the data should be an array with one topic per value, like you did with the previous question. I would say it's already solved. Don't know which concrete problem you have with the number of columns, the calculation looks good, so I skip that until you provide concrete information about it. "Does not work" does not qualify.
The X news items per column variant:
This is more interesting. An easy move here is to continue the previous topic with the next column by adding the topic title again. Something like:
Topic A Topic A Topic B
- A-1 - A-5 - B-4
- A-2 Topic B - B-5
- A-3 - B-1 - B-6
- A-4 - B-2
- B-3
To achieve this you need to process your data a bit differently, namely by item (news) count.
Let's say you managed to retrieve the data grouped (and therefore sorted) from your database:
SELECT TopicName, NewsID FROM news GROUP BY 1;
You can then just iterate over all returned rows and create your columns, finally output them (already solved):
$itemsPerColumn = 4;
// get columns
$topics = array();
$items = 0;
$lastTopic = NULL;
foreach ($rows as $row)
{
if ($lastTopic != $row['TopicName'])
{
$topic = array('title' => $row['TopicName']);
$topics[] = &$topic;
}
$topic['entries'][] = $row;
$items++;
if ($items === $itemsPerColumn)
{
$columns[] = $topics;
$topics = array();
$lastTopic = NULL;
}
}
// output
foreach($columns as $column)
{
render_column($column);
}
So this is actually comparable to the previous answer, but this time you don't need to re-arrange the array to obtain the news ordered by their topic because the database query does this already (you could do that for the previous answer as well).
Then again it's the same: Iteration over the returned result-set and bringing the data into a structure that you can output. Input, Processing, Output. It's always the same.
Hope this is helpful.

Possibly simple PHP/MYSQL issue with retrieving and showing data

I have been racking my brains over this for a while now. Here is the data I have in the SQL data base as an example:
ID | TYPE | DATA
1 | TXT | TEST
2 | PHP | php
3 | JS | JAVASCRIPT
That is just an example, there are multiple listing for TXT, PHP and JS throughout the table. What I want to do is retrive all the data and display it all into separate drop down/select boxes. Meaning, select box one would list all data with type TXT, select box two would list all data with type PHP and select box 3 would list all data with type JS. The only way I have came about doing this is doing individual sql queries for each different type. I know there is a way to do it all in 1 query and then display it the way I want to but I just can't seem to figure out how and I know its going to drive me nuts when someone helps and I see just how they did it.
The only way that I know of to get all of the data in one query is just to do a generic SELECT * FROM tbl, and then you can group them in the code:
$res = mysqli_query('SELECT * FROM tbl');
$data = array();
while($row = mysql_fetch_assoc($res)) {
$type = $row['type'];
$data[$type][] = $row;
}
// $data contains all of the record, grouped by the TYPE column
foreach($data as $type => $records) {
echo "records for $type: <select>";
foreach($records as $record) {
echo "<option value='$id'>$id</option>";
}
echo "</select>";
}
Just retrieve all records and loop through them using PHP. Use an iterator if the recordset is going to be huge to prevent using too much memory.
$lists = array();
foreach($recordset as $record) {
$lists[$record['type']][$record['id']] = $record['data'];
}
Know you have an array containing all data.
Just order it by Type and make a loop using "foreach" into the results, changing of select box when the type is different than the preivous.
In this way you only loop once over the array.
You can do kind of grouping with "ORDER BY TYPE":
SELECT id, data
FROM table
ORDER BY type;
Then, in data output loop you can track current type, and build another select box once type changed:
$currentType = "no type";
while($row = mysql_fetch_assoc($res)) {
if ($currentType != $row['type']) {
$currentType = $row['type'];
// start new select box here
}
// do some other work here
}
BTW, such approach looks like kind of hack :)

Categories