Echo all results from sql join when using group by - php

I have the following query which I run via PHP:
select
{$tableProducts}.*,
{$tableImages}.*
from {$tableProducts}
left join {$tableImages}
on {$tableImages}.product_id = {$tableProducts}.product_id
group by {$tableProducts}.product_id;
Each product (from product table) can have several images (in images table). I loop through the results with a simple while statement:
while($row = $results->fetch_object()) {
echo $row->product_name; // Product table
echo $row->image_src; // Image table
}
Problem: Only the first image of each product is printed, but I want to display all of them. All images are printed if I remove the "order by" part, but then the product_name is printed once for each image (so if one product has three images, the product_name would be printed three times as well).
How do I best solve this?

That's how GROUP BY works.
If you want to get all images for all products, you can solve that (at least) 3 ways:
1: Do not use GROUP BY, handle it in the loop, like:
$last_product = null;
while($row = $results->fetch_object()) {
if ($last_product !== $row->product_id) {
// new product starts here
$last_product = $row->product_id;
echo $row->product_name; // Product table
}
echo $row->image_src; // Image table
}
2: Use GROUP BY & query all images with different statements within the loop.
$products = <query products>;
while($row = $products->fetch_object()) {
echo $row->product_name; // Product table
$images = <query images for product in $row>;
while($row = $images->fetch_object()) {
echo $row->image_src; // Image table
}
}
3: Use aggregate string functions to get all images for a product. This is only works in special cases, f.ex. here, as URL cannot consist new lines, for example.
In MySQL:
select
{$tableProducts}.*,
group_concat({$tableImages}.image_src SEPARATOR '\n') as image_srcs
from {$tableProducts}
left join {$tableImages}
on {$tableImages}.product_id = {$tableProducts}.product_id
group by {$tableProducts}.product_id;
In PostgreSQL:
select
{$tableProducts}.*,
string_agg({$tableImages}.image_src, '\n') as image_srcs
from {$tableProducts}
left join {$tableImages}
on {$tableImages}.product_id = {$tableProducts}.product_id
group by {$tableProducts}.product_id;
In the loop:
while($row = $products->fetch_object()) {
echo $row->product_name; // Product table
foreach (explode("\n", $row->image_srcs) as $image_src) {
echo $image_src; // Image table
}
}

Related

how to get id for specific car in select statement

Hi I'm trying to display all car names,their images ,rental rate all from different tables in a page. How do I get id for each car in select statement?
for instance:
while($row_showall = mysql_fetch_array($result_showall))
{
$car_id[] = $row_showall['carName_id'];
}
foreach($car_id as $id)
{
$id;
echo $id.'<br/>';
//result is
27773
27774
27778
27779
}
when I place $id outside foreach loop obviously it doesn't loop and the result is
2779.
so when I use select statement here like this:
$query_showdays_1="SELECT ........WHERE car_name.carName_id='$id'";
how do I make it select each and every of the car id instead of only one id?
I'm guessing that your table name is cars, because you haven't provided that info. If it's not then change the cars with your table name.
$query_showdays_1 = "SELECT * FROM cars";
while ($row_showall = mysql_fetch_array($query_showdays_1))
{
echo $row_showall['carName_id'] . '<br />';
}

How to show data from two tables php mysql by avoiding repeats?

Here is what i am doing I have two tables one is categories and other is softwares
Category table Looks like this
cid name
1 Anti-malware
And Software Table looks like this
id title catid
1 Avast 1
And catid is equal cid you can say that catid is id of category
here is my current code
require('config/db.php');
$cateq="SELECT * FROM softwares A INNER JOIN categories B ON A.catid=B.cID ";
$cateresult=mysql_query($cateq);
define('MAX_TITLES_PER_CAT',5);
$cat='';
$tcount=0;
while ($row =mysql_fetch_array($cateresult)){
$title=$row['title'];
$softid=$row['id'];
$cateid=$row['cid'];
$check_pic = "admin/images/icons/".$softid."/image01.jpg";
if (file_exists($check_pic)) {
$icon = "<img src=\"$check_pic\" class=\"home_icon\" />";
} else {
$icon = "<i class=\"icon-archive\"></i>";
}
if ($row['name']!=$cat) {
if ($cat!='') echo '</div>';
$cat=$row['name'];
$tcount=MAX_TITLES_PER_CAT;
echo "<div class=\"widget white_box\"><h3 class=\"widget_title\">".$cat." <i class=\"icon-chevron-right\"></i></h3><ul>";
}
if ($tcount--<=0) continue;
echo '<li>'.$icon.' <a href=\'software-profile.php?pid='.$softid.'\'>'.$title.'</a></li>';
}
if ($cat!='') echo '</ul></div>';
and here are the results what i am getting
http://postimg.org/image/d7zn2k6ev/full/
its repeating certain categories in different divs instead of showing in same div all the results.
When you parse your query results, you assume the rows are sorted by category (if ($row['name']!=$cat)), but no order is guaranteed without an ORDER BY clause.
Add ORDER BY name to the end of your query.

Inefficient SQL Query

I'm building a simple web app at the moment that I'll one day open source. As it stands at the moment, the nav is generated on every page load (which will change to be cached one day) but for the moment, it's being made with the code below. Using PHP 5.2.6 and MySQLi 5.0.7.7, how more efficient can the code below be? I think joins might help, but I'm after advice. Any tips would be greatly appreciated.
<?php
$navQuery = $mysqli->query("SELECT id,slug,name FROM categories WHERE live=1 ORDER BY name ASC") or die(mysqli_error($mysqli));
while($nav = $navQuery->fetch_object()) {
echo '<li>';
echo ''. $nav->name .'';
echo '<ul>';
$subNavQuery = $mysqli->query("SELECT id,name FROM snippets WHERE category='$nav->id' ORDER BY name ASC") or die(mysqli_error($mysqli));
while($subNav = $subNavQuery->fetch_object()) {
echo '<li>';
echo ''. $subNav->name .'';
echo '</li>';
}
echo '</ul>';
echo '</li>';
}
?>
You can run this query:
SELECT c.id AS cid, c.slug AS cslug, c.name AS cname,
s.id AS sid, s.name AS sname
FROM categories AS c
LEFT JOIN snippets AS s ON s.category = c.id
WHERE c.live=1
ORDER BY c.name, s.name
Then iterate thru the results to create the proper heading like:
// last category ID
$lastcid = 0;
while ($r = $navQuery->fetch_object ()) {
if ($r->cid != $lastcid) {
// new category
// let's close the last open category (if any)
if ($lastcid)
printf ('</li></ul>');
// save current category
$lastcid = $r->cid;
// display category
printf ('<li>%s', $r->cslug, $r->cname);
// display first snippet
printf ('<li>%s</li>', $r->cslug, $r->sname, $r->sname);
} else {
// category already processed, just display snippet
// display snippet
printf ('<li>%s</a>', $r->cslug, $r->sname, $r->sname);
}
}
// let's close the last open category (if any)
if ($lastcid)
printf ('</li></ul>');
Note that I used printf but you should use your own function instead which wraps around printf, but runs htmlspecialchars thru the parameters (except the first of course).
Disclaimer: I do not necessarily encourage such use of <ul>s.
This code is just here to show the basic idea of processing hierarchical data got with one query.
First off, you shouldn't query your database in your view. That would be mixing your business logic and your presentation logic. Just assign the query results to a variable in your controller and iterate through it.
As for the query, yup a join can do that in 1 query.
SELECT * -- Make sure you only select the fields you want. Might need to use aliases to avoid conflict
FROM snippets S LEFT JOIN categiries C ON S.category = C.id
WHERE live = 1
ORDER BY S.category, C.name
This will get you an initial result set. But this won't give you the data nicely ordered like you expect. You'll need to use a bit of PHP to group it into some arrays that you can use in your loops.
Something along the lines of
$categories = array();
foreach ($results as $result) {
$snippet = array();
//assign all the snippet related data into this var
if (isset($categories[$result['snippets.category']])) {
$categories[$result['snippets.category']]['snippet'][] = $snippet;
} else {
$category = array();
//assign all the category related data into this var;
$categories[$result['snippets.category']]['snippet'] = array($snippet);
$categories[$result['snippets.category']]['category'] = $category;
}
}
This should give you an array of categories which have all the related snippets in an array. You can simply loop through this array to reproduce your list.
I'd try this one:
SELECT
c.slug,c.name,s.name
FROM
categories c
LEFT JOIN snippets s
ON s.category = c.id
WHERE live=1 ORDER BY c.name, s.name
I didnt test it, though. Also check the indexes using the EXPLAIN statement so MySQL doesnt do a full scan of the table.
With these results, you can loop the results in PHP and check when the category name changes, and build your output as you wish.
Besides a single combined query you can use two separate ones.
You have a basic tree-structure here with branch elements (categories table) and leaf elements (snippets table). The shortcoming of the single-query solution is that you get owner brach-element repeatedly for every single leaf element. This is redundant information and depending on the number of leafs and the amount of information you query from each branch element can produce large amount of additional traffic.
The two-query solution looks like:
$navQuery = $mysqli->query ("SELECT id, slug, name FROM categories WHERE live=1 ORDER BY name")
or die (mysqli_error ($mysqli));
$subNavQuery = $mysqli->query ("SELECT c.id AS cid, s.id, s.name FROM categories AS c LEFT JOIN snippets AS s ON s.category=c.id WHERE c.live=1 ORDER BY c.name, s.name")
or die (mysqli_error ($mysqli));
$sub = $subNavQuery->fetch_object (); // pre-reading one record
while ($nav = $navQuery->fetch_object ()) {
echo '<li>';
echo ''. $nav->name .'';
echo '<ul>';
while ($sub->cid == $nav->id) {
echo '<li>';
echo ''. $sub->name .'';
echo '</li>';
$sub = $subNavQuery->fetch_object ();
}
echo '</ul>';
}
It should print completely the same code as your example
$navQuery = $mysqli->query("SELECT t1.id AS cat_id,t1.slug,t1.name AS cat_name,t2.id,t2.name
FROM categories AS t1
LEFT JOIN snippets AS t2 ON t1.id = t2.category
WHERE t1.live=1
ORDER BY t1.name ASC, t2.name ASC") or die(mysqli_error($mysqli));
$current = false;
while($nav = $navQuery->fetch_object()) {
if ($current != $nav->cat_id) {
if ($current) echo '</ul>';
echo ''. $nav->cat_name .'<ul>';
$current = $nav->cat_id;
}
if ($nav->id) { //check for empty category
echo '<li>'. $nav->name .'</li>';
}
}
//last category
if ($current) echo '</ul>';

MySQL: printing data just once for each grouping

I'm coding in PHP/MySQL and have the following query to fetch products and product group data:
SELECT products.id,products.name,product_groups.id,product_groups.name
FROM products
INNER JOIN product_groups
ON products.id=product_groups.id
WHERE products.name LIKE '%foobar%'
ORDER by product_groups.id ASC
So this query fetches products and orders them by product group. What I would like to have is to display product_groups.name just once for each product grouping. So even if I have ten shoe products, the group name "Shoes" is only displayed once.
I'm using the following PHP to print out the results:
while ($data = mysql_fetch_array($result))
If you want it done in the MySQL query, it is honestly more trouble than it's worth. For one, the syntax is really wonky (as I recall) to have a group name listed at the top of each grouping. And the results are still treated as rows, so the group name will be treated like a row with all the other columns as Null, so you won't really save any time or effort in the PHP script as it has to do an if statement to catch when it hits a group name instead of the group data.
If you want it done by the PHP while loop, Johan is on the right track. I use the following for a similar situation:
$result = $sql->query($query);
$prev_group = "";
while($data = $result->fetch_assoc()){
$curr_group = $data['group'];
if ($curr_group !== $prev_group) {
echo "<h1>$curr_group</h1>";
$prev_group = $curr_group;
}
else {
echo $data;
.....
}
Obviously the echo data would be set up to echo the parts of the data the way you want. But the $prev_group/$curr_group is set up so that the only time they won't match is when you are on a new group and thus want to print a header of some sort.
while($data = mysql_fetch_assoc($result)){
if($data['product_groups.name'] != $groupname){
echo "Groupname: ".$data['product_groups.name']."<br />";
$groupname = $data['product_groups.name'];
}
echo "ID: ".$data['products.id']."<br />";
echo "Name: ".$data['products.name']."<br />";
}
Maybe you can do like this!?

PHP, SQL newbie help

I have the following table:
Comments
--------
id PK
cid
content
uid
comment
If the content is image /i want it to print ?imgID=$cid and get the data from the row title from the table images and if it's a thread I want it to print ?threadID=$cid and get the title from the table threads and so on. How should I do this?
<h3>Latest comments</h3>
<?php
$rs = mysql_query("
SELECT *
FROM comments
LEFT JOIN threads
ON threads.id = comments.cid
WHERE comments.uid = $id
");
while ($row = mysql_fetch_assoc($rs)) {
echo $row['title'];
}
while ($row = mysql_fetch_assoc($rs)) {
if($row['content'] == "image") {
echo "?imgID={$cid}";
$title = mysql_fetch_assoc(mysql_query("SELECT title from images WHERE id = '$cid'");
} elseif ($row['content'] == "thread") {
echo "?threadID={$cid}";
$title = $row['title']; // Will only work as long as there is no title field in comments, oherwise use threads.title
}
}
echo $title;
There you go. The curly brackets around the $cid aren't strictly nessecary, but they help avoid issues where if you are trying to print text afterwards, and it reads the variable name as something else.
$q="SELECT c.id, c.cid, c.content, c.uid, c.comment,
COALESCE(t.title,i.title) AS the_title
FROM comments c LEFT JOIN threads t ON (c.cid=t.id AND c.content='thread')
LEFT JOIN images i ON (c.cid=i.id AND c.content='image');
The above combines your two queries together and puts the title attribute in one column.

Categories