Display multiple row data within 1 row MySQL PHP - php

I have the following query which pulls from 3 tables. I'll end up with 1 family per row but with multiple children for each. I want to be able to show all children ages within the family row. I thought about opening another connection/query, but figured there is a smarter way.
Query:
SELECT
families.*, job.*, children.*, families.first_name AS fam_firstname, children.first_name AS child_firstname
FROM job
LEFT OUTER JOIN families ON job.fam_id = families.fam_id
LEFT OUTER JOIN children ON families.fam_id = children.fam_id
WHERE
job.published = 2
GROUP BY job.job_id
ORDER BY job.created_on DESC
Loop:
if ($result = $mysqli->query($query)) {
$from = new DateTime($row['dob']);
$to = new DateTime('today');
while ($row = $result->fetch_assoc()) {
echo '<tr>';
echo '<td>' .$row['fam_firstname']. '</td>';
echo '<td>' .$row['last_name'].'</td>';
/* Looking to list all children ages. Separate by comma or break */
echo '<td>' . $from->diff($to)->y .'</td>';
echo '</tr>';
}
$result->free();
}
Desired Output:
Family First Name | Family Last Name | Child 1 Age, Child 2 Age

You need to use the mysql group_concat function to achieve this:
SELECT
families.*, group_concat(children.age)
FROM job
LEFT OUTER JOIN families ON job.fam_id = families.fam_id
LEFT OUTER JOIN children ON families.fam_id = children.fam_id
WHERE
job.published = 2
group by families.fam_id
ORDER BY job.created_on DESC

Follow this question: Nested Array from multiple tables.
Refer to the second option in the question, that explains how you subtract your data from the JOIN query.
P.S.
I'ts a question I've asked myself, with an implementation with what your'e trying to do here. If you need more lead on how to implement it here, ask in comments...
Here is a way to implement it in your code (notice you should order your JOIN query by "fam_firstname", for this code to work for you):
/* init temp vars to save current family's data */
$current = null;
$fam_firstname = null;
$children = array();
while ($row = mysql_fetch_assoc($result))
{
/*
if the current id is different from the previous id:
you've got to a new family.
print the previous family (if such exists),
and create a new one
*/
if ($row['fam_firstname'] != $fam_firstname )
{
// in the first iteration,
// current (previous family) is null,
// don't print it
if ( !is_null($current) )
{
$current['children'] = $children;
/*
Here you print the whole line
I'm just dumping it all here, but you can print
it more nicer...
*/
var_dump($current);
$current = null;
$fam_firstname = null;
$children = array();
}
// create a new family
$current = array();
$current['fam_firstname'] = $row['fam_firstname'];
/*
Add more columns value here...
*/
// set current as previous id
$fam_firstname = $current['fam_firstname'];
}
// you always add the phone-number
// to the current phone-number list
$children[] = $row['child_firstname'] . " is " . $row['child_age'] . " years old";
}
}
// don't forget to print the last family (saved in "current")
if (!is_null($current))
/*
Here you print the whole line
I'm just dumping it all here, but you can print
it more nicer...
*/
var_dump($current);

Related

Include files in PHP

Thank you for reading this and for your help in advance.
I got a simple Book-Catalogue /procedure code/, every visitor can see the book catalogue and click through out the books and check their authors BUT I've got an idea to add a feature that allows the users to write a comment to each book ONLY if they're logged. So I added a session variable $_SESSION['isLogged']. But there is a lot of code blocks that duplicates. What I need is an advice, what to do with this duplicated blocks of code. What the good practice says? And my code below is from one of the 6 files that I got. In everyfile I got this repeating of code.
So here's my code:
if (!isset($_SESSION['isLogged'])) {
echo '<div class="user-navigation">
<a href="register.php" class="user-nav" >Register now</a>
Log in
</div>
<div class="navigation1">
Add book
Add author
</div>';
$booksAndAuthors = mysqli_query($connection, 'SELECT DISTINCT * FROM books LEFT JOIN books_authors ON books.book_id=books_authors.book_id LEFT JOIN authors ON authors.author_id=books_authors.author_id');
$result = array();
while ($resultArr = mysqli_fetch_assoc($booksAndAuthors)) {
$result[$resultArr['book_id']] ['book_name'] = $resultArr['book_title']; // Reodering array
$result[$resultArr['book_id']] ['author'][$resultArr['author_id']] = $resultArr['author_name']; // Reordering array
}
echo '<table class="table"><tr><th>Book name</th><th>Author</th></tr>'; // Open table html table tags
foreach ($result as $k=>$b) { // Foreach the result array to get the book_name
echo '<tr><td>' . $b['book_name'] . '</td><td>';
$data = array(); // Create an empty array in order to fill the data inside
foreach ($b['author'] as $k => $a) { // Foreach the nested array with the authors to get the author_name displayed
$_GET['author_name'] = $a;
$data[] = '' . $a . ''; // Link is chosen by the author_id
}
echo implode(', ', $data); // Add a comma after every record
echo '</td></tr>'; // Close table cell and row
}
exit;
echo '</table>'; // Close html table tag
}
else {
echo '<div class="user-navigation">
My Profile
Log out
</div>
<div class="navigation2">
Add book
Add author
</div>';
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
$booksAndAuthors = mysqli_query($connection, 'SELECT DISTINCT * FROM books LEFT JOIN books_authors ON books.book_id=books_authors.book_id LEFT JOIN authors ON authors.author_id=books_authors.author_id');
$result = array();
while ($resultArr = mysqli_fetch_assoc($booksAndAuthors)) {
$result[$resultArr['book_id']] ['book_name'] = $resultArr['book_title']; // Reodering array
$result[$resultArr['book_id']] ['author'][$resultArr['author_id']] = $resultArr['author_name']; // Reordering array
}
echo '<table class="table"><tr><th>Book name</th><th>Author</th></tr>'; // Open table html table tags
foreach ($result as $k=>$b) { // Foreach the result array to get the book_name
echo '<tr><td>' . $b['book_name'] . '</td><td>';
$data = array(); // Create an empty array in order to fill the data inside
foreach ($b['author'] as $k => $a) { // Foreach the nested array with the authors to get the author_name displayed
$_GET['author_name'] = $a;
$data[] = '' . $a . ''; // Link is chosen by the author_id
}
echo implode(', ', $data); // Add a comma after every record
echo '</td></tr>'; // Close table cell and row
}
echo '</table>'; // Close html table tag
}
*If my question is not properly asked or you have some notices.Please let me know!
* (=
Put this code in a file then include that file at the bottom of all of the pages.
For example put the code in isLogged.php
Then on each page where the code is duplicated replace it with:
include "isLogged.php";
include is what you're looking for.
You should create a file that contains your repeated code. functions.php for example.
Then call it with one of these methods.
include "functions.php"
http://php.net/manual/en/function.include.php
include_once("functions.php")
http://php.net/manual/en/function.include-once.php
require_once("functions.php")
http://php.net/manual/en/function.require-once.php
create a function of repeated block and call when it required or create a file with the set of repeated code and use any of include,include_once,require_once to add the file.for more detail see php manual for these functions.

Query 2 tables and combine data in a single array

I am trying to combine the results from 2 queries into a single array but am having problems getting the correct code.
I have a table called PrimaryEvents that I query to add to an array. This query works ok:
$rows = query("SELECT * FROM PrimaryEvents WHERE unit = ? ORDER BY event", $unit);
I add the data to an array using the following:
foreach ($rows as $row)
{
// $event = lookup($row["event"]); (removed as spotted by sergio not required)
$event["event"] = $row["event"];
$event["validity"] = $row["validity"];
$event["eventsrequired"] = $row["eventsrequired"];
$event["unit"] = $row["unit"];
$event["role"] = $row["role"];
//now we have the data lets try and do something to it
if ($event["role"] == "0")
{
//then the user is active so change value to a YES
$event["role"] = "ALL";
}
//add our stock to the portfolio array
$portfolio[] = $event;
}
So far everything works well. I would like to query a separate table and add certain results to the array portfolio[];
$rowsCompleted = query("SELECT * FROM portfolio WHERE id = ? ORDER BY name", $_SESSION["id"]);
My original plan was to use the following additional code to add the data of interest to the portfolio[] but it doesn't work:
//now add individual stats to the array
foreach($rowsCompleted as $row)
{
if ($portfolio["event"] == $row["name"]
{
//then add our number and date to the array
$portfolio["datecompleted"] = $row["datecompleted"];
$portfolio["number"] = $row["number"];
}
//else do nothing
}
My final aim was to pull the datacompleted value and number value from the portfolio table and then add it to the array ONLY if the event name matches the name in portfolio array.
Hopefully I have described my problem and required behaviour well enough.
Thanks for nay help that can be offered, I am new to php/sql so learning as I go.
Andy
EDIT:
Also tried the following loop to try and loop through the array:
//now add individual stats to the array
foreach($rowsCompleted as $row)
{
foreach ($portfolio["event"] as $item)
{
if ($portfolio["event"] == $row["name"]
{
//then add our number and date to the array
$portfolio["datecompleted"] = $row["datecompleted"];
$portfolio["number"] = $row["number"];
}
}
//else do nothing
}
EDIT2: To try and make my question more clear: The Primary-events table lists all the events a specific user has to complete. The portfolio table tracks which events have been completed by the user (by means of date-completed and number).
I want to run a query that lists all the events from Primary-events, then into those results add the date-completed and number from the users portfolio. If the answer is blank from the portfolio then the user has not yet completed the event but I would still like to list it from the Primary-events table data.
I tried 2 queries to start with as it seemed to follow what I was trying to achieve.
EDIT3:
New code with help from Barmar
foreach ($rows as $row) {
$event = lookup($row["event"]);
$event["event"] = $row["event"];
$event["validity"] = $row["validity"];
// $event["datecompleted"] = $row["datecompleted"];
// $event["number"] = $row["number"];
$event["eventsrequired"] = $row["eventsrequired"];
$event["unit"] = $row["unit"];
$event["role"] = $row["role"];
//now we have the data lets try and do something to it
if ($event["role"] == "0") {
//then the user is active so change value to a YES
$event["role"] = "ALL";
}
//add our stock to the portfolio array
$portfolio[] = $event;
//now add individual stats to the array
foreach ($rowsCompleted as $row) {
foreach ($portfolio as &$item) {
if ($item["event"] == $row["name"]) {
//then add our number and date to the array
$item["datecompleted"] = $row["datecompleted"];
$item["number"] = $row["number"];
}
}
}
}
The page still isn't being displayed by chrome so guessing i've missed something.
EDIT4: Page displayed, I am now getting undefined index errors when the table is rendered on the html page.
Specifically the undefined index errors are for date-completed and number. I am taking it to mean that these values are not being added to the portfolio array? Do try and help I have added an Else statement (as below) to ensure that even if the event isn't available the index is:
//now add individual stats to the array
foreach($rowsCompleted as $row)
{
foreach ($portfolio as &$item)
{
if ($item["event"] == $row["name"])
{
//then add our number and date to the array
$item["datecompleted"] = $row["datecompleted"];
$item["number"] = $row["number"];
}
else
{
$item["datecompleted"] = "Never";
$item["number"] = "0";
}
}
}
EDIT 5: Near Success. Sorry to reopen this but I have just noticed behaviour I wasn't expecting: The nested loop only sets the date-completed and number for the first value to matches from the portfolio table. The follow on values are not set. It would seem like the nested loop isn't stepping through all of the portfolio values, and just exiting once the first "event" == "name".
EDIT 6: Sample data and clarification on desired functions:
portfolio sample data
|id|name |number|datacompleted
|21|event1 |3 |2014-07-07
|15|event1 |5 |2014-07-05
|21|event2 |5 |2014-05-08
|15|event1 |1 |2013-05-05
id is the id of the user that completed the event
number is the number of events completed
PrimaryEvents sample data
|id|event |validity|eventsrequired
|1 |event1 |7 |10
|1 |event2 |25 |1
|1 |event3 |12 |50
id is the id of the user that created the entry (used for historic purpose only)
The desired functionality is:
The query should create a an array to allow a html table to be created of everything within the Primary-events table. This table lists the events the user must complete.
The second query or current nested loop should gather the data from the portfolio table for the current user id, then match the event name to the name in the Primary-events array and update (if present) the number and date-completed value. (I.E populate the data for the events that the user has completed).
The current code merges the data from portfolio only for the first match, but then the nested loop seems to exit.
Hopefully this is a more clear description of what I am trying to achieve.
EDIT: I have changed the functionality to use the Left join statement below but am still having problems:
The table only contains some of the events from primary-events table and not all of them. The events being pulled over are only those that the user has completed, the ones the user has not yet completed are not being shown.
EDIT: This query seems to work:
$allEvents = query("SELECT * FROM PrimaryEvents LEFT JOIN portfolio ON (PrimaryEvents.event = portfolio.name) WHERE PrimaryEvents.event = ? AND (portfolio.id = ? Or portfolio.id is null) ORDER BY PrimaryEvents.event", $currentEvent, $_SESSION["id"]);
You need to notice that portfolio is not associative array but multidimensional array, so you cannot access it using $portfolio["event"]. You should use $portfolio[0]["event"], $portfolio[1]["event"] and so on.
It's hard to show you exact solution because I don't know how those arrays/database queries should be merged.
EDIT
It seems your query should look like this:
query("SELECT * FROM PrimaryEvents e LEFT JOIN portfolio p ON e.event = p.name WHERE e.unit = ? AND p.id = ? ORDER BY e.event", $unit,$_SESSION["id"]);
EDIT2
I haven't proposed nested loop (as it's now in modified question) because of performance loss.
You're getting closer with your second query, but still confused about what's in each array.
foreach($rowsCompleted as $row)
{
foreach ($portfolio as &$item) // Need to use reference so we can update it
{
if ($item["event"] == $row["name"])
{
//then add our number and date to the array
$item["datecompleted"] = $row["datecompleted"];
$item["number"] = $row["number"];
break;
}
}
}
To avoid the nested loop, it would be better for $portfolio to be an associative array. Change the code for your initial query to use:
//add our stock to the portfolio array
$portfolio[$event["name"]] = $event;
Then the second loop becomes:
foreach($rowsCompleted as $row)
{
$name = $row["name"];
if (isset($portfolio[$name]) {
$portfolio[$name]["datecompleted"] = $row["datecompleted"];
$portfolio[$name]["number"] = $row["number"];
}
}

PHP - Loop and not loop at the same time from a join

I'm trying to display a company name from a company table, and then loop out news from another table. The two tables are joined. This means that: I want to just pick out one row from one table in the join, and loop out data from the other table in the join. Is this possible? My code below only display two out of three posts in the news table in my loop.
Thanks!
$sql = "SELECT
newID, newTitle, newSummary, newDate,
comID, comName, comImageThumb
FROM
tblNews a RIGHT JOIN tblCompanies b
ON a.newCompanyID = b.comID
ORDER BY newDate DESC";
// Get company name and display
$companyData = mysql_fetch_assoc($result);
$comName = $companyData['comName'];
echo "<a href='#' class='name'>$comName</a>";
// Looping news
while($news = mysql_fetch_assoc($result)) {
// Display news posts
$newTitle = $news['newTitle'];
echo $newTitle;
}
Put everything inside while loop and try :
while($news = mysql_fetch_assoc($result)) {
// Get company name and display
$comName = $news ['comName'];
echo "<a href='#' class='name'>$comName</a>";
// Looping news
// Display news posts
$newTitle[] = $news['newTitle'];
echo $newTitle;
}
You should look at mysql GROUP_CONCAT for more detail.
Easiest solution probably would be to keep track of whether you are in the first iteration:
// query company name and news entries here
$firstRow = true;
while($news = mysql_fetch_assoc($result)) {
if ($firstRow) { echo $news['comName']; }
$firstRow = false;
echo $news['newTitle'];
}
You could also keep your current code and use mysql_data_seek(0) to reset the cursor after extracting the company name first.
And on a side note: Don't use mysql_* functions anymore! Use PDO instead.
Move the fetch operation to the end of the loop:
$companyData = mysql_fetch_assoc($result);
$comName = $companyData['comName'];
echo "<a href='#' class='name'>$comName</a>";
// you've already fetched a news item....
$news=$companyData;
do {
$newTitle = $news['newTitle'];
echo $newTitle;
} while ($news=mysql_fetch_assoc($result));

How to fetch query where sub/child create new UL

I am trying to make un-order list for parent child categories where if there is any child category than it will create another un-order list ( like indented text) so user can understand properly.
I have fetch sql but with foreach I don't understand how to set so where child category only will display under parent category by creating another un-order list under the parent category.
Here is my code
$query_cat = "SELECT * FROM ^categories";
$query = qa_db_query_sub($query_cat);
$catsid = qa_db_read_all_assoc($query);
echo '<UL>';
foreach ($catsid as $catid){
echo '<LI>'. $catid['title'].' '. $catid['categoryid'].'</LI>';
}
echo '</UL>';
So final result would be
First Category
Sub Category1
Second Category
EDIT:
After modified code with #vlcekmi3 answer https://stackoverflow.com/a/13451136/1053190 I am getting this result
Now how to exclude subcategory from parent list?
There's no really easy solution for this with your design. The most effective way would be to add column like order_in_list (and maybe depth_in_list).
They would be pre calculated in loop (pseudocode):
START TRANSACTION
UPDATE t1 SET order_in_list = 0 // Restart whole loop
$ids = array(0);
while $id = array_shift($ids){
$record = SELECT * FROM t1 WHERE id = $id // Get id details, order_in_list is important
$children = SELECT * FROM t1 WHERE parent_id = $id // get list of all childs
// If it's root element, start indexing from 0
$root_order = ($record ? $record->order_in_list : 1)
$child_no = count($children) // How many child will be adding
// No children, nothing to do:
if $child_no < 1{
continue;
}
append_to_array($ids, $children) // Store ids to process
// Shift all later records, we'll be creating gap in order_in_list 1,2,3,4,5
// To 1,2,5,6,7 to insert items on places 3,4
UPDATE t1 SET order_in_list = (order_in_list + $child_no)
WHERE order_in_list > $record->order_in_list
// Okay, set IDs for direct children
foreach( $children as $child){
UPDATE t1 SET order_in_list = $root_order, depth_in_list = $record->depth_in_list+1
WHERE id = $child->id
$root_order++;
}
}
COMMIT
This way you'll get records like:
First category, 1, 1
Second category 3, 1
Sub category, 2, 2
Which you could display with simple loop:
$last_depth = 0;
foreach( (SELECT * FROM t1 ORDER by `order_in_list`) as $row){
if( $last_detph > $row['depth_in_list'])){
// Close level </ul>
} else if($last_detph < $row['depth_in_list']){
// Opening level <ul>
} else {
// The same depth
}
$last_depth = $row['depth_in_list'];
}
Without modifying database
It would be probably most effective to build two arrays containing root elements and all elements:
$root_elements = array();
$all_elements = array();
foreach( (SELECT * FROM t1) as $row){
// Store details into all_elements, note that entry may have already be created when
// processing child node
if( isset( $all_elements[$row['id']])){
// set details
} else {
$all_elements[$row['id']] = $row;
$all_elements[$row['id']]['children'] = array(); // Array of child elements
}
if( $row['parent_id'] == NULL){
$all_elements[] = $row['id']; // Add row element
} else {
if( isset( $all_elements[ $row[ 'parent_id']])){
$all_elements[ $row[ 'parent_id']]['children'][] = $row['id'];
} else {
// Create new record:
$all_elements[ $row[ 'parent_id']] = array();
$all_elements[ $row[ 'parent_id']]['children'] = array($row['id']);
}
}
}
And then write it as:
foreach( $root_elements as $element_id){
write_recursive( $all_elements[ $element_id]);
}
// And display
function write_recursive( $element)
{
echo '<ul>...';
if( count( $element['children'])){
foreach( $element['children'] as $child){
write_recursive( $all_elements[ $child]);
}
}
echo '</ul>';
}
You better create class for that (to replace using global variables), but you should have a solid way to do this. Anyway try avoid using this with large number of records (I wouldn't go past 2000-5000 menu entries), try to at least cache it.
Note: solutions are oriented towards minimal number of requests on database when displaying list.
you can use complicated query or something like this
foreach ($catsid as $catid) {
...
$subquery_cat = "SELECT * FROM ^categories WHERE parentid='".$catid['categoryid']."'";
$query = qa_db_query_sub($subquery_cat);
$subcatsid = qa_db_read_all_assoc($query);
// wrap into html
...
}

How do I extract and display hierarchical data from my database?

I have two tables.
The chapters table has the columns id and name.
The chapters_chapter table has columns id, master_id, and slave_id.
Lets say that the chapters table has 7 records:
id name
1 test01
2 test02
3 test03
4 test04
5 test05
6 test06
7 test07
And in the chapters_chapters table I have these records:
id master_id slave_id
1 1 5
2 1 6
3 6 7
4 7 2
Given that data, how can I extract the hierarchy of that data so that it looks like this?
test01
test05
test06
test07
test02
test03
test04
So this was kind of a pain because of the fact that we had to have the hierarchy stored in the DB. Because of this, each item can have multiple children, and each child can have multiple parents.
This second part means we cannot simply loop through the list once and be done with it. We might have to insert an item in multiple places in the hierarchy. While you probably won't actually structure your data that way, the database schema you've described supports this scenario, so the code must support it too.
Here's a high-level version of the algorithm:
Query both tables
Create a map (array) of a parent (number) to its children (another array)
Create a set of items that are children (array of numbers)
Create a function that displays a single item, indenting it to the desired depth.
If that item has children, this function increases the depth by one, and calls itself recursively
Loop through all items that aren't children (root items).
Call the function for each of those items, with a desired depth of 0 (no indent).
Here's two hours work. Enjoy :)
Note that I stuck it within a <pre> block, so you might have to mess with how the indentation is done (output something other than two spaces, mess with the style of the divs, etc).
<?php
$con = mysql_connect("localhost", "test_user", "your_password");
if(!$con)
{
die("could not connect to DB: " . mysql_error());
}
mysql_select_db("your_db", $con);
// get chapters
$chapters = array();
$result = mysql_query("SELECT * FROM chapters");
while($row = mysql_fetch_array($result))
{
$id = $row["id"];
$name = $row["name"];
$chapters[$id] = $name;
}
// get chapters_chapters - We'll call it "parent/child" instead of "master/slave"
$parent_child_map = array();
$is_child = array();
$result = mysql_query("SELECT master_id, slave_id FROM chapters_chapters");
while($row = mysql_fetch_array($result))
{
$parent_id = $row["master_id"];
$child_id = $row["slave_id"];
$children = $parent_child_map[$parent_id];
if($children == null)
{
$children = array();
}
$children[] = $child_id;
$parent_child_map[$parent_id] = $children;
$is_child[$child_id] = true;
}
// display item hierarchically
$display_item_and_children = function($id, $name, $depth)
use ($chapters, $parent_child_map, &$display_item_and_children)
{
echo "<div><pre>";
// indent up to depth
for($i = 0; $i < $depth; $i++)
{
echo " ";
}
echo "id: " . $id
. " name: " . $name
. "</pre></div>";
// if there are children, display them recursively
$children = $parent_child_map[$id];
if($children != null)
{
foreach($children as $child_id)
{
$child_name = $chapters[$child_id];
$display_item_and_children($child_id, $child_name, $depth + 1);
}
}
};
// display all top-level items hierarchically
foreach($chapters as $id => $name)
{
// if it is a top-level item, display it
if($is_child[$id] != true)
{
$display_item_and_children($id, $name, 0);
}
}
mysql_close($con);
?>
And here's a screenshot:
The question becomes how complex you want your solution to be. I'd do it with the following pseudo code.
SELECT all the chapters
SELECT all the *chapters_chapters*
loop over the chapters to create an array chapter objects
loop over the `chapters_chapters* and create the relationships using the chapter objects
Essentially you're creating a link-list.

Categories