Loop through multi dimension array to show grid in PHP - php

I have 3 tables. Apls, Hulls and AplsHulls.
Apls consists of id, name, date
Hulls consists of id, production_name
AplsHulls is a join table and consists of id, apl_id, hull_id, status.
Not every Hull is associated with each Apl. The ones that are are in the join table with a status (shipped,in production, etc.)
I need to display a report in a table/grid that has the following columns headers: Apl Name, Apl_Date then the hull production names as the remaining column titles. (if hull 7 isn't in the result set, it doesn't even get a column.
For the data I need to list the apl name, apl date, then loop across the remaining columns and fill in the status for the records in the join table. If the apl and hull aren't associated in the join table, then just fill the cell with "NA".
I have tried this a bunch of different ways and I can currently get the dynamic list of column headers for the hulls, I never seem to be able to get the data to loop across correctly.
Sample Data:
Apls Table
Id: 1, Name: X1-0000, Date: 1/1/2009
Id: 2, Name: BG-5480, Date: 2/22/2009
Id: 3, Name: HG-0000, Date: 2/27/2009
Hulls Table
Id: 1, Production_name: ProdA
Id: 2, Production_name: ProdB
Id: 3, Production_name: ProdC
Id: 4, Production_name: ProdD
AplsHulls Table
Id: 1, Apl_id: 1, Hull_id: 1, Status:Delivered
Id: 2, Apl_id: 1, Hull_id: 3, Status:Ordered
Id: 3, Apl_id: 2, Hull_id: 4, Status:Delivered
I need the table to show like this:
APL | Date | ProdA | ProdC | ProdD
X1-0000 | 01/01/2009 | Delivered | Ordered | NA
BG-5480 | 02/22/2009 | NA | NA | Delivered
Notice the column headers ignore ProdB since that record wasn't in the join table at all. Also it fills in NA for the columns that are in the join table but it may not have an association to in the join table.
It is very confusing, I know.

You can get the list of hulls you care about with a query like this:
select h.id, pname from hulls h join aplshulls on (h.id=hull_id)
You can (and probably should) stop reading this answer now and just use that get the columns you care about and then figure out how to put the data you have into the table.
But once you have that list of hulls you care about you can have your program write some evil sql to build the result for you. The code below assumes your DB library returns an array of rows for your sql query.
$hulls = query("select h.id, pname from hulls h join aplshulls on (h.id=hull_id)");
/* This should give a result like:
* $hulls = array(array('id'=>1,'pname'=>'proda'),
* array('id'=>3,'pname'=>'prodc'),
* array('id'=>4,'pname'=>'prodd'));
*/
$sql = "select name, mdate";
foreach ($hulls as $row) {
$sql .= ", ifnull({$row['pname']},'NA') {$row['pname']}";
}
$sql .= " from apls ";
foreach ($hulls as $row) {
$sql .= " left join (select apl_id, status as {$row['pname']} from hulls h \join aplshulls ah on (h.id=hull_id) where pname='{$row['pname']}') as {$row['pn\ame']} on ({$row['pname']}.apl_id=apls.id)";
}
$sql .= " where apls.id in (select distinct apl_id from aplshulls)";
$result = query($sql);
foreach ($result as $row) {
print "<tr>";
foreach ($row as $value) {
print "<td>$value</td>";
}
print "</tr>\n";
}
Replace the calls to query with your database query methods.
The resulting sql is:
select name, date,
ifnull(proda,'NA') proda, ifnull(prodc,'NA') prodc, ifnull(prodd,'NA') prodd
from apls
left join (select apl_id, status as proda
from hulls h join aplshulls ah on (h.id=hull_id)
where pname='proda') as proda on (proda.apl_id=apls.id)
left join (select apl_id, status as prodc
from hulls h join aplshulls ah on (h.id=hull_id)
where pname='prodc') as prodc on (prodc.apl_id=apls.id)
left join (select apl_id, status as prodd
from hulls h join aplshulls ah on (h.id=hull_id)
where pname='prodd') as prodd on (prodd.apl_id=apls.id)
where
apls.id in (select distinct apl_id from aplshulls);
There is probably a better way to build the query but this should work. It probably breaks down if the number of hulls is very large. Or if any of the involved tables is very large.
If your product names aren't legal in the sql you will need to map them to something else.

Assuming that you've pulled the table data into a set of arrays:
<?php
$aplToHullMap = array();
foreach( $AplsHulls as $row )
{
$aplID = $row[ 'apl_id' ];
$hullID = $row[ 'hull_id' ];
$status = $row[ 'status' ];
if( isset( $aplToHullMap[ $aplID ] ) )
$aplToHullMap[ $aplID ][ $hullID ] = $status;
else
$aplToHullMap[ $aplID ] = array( $hullID => $status );
}
?>
<table>
<tr>
<th>Apl Name</th>
<th>Apl Date</th>
<?php
foreach( $Hulls as $row )
echo( "<th>" . $row[ 'production_name' ] . "</th>\r\n" );
?>
</tr>
<?php
foreach( $Apls as $row )
{
?>
<tr>
<td><?php echo( $row[ 'name' ] ); ?></td>
<td><?php echo( $row[ 'date' ] ); ?></td>
<?php
$map = $aplToHullMap[ $row[ 'id' ] ];
foreach( $Hulls as $hull )
{
if( isset( $map[ $hull[ 'id' ] ] ) )
$status = $map[ $hull[ 'id' ] ];
else
$status = 'NA';
echo( "<td>" . $status . "</td>\r\n" );
}
?>
</tr>
<?php
}
?>
</table>

Related

How can sort records by low value. All record in same row in my table

How can i sort the records with same values:
This is the example of values currently i have in my table
record_id a_id b_id c_id a_value b_value c_value
25 A B C 450 390 395
Sorry im not able to create table here.
I'm using Php/MySQL Backend.
I need the following output:
B 390
C 395
A 450
It should be sort by lower value with the id name as well.
I know if all these records in different different rows. it was easy to sort by small value. buy using MIN function of mysql.
Im not sure how to sort this in same row records.
You can accomplish this with php, for example:
<?php
$row = ...
$myArray = array(
'A' => $row['a_value'],
'B' => $row['b_value'],
'C' => $row['c_value'],
);
asort($myArray);
echo '<pre>';
print_r($myArray);
echo '</pre>';
If you like to make it dynamic then you can do something like this:
<?php
$row = ...
$myArray = array();
foreach ($row as $column => $value) {
if (strpos($column, '_value') !== FALSE) {
$myArray[strtoupper(substr($column, 1))] = $value;
}
}
asort($myArray);
echo '<pre>';
print_r($myArray);
echo '</pre>';
SELECT record_id, mim(
SELECT a_value FROM table t2 WHERE t1.record_id = t1.record_id UNION ALL
SELECT b_value FROM table t3 WHERE t3.record_id = t1.record_id UNION ALL
SELECT c_value FROM table t4 WHERE t4.record_id = t1.record_id )
FROM table t1
Your sql should fallow the logic above. I canĀ“t test as i dont have mysql in this pc.

PHP/MYSQL: how to loop matching results from a query already inside a while loop

I'm very new to PHP/MYSQL and find this quite difficult to explain, so hopefully someone will understand what I'm trying to do. I have a database that collects information on artists and songs. I want to be able to link the artists to the song and display the "credit" with the song information.
I have a database with tables similar to this:
Artist
| artist_id, artist_name
Credits
| credit_id, credit_name
Song
| song_id, song_name
Credit_To_Artist
| credit_id, artist_id, song_id
Example data:
Artist
| 2, Peter Mark
Artist
| 5, Mette Christiansen
Credits
| 1, Producer
Credits
| 2, Writer
Credits
| 3, Vocalist
Song
| 23, The Game
Credit_To_Artist
| 1, 2, 23
Credit_To_Artist
| 2, 2, 23
Credit_To_Artist
| 3, 5, 23
I have created a page "song.php" that displays information on each song using mysql_real_escape_string to get the song ID from the URL:
$id = mysql_real_escape_string($_GET['id']);
if (!$id) {
die('Please provide an id!');
}
$query = "SELECT * FROM `Credit_To_Artist` AS c2a
INNER JOIN `Credits` AS cr ON cr.credit_id = c2a.credit_id
LEFT OUTER JOIN `Artist` AS a ON a.artist_id = c2a.artist_id
LEFT OUTER JOIN `Song` AS s ON s.song_id = c2a.song_id
WHERE c2a.song_id = $id";
$res = mysql_query($query);
$row = mysql_fetch_assoc($res);
The issue I'm having is I want to be able to list all of the artists linked to that song, and all of their credits in brackets next to it. Since there are more than one artist linked to each song, and most of them have more than one credit (producer, writer, vocalist etc), I have no idea how to write a loop function that shows both of these. Below is my attempt to show what I mean, although it obviously doesn't work:
while ($row = mysql_fetch_array($res)) {
$artist = $row[artist_name];
echo "$artist";
while ($row = mysql_fetch_array($res)) {
$credit = $row[credit_name];
echo "$credit";
}
echo "<br />";
}
This is what I'd ideally like to achieve from the example data above:
Song: The Game
Credits: Peter Mark (Producer, Writer)
Mette Christiansen (Vocalist)
You have two options:
GROUP_CONCAT(..)
You can use GROUP_CONCAT(..). This mysql function groups values in a column that are in each group. You would alter the sql to group by artist_id in this case.
SELECT a.artist_name as aname, GROUP_CONCAT(c.credit_name) as credits
FROM Credits_To_Artist as c2a
JOIN Artist as a ON c2a.artist_id = a.artist_id
JOIN Credits as c ON c2a.credit_id = c.credit_id
GROUP BY c2a.credit_id
Your rows would look like:
Array( "aname" => "name",
"credits" => "function 1,function 2" )
The biggest problem with GROUP_CONCAT is that if you have to concat a lot of values together, it might exceed the maximum width of the row. This does not seem to be the case for your problem. You would not need a loop with this approach.
Adding to array
If you keep the query as it is, you have a row for each 'credit'. You can prepare your data by adding it to an Array, then use implode(..) in php to add commas.
$artists = Array();
while( $row = mysql_fetch_array($res) ) {
$artist = $row[artist_name];
$credit = $row[credit_name];
if( !array_key_exists( $artist, $artists ) ) {
$artists[$artist] = Array();
}
$artists[$artist][] = $credit;
}
foreach( $artists as $artist => $creditarr ) {
$credits = implode( ", ", $creditarr );
echo "{$artist} ({$credits})<br>";
}
You'll find that preparing your data in an array will sometimes be much faster than writing a query that does the same thing. I would probably choose the latter solution.
Let's start with the tables, here is what i think you should do
artist | id, name
song | id, title
credit | id, credit
song_artists | id, song_id, artist_id
credit_to_artists | id, song_artists_id, credit_id
Should be the way to handle the kind of relationship you want.
And here is the PHP code, it might not be the most efficient one, but it will do the job
$query = "SELECT * FROM song_artists WHERE song_id = $id_from_link";
$result = mysql_query($query);
while($row = mysql_fetch_array($result))
{
$artistQuery = "SELECT * from artist WHERE id = {$row['artist_id']}";
$artistResult = mysql_query($artistQuery);
while($artistRow = mysql_fetch_array($artistResult))
{
echo "The Artist: " . $artistRow['name'];
}
echo $songArtistId;
$creditToArtistQuery = "SELECT * FROM credit_to_artists WHERE song_artists_id = {$row['id']}";
$creditToArtistResult = mysql_query($creditToArtistQuery);
if(mysql_num_rows($creditToArtistResult)>0)
{
echo " { ";
$isFirstCredit = true;
while($creditToArtistRow = mysql_fetch_array($creditToArtistResult))
{
$creditQuery = "SELECT * FROM credit WHERE id = {$creditToArtistRow['credit_id']}";
$creditResult = mysql_query($creditQuery);
while($creditRow = mysql_fetch_array($creditResult))
{
if($isFirstCredit)
{
echo $creditRow['credit'];
$isFirstCredit = false;
}
else
{
echo ", " .$creditRow['credit'];
}
}
}
echo " } <br />";
}
}

How get all two array values using select query at once

I want to get Name and corresponding Score at the latest time. So I tried:
$queryObj = mysql_query("SELECT `Name`,`Score` from `Table`.`Score` where `Date` = ( SELECT max(`Date`) from `Table`.`Score`) and `Name`<>'' ");
then get value from it by:
while( $obj = mysql_fetch_object( $queryObj ) ) {
$data = array();
$data['Name'] = $obj->Name;
$data['Score'] = $obj->Score;
$searches[] = $data;
}
But when I print :
print_r(array_values($searches));
the first value is missing in the array, so that won't be the right way.
I also tried:
$row = mysql_fetch_assoc($queryObj);
for ($i = 0; $i <3; $i++)
print( $row['Name'][$i]." Score: ".$row['Score'][$i]."<br />\n");
But it won't give me the right results also. How do I get the value from that query? (the query is correct, I tested it). Any body has suggestion ?
Edit: I add my sample data here:
Name Score Date
abc 3 2013-08-29 10:11:47
abc 2 2013-08-29 09:39:23
abc 1 2013-08-28 10:22:28
jane 2 2013-08-29 09:39:23
2013-08-29 10:08:36
jane 1 2013-08-29 10:11:47
tarzan 1 2013-08-29 10:11:47
Note: Yes, there is some blank values.
My expected result would be:
abc score 3
jane score 1
tarzan score 1
Ok, so after you have updated your question and provided what you expect, your query should look like this:
SELECT t1.Name, t1.Score
FROM Table.Score t1
INNER JOIN
(
SELECT max(Date) MaxDate, Name, Score
FROM Table.Score
WHERE Name <> ''
GROUP BY Name
) t2
ON t1.Name = t2.Name AND t1.Date = t2.MaxDate
This will give you pairs of Name and Score for each Name with Score based on his latest Date (1 row per Name).
So replace your original query with mine in this line:
$queryObj = mysql_query(" ... ");
Then:
$rows = array();
while($row = mysql_fetch_assoc($queryObj)) {
$rows[$row['Name']] = $row['Score'];
}
And you can nicely foreach it in the exact way you wanted in your last comment:
foreach($rows as $name => $score) {
echo $name . ' - ' . $score . "\n";
}

Join of 3 Tables - How to walk through results?

I have this sql statement joining 3 tables:
SELECT * FROM `int_news`
LEFT JOIN tl_member ON int_news.member_id = tl_member.id
LEFT JOIN tl_news ON int_news.news_id = tl_news.id
The 3 Tables are like this:
Table 1 (int_news)
ID, member_id, news_id
Table 2 (tl_member)
id, firstname, lastname
Table 3 (tl_news)
id, headline
So far so good, but it seems i have a big blackhole in my head making me unable to solve how to output the result like this
For each "headline" i want ALL lastnames e.g.
headline 1 Jonny
Walker
Jim
headline 2 Knopf
Jon
Doe
It sounds your looking something like a pivot so if you group your query by headline, it will display each lastname as a column.
I found this good tutorial on pivots for mysql that might help you http://www.artfulsoftware.com/infotree/queries.php#78
headline 1 Jonny Walker Jim
headline 2 Knopf Jon Doe
Here is a loop that would do that *Forgive my php it's been a while.
$curHeadline = "";
while ( $db_field = mysql_fetch_assoc($result) ) {
if($curHeadline != $db_field['headline'])
{
$curHeadline = $db_field['headline'];
print $curHeadline . $db_field['ID']
}
print $db_field['lastName'] . "<BR>";
}
Try this for mysql :
SELECT tlnews.headline, GROUP_CONCAT(tl_member.last_name)
FROM `int_news` LEFT JOIN tl_member ON int_news.member_id = tl_member.id
LEFT JOIN tl_news ON int_news.news_id = tl_news.id
GROUP BY 1;
Expected Output:
headline1 Johnny,Walker,Jim
headline2 Knopf,Jon,Doe
...
Would something like this vaguely reflect your situation?
<?php
$sql = "
SELECT
`int_news`.`ID` AS `int_news_ID`,
`int_news`.`member_id`,
`int_news`.`news_id`,
`t1_member`.`id` AS `t1_member_id`,
`t1_member`.`firstname`,
`t1_member`.`lastname`, /* desired */
`t1_news`.`id` AS `t1_news_id`,
`t1_news`.`headline` /* desired */
FROM
`int_news`
LEFT JOIN `tl_member` ON `int_news`.`member_id` = `tl_member`.`id`
LEFT JOIN `tl_news` ON `int_news`.`news_id` = `tl_news`.`id`
";
$res = mysql_query($query);
$arrHeadings = array();
while($row = mysql_fetch_assoc($res)) {
// We want to output the results in groups of headings
$arrHeadings[$row['heading']][] = $row;
}
// Don't forget to cleanse for html output (unlike below)
// Loop through the headers
foreach($arrHeadings as $heading=>$arrRow) {
echo '<dl>';
echo '<dt>'.$heading.'</dt>';
echo '<dd>';
// Loop through rows with the same header
foreach($arrRow as $index=>$dbRow) {
echo $dbRow['lastname'].'<br />';
}
echo '</dd>';
echo '</dl>';
}
?>

Cross tabulating a query with php

I'm trying to come up with an efficient way of displaying some data but not succeeding at all.
The table is built dynamically from a few tables to show a row of headers with the columns populated showing the results by site e.g:
Site name | Column A | Column B | Column C => column headers continue from DB query
Site A | Result A | Result B | Result C
Site B | Result C | Result B | Result C
Site C | Result C | Result B | Result C
Results keep going vertically.
Here's the query I'm using
$risk_section=$row_risk_category['risksectid'];
mysql_select_db($database_auditing, $auditing);
$qry_selfsites = sprintf("SELECT
tblself.siteid AS selfsite,
tblsite.sitename AS sitename,
tblsite.address AS address,
tblpct.pctname AS pctname,
tblresultsnew.total,
tblresultsnew.auditid AS auditid,
tblriskcategories.risksectid AS risksectid,
tblriskcategories.risksection AS risksection
FROM tblself
LEFT JOIN tblresultsnew ON tblself.auditid = tblresultsnew.auditid
LEFT JOIN tblsite ON tblself.siteid = tblsite.siteid
LEFT JOIN tblpct ON tblsite.pctid = tblpct.pctid
LEFT JOIN tblriskcategories ON
tblresultsnew.risksectid=tblriskcategories.risksectid
WHERE tblsite.pctid IN (SELECT pctid FROM tblreportpcts WHERE
pctreportid='$pctreportid')
AND tblsite.sitetypeid IN (SELECT sitetypeid FROM tblreportsites WHERE
pctreportid='$pctreportid')
AND tblself.year = %s
ORDER BY tblsite.pctid,tblsite.sitename",
GetSQLValueString($yearofrpt, "int"));
$selfsites = mysql_query($qry_selfsites, $auditing) or die(mysql_error());
$totalRows_selfsites = mysql_num_rows($selfsites);
So the question is, is there a way to get the data from the above query (or an adapted version thereof) to build the table dynamically with all the results lining up correctly?
So far, I can only get it to build vertically.
Sorry, edit time (having worked with the answer below, I realised I hadn't got the question worded very well)
What I'm trying to get is a row of column headers from the query (tblriskcategories.risksection AS risksection) These populate horizontally to give the columns names.
Then underneath, the sitename is displayed with the result corresponding to the column header above i.e.
<table>
<tr>
<th>Sitename</th>
<?php while($row_selfsites=mysql_fetch_assoc($selfsites){
//loop through the section headers pulled from the DB (tblriskcategories)
<th><?php echo $row_selfsites['risksection'];//show the section headers?></th>
<?php }
//end header loop then start another loop to show a row for each site pulled
//out by the query and show the relevant results in the correct column
while($row_selfsites=mysql_fetch_assoc($selfsites)) {
//do the vertical drop matching the header rows with the sitenames from tblsite
//and the results from tblresultsnew
?>
<tr>
<td><?php echo $row_selfsites['sitename'];?></td>
<td><?php echo $row_selfsites['total'];
//these need to grow to fit the headers and each site?></td>
<tr>
<?php } //end displayed data loop?>
</table>
The relevant tables structure below:
tblresultsnew resultsid,auditid,risksectid,total
tblriskcategories risksectid, risksection
tblself selfauditid,siteid,auditid
tblsite siteid,sitename
So tblself holds the list of sites we need the data for and the relevant auditid,
tblresultsnew holds the results - the total column - for each risksectid and each auditid eg, one auditid can have approx 8 risksectid's each with corresponding total
tblriskcategories holds the column headings
tblsite holds the site data to make it mean something
I hope this explains the question a little further.
Thanks again for all the help.
Dave
$rows = array(
0 => array('headingA' => 'results1a', 'headingB' => 'results1b', ),
1 => array('headingA' => 'results2a', 'headingB' => 'results2b', ),
2 => array('headingA' => 'results3a', 'headingB' => 'results3b', ),
);
// $rows is spoofing a db result set as an array
//var_dump( $rows );
$first = true ;
$header_row = "START TABLE <br />" ;
$data_rows = "";
foreach( $rows as $row ) {
foreach( $row as $key=>$value ){
if( $first === true ){
$header_row .= "$key | ";
}
$data_rows .= "$value |";
}
if( $first ) $header_row .="<br />";
$data_rows .= "<br />";
$first = false;
}
echo $header_row . $data_rows . "END TABLE";
Gives:
START TABLE
headingA | headingB |
results1a |results1b |
results2a |results2b |
results3a |results3b |
END TABLE
Is that the kind of solution you are after?

Categories