I have a situation where I have several tables that I am pulling from with an INNER JOIN. There is a one to many relationship, where the main table has one line for each park, but the photos table could have several lines for some parks. My code works in that a photo is being displayed for each park, but I can only get one to display. I suspect the problem is in the foreach loop, but I'm a little stumped. Here's the code:
try
{
$sql = 'SELECT parks.id, parks.state, parks.name, parks.description, parks.site, parks.sname, parks.street, parks.city, parks.zip, parks.phone, comments.comment, comments.commentname, events.event, events.date, events.description2, photos.parkid, photos.type, photos.filename, photos.big FROM parks
INNER JOIN comments INNER JOIN photos INNER JOIN events ON parks.parkid = comments.parkid and parks.parkid = photos.parkid and parks.parkid = events.parkid
GROUP BY parks.id
ORDER BY parks.name asc';
$result = $pdo->query($sql);
}
catch (PDOException $e)
{
$error = 'Error fetching data: ' . $e->getMessage();
include 'output.html.php';
exit();
}
//This is pulling the information from the database for display. On the foreach it will display each
//line until there are no more lines to display.
foreach ($result as $row)
{
$datas[] = array ('id' =>$row['id'],
'parkid' =>$row['parkid'],
'state' =>$row['state'],
'name' =>$row['name'],
'description' =>$row['description'],
'site' =>$row['site'],
'sname' =>$row['sname'],
'street' =>$row['street'],
'city' =>$row['city'],
'phone' =>$row['phone'],
'zip' =>$row['zip'],
'commentname' =>$row['commentname'],
'comment' =>$row['comment'],
'event' =>$row['event'],
'date' =>$row['date'],
'description2' =>$row['description2'],
'type' =>$row['type'],
'filename' =>$row['filename'],
'big' =>$row['big']);
}
include 'writing.html.php';
and
<?php
foreach ($datas as $name)
{
if ($name['state'] === 'PA')
{
?>
<a href="#header" title="return to the top of the page">Back to top</a>
<input type="hidden" name="id" value="' . $name['id'] . '" />
<h1 id="name"> <?php echo ($name['name']) ?> </h1>
<p id="descriptionlist">
<?php echo ($name['description']) ?>
<br />
<ul id="link">
<li class="l1">
<a href=<?php echo $name['site'] ?> target="_blank"> <?php echo $name['sname'] ?> </a>
</li>
</ul>
</p>
<h2>Location</h2>
<div class = "loc">
<p class="loct">
<a class = "fancyImg" href="maps/<?php echo $name['id'] ?>state.gif"> <img src= "maps/<?php echo $name['id'] ?>state.gif"> </a>
<br />
<php echo ($name['street']) . ?>
<br />
<?php echo ($name['city']) .
($name['state']) .
($name['zip']) ?>
<br>
<?php echo ($name['phone']) ?>
<br> <br>
</p>
</div>
<h2>Trail Map</h2>
<div class = "map">
<p class = "mapt">
Click to Enlarge
<a class ="fancyImg" href= "/maps/<?php echo $name['id'] ?>maplink.gif">
<img src= "/maps/<?php echo $name['id'] ?>.gif"></a> <br> <br>
</p>
</div>
<h2>Photos</h2>
<div class = "pho">
<p class = "phot">
<a class = "fancyImg" href= "/assets/indiv/<?php echo $name['big'] ?>.gif">
<img src= "<?php echo $name['filename'] ?>.gif"></a>**
Submit <i>your</i> photos of <?php echo ($name['name']) ?> through our <ul id = "link"><li><a href="https://www.facebook.com/Ride4Wheel">Facebook Page!</li></ul></a></h3><p> Or go to our Contact Us page for information on how to e-mail us your favorite pictures!
</p>
</div>
The issue at hand is in the pho div at the end here. I was hoping that $name['big'] would give me all of the items for this loop, but it only gives me the first. I'm missing something fundamental here.
The link is http://www.ride4wheel.com/new_ma.php
The relational database results will be always returned as rows beside the fact that your query has a relationship one to many, I think in your case you will have to loop again using your unique Id and looking for different values for 'big' field.
I don't also think that you need the foreach
loop to make you results like associative array, you may need to use this instead : PDOStatement::fetchAll
The problem is in your query. You INNER JOIN
photos which would create a row for all photos and the parks repeating for each assigned foto. The GROUP BY
distincts the parks again but forces MySQL to select one of the assigned photos.
If a park doesn't have a picture, it will not be in the list at all ( INNNER JOIN
= give me all which have a relation in both tables )
You could remove the GROUP BY
and replace the INNER JOINs
by LEFT JOINs
( give me all parks and attach the images and comments if you got any ) which would require checks in the output loop ( whats my current park, did I display this park already, display curren picture, display current comment )
The cleaner
but slower solution would be to remove the joins and field for fetching images and comments so you only get the parks; then in the parks loop fetch the comments and fetch the images for the current park in two extra queries.
EDIT 1:
Just like said in the comments this is not a really good alternative since you'll fire two additional queries and run two more loops for every park you add. I want you to understand the basic problematic of a many to many relationship - db results can be only two-dimensional tables
EDIT 2:
I've fixed your code to what I suggested, watch this
<?php
try
{
$sql = 'SELECT
parks.id,
parks.state,
parks.name,
parks.description,
parks.site,
parks.sname,
parks.street,
parks.city,
parks.zip,
parks.phone,
comments.comment,
comments.commentname,
events.event,
events.date,
events.description2,
photos.type,
photos.filename,
photos.big
FROM
parks
LEFT JOIN comments ON comments.parkid = parks.id
LEFT JOIN photos ON photos.parkid = parks.id
LEFT JOIN events ON events.parkid = parks.id
ORDER BY
parks.name ASC';
$result = $pdo->query($sql);
}
catch (PDOException $e)
{
$error = 'Error fetching data: ' . $e->getMessage();
include 'output.html.php';
exit();
}
//This is pulling the information from the database for display. On the foreach it will display each
//line until there are no more lines to display.
$datas = array();
foreach ( $result as $row )
{
// we didn't add this park yet
if ( !array_key_exists( $row['id'], $datas )
{
$datas[$row['id']] = array (
'id' => $row['id'],
'state' => $row['state'],
'name' => $row['name'],
'description' => $row['description'],
'site' => $row['site'],
'sname' => $row['sname'],
'street' => $row['street'],
'city' => $row['city'],
'phone' => $row['phone'],
'zip' => $row['zip'],
'comments' => array(),
'photos' => array(),
'events' => array()
);
}
// if there is no comment for this park, this will be null
if ( $row['comment'] )
{
$datas[$row['id']]['comments'][] = array (
'comment' => $row['comment'],
'commentname' => $row['commentname']
);
}
// same for photos
if ( $row['filename'] )
{
$datas[$row['id']]['photos'][] = array (
'type' => $row['type']
'filename' => $row['filename']
'big' => $row['big']
);
}
// same for events
if ( $row['event'] )
{
$datas[$row['id']]['events'][] = array (
'event' => $row['event'],
'date' => $row['date'],
'description2' => $row['description2']
);
}
}
include 'writing.html.php';
and writing.html.php
<?php
foreach ($datas as $park)
{
// do you only want to display PA?
// then add " WHERE state = 'PA' " to your query
if ($park['state'] === 'PA')
{
?>
<a href="#header" title="return to the top of the page">Back to top</a>
<input type="hidden" name="id" value="' . $park['id'] . '" />
<h1 id="name"> <?php echo ($park['name']) ?> </h1>
<p id="descriptionlist">
<?php echo ($park['description']) ?>
<br />
<ul id="link">
<li class="l1">
<a href=<?php echo $park['site'] ?> target="_blank"> <?php echo $park['sname'] ?> </a>
</li>
</ul>
</p>
<h2>Location</h2>
<div class = "loc">
<p class="loct">
<a class = "fancyImg" href="maps/<?php echo $park['id'] ?>state.gif"> <img src= "maps/<?php echo $park['id'] ?>state.gif"> </a>
<br />
<php echo ($park['street']) . ?>
<br />
<?php echo ($park['city']) .
($park['state']) .
($park['zip']) ?>
<br>
<?php echo ($park['phone']) ?>
<br> <br>
</p>
</div>
<h2>Trail Map</h2>
<div class = "map">
<p class = "mapt">
Click to Enlarge
<a class ="fancyImg" href= "/maps/<?php echo $park['id'] ?>maplink.gif">
<img src= "/maps/<?php echo $park['id'] ?>.gif"></a> <br> <br>
</p>
</div>
<?php if ( !empty( $park['photos'] ) ): ?>
<h2>Photos</h2>
<?php foreach( $park['photos'] as $photo ): ?>
<div class = "pho">
<p class = "phot">
<a class = "fancyImg" href= "/assets/indiv/<?php echo $photo['big'] ?>.gif">
<img src= "<?php echo $photo['filename'] ?>.gif"></a>**
Submit <i>your</i> photos of <?php echo ($photo['name']) ?> through our <ul id = "link"><li><a href="https://www.facebook.com/Ride4Wheel">Facebook Page!</li></ul></a></h3><p> Or go to our Contact Us page for information on how to e-mail us your favorite pictures!
</p>
</div>
<?php endforeach; ?>
<?php endif; ?>
<?php if ( !empty( $park['comments'] ) ): ?>
<h2>Comments</h2>
<?php foreach( $park['comments'] as $comment ): ?>
<?php echo $comment['comment']; ?>
<?php endforeach; ?>
<?php endif; ?>
<?php if ( !empty( $park['events'] ) ): ?>
<h2>Events</h2>
<?php foreach( $park['events'] as $event ): ?>
<?php echo $event['event']; ?>
<?php endforeach; ?>
<?php endif; ?>
<?php
}
}
EDIT 3:
What you need to understand is, that you can only return a two-dimensional result table from the database. So how would you return 10 photos for one park if your park would not be repeated? Right, that's not ( cleanly ) possible. That's why you have to filter the duplicate rows and only take the photos, comments and events once you got your park.
In the code above I use the unique park id as array index so I can determine wether this park is already in the datas array or not, then add photos, events and comments.
If you do print_r( $results )
and compare this to print_r( $datas )
you'll understand the whole thing
Unless every one of the parks has at least one comment and one photo, your query won't return rows for all parks. This is how INNER JOIN
works, it only takes the intersection of all the rows that match according to the join criteria you specify.
If you want to display all parks, regardless of whether or not they have comments and/or photos, may I suggest you use LEFT JOIN
instead:
SELECT
parks.id,
parks.state,
parks.name,
parks.description,
parks.site,
parks.sname,
parks.street,
parks.city,
parks.zip,
parks.phone,
comments.comment,
comments.commentname,
events.event,
events.date,
events.description2,
photos.parkid,
photos.type,
photos.filename,
photos.big
FROM
parks
LEFT JOIN comments ON (parks.parkid = comments.parkid)
LEFT JOIN photos ON (parks.parkid = photos.parkid)
LEFT JOIN events ON (parks.parkid = events.parkid)
ORDER BY
parks.name asc
EDIT :
I removed the GROUP BY
clause since I'm pretty sure it's not what you want; you'll probably want to see every photo or comment associated with a park, not have them all arbitrarily reduced to a single row for each park.
EDIT :
Using this query should solve any problems you might have had with the original query not returning the rows you were expecting for comments and photos. You had said that the relationship was 1 park to many comments and many photos. This query should return every comment and photo associated with each park, but there will be multiple rows returned for each park. You'll need to adjust your code to compensate for this, perhaps by creating an array of park data, where each element in the array corresponds to a single park. Each element should also contain an array of comments and photos, for which there might be more than one for each park.
Each iteration over the result set should be prepared to create a new element in the park data array, or recognize that the current result set row corresponds to a park whose data you've already begun to build. Additionally, if there are any comments or photos in the current iteration, they should be added to the current (new or existing) park's array of comments and/or photos. Here's an example foreach
loop of how to do this:
$parks = array();
foreach ($result as $row) {
// Create a new element in $parks because it's not in the $parks array yet
if (! array_key_exists($row['id'], $parks)) {
$park = array();
$park['id'] = $row['id'];
$park['state'] = $row['state'];
$park['name'] = $row['name'];
$park['description'] = $row['description'];
$park['site'] = $row['site'];
$park['sname'] = $row['sname'];
$park['street'] = $row['street'];
$park['city'] = $row['city'];
$park['zip'] = $row['zip'];
$park['phone'] = $row['phone'];
$park['comments'] = array();
$park['photos'] = array();
$parks[$row['id']] = $park;
} else {
// Otherwise, this is a park we've already seen
$park = $parks[$row['id']];
}
// If there are comments in this result set row, add them
if ($row['comment'] || $row['commentname']) {
$park['comments'][] = array(
'comment' => $row['comment'],
'commentname' => $row['commentname']
);
}
// If there are photos in this result set row, add them
if ($row['type'] || $row['filename'] || $row['big']) {
$park['photos'][] = array(
'type' => $row['type'],
'filename' => $row['filename'],
'big' => $row['big']
);
}
}
This code isn't that pretty or well-factored, and it could certainly be improved. I just hastily threw it together to give you an example of how to build such a structure.
Relational data doesn't always map cleanly to hierarchical object graphs, this is the object-relational impedance mismatch .
要获取一个数组而不是第一个foreach循环中的所有行,请尝试使用<?$datas = $pdo->fetchAll();?>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.