MYSQL multiple grouping with join - php

SELECT
p.product,
q.format,
p.title
FROM
product p
JOIN info q ON p.product = q.product
WHERE p.user='$user'
GROUP BY p.product,q.format
I want to first group by 'product' from the product table but the also by format on the info table.
This is to not show duplicates of format and product. At the moment only the grouping by product is working.
Table - products
product | title
0 one
1 two
1 two - a
2 three
Table - product_details
product | title | format |
0 one home
1 two home
1 two - a home
2 three work
So for this example I want a list like:
product | title | format
0 one home
2 three work
Instead of:
product | title | format
0 one home
1 two home
2 three work

After your table structures were posted, I can see what your intent is, I believe. It looks like you are attempting to limit your output result set to those values for product.product which are never repeated. That is, values for product.product which have exactly one product.title.
For that, you can use a GROUP BY aggregation to return only those with COUNT(*) = 1 after the group is applied.
In this case, since you only expect one row back per product.product anyway, you can do the aggregation at the top level, not requiring a subquery. If you had joined in other tables, and ended up getting multiple rows back per product due to other one-to-many relationships, you would need to use the subquery method instead (to be portable anyway - MySQL would still probably allow this)
SELECT
p.product,
q.format,
p.title
FROM
products p
JOIN product_details q ON p.product = q.product
GROUP BY
p.product,
q.format,
p.title
HAVING COUNT(*) = 1
Here is a demonstration: http://sqlfiddle.com/#!2/72eda/6
If you did expect multiple rows back per p.product, such as if you joined in additional one-to-many related tables, an efficient way to handle that is to perform a JOIN against a subquery that imposes that limit in the HAVING clause. Those which don't meet the HAVING condition won't be returned in the subquery and therefore get discarded by the INNER JOIN.
SELECT
p.product,
q.format,
p.title
FROM
products p
INNER JOIN product_details q ON p.product = q.product
/* Subquery returns only product values having exactly 1 row */
INNER JOIN (
SELECT product
FROM products
GROUP BY product
HAVING COUNT(*) = 1
) pcount ON p.product = pcount.product
WHERE p.user = '$user'
http://sqlfiddle.com/#!2/72eda/2

Related

How do you control which match is chosen when running a `SELECT INNER JOIN WHERE t1.column=t2.column` and there are multiple matches?

I have two MySQL tables:
Products
title id hidden categories
Thing 10 N 12,14,
Stuff 23 N 12,
Object 41 Y 13,14
Images
filename id productid
aca8t.jpg 1 10
ev7ha.jpg 2 10
mscpk.jpg 3 10
asges.jpg 4 23
fcuhg.jpg 5 23
scvfe.jpg 6 41
vf6kl.jpg 7 41
fgszy.jpg 8 41
I build a list of product titles and images using a SELECT statement like this:
SELECT t1.title,
t1.id,
t1.hidden,
t1.categories,
t2.image
FROM products AS t1
INNER JOIN pimage AS t2
WHERE t1.categories LIKE '%$categoryId%'
AND t1.id=t2.productid
AND NOT t1.hidden='Y'
GROUP BY id
ORDER BY id ASC
After running this query, I have a list of non-hidden products in a given category, as well as their IDs and one image. However, the selection of an image appears to be random. Sometimes it's alphabetical, sometimes it's the lowest ID, and sometimes it's neither. However, it's always the same when the filenames for a given productid stay the same.
This is in use on a small website where product managers upload photos of a product and its accessories. The first photo should be used as a thumbnail and visible on the category page. However, a photo of a random accessory is sometimes selected as the thumbnail image, and the product managers have to re-upload the images until the right one gets selected. This process is onerous.
How can I modify the SQL statement so that the first photo (the filename with the lowest images.id) is selected?
Try using a correlated subquery instead of a join/group by:
SELECT p.*,
(SELECT i.image
FROM pimage i
WHERE p.id = i.productid
ORDER BY i.image ASC
LIMIT 1
) as image
FROM products p
WHERE p.categories LIKE '%$categoryId%' AND
p.hidden <> 'Y'
ORDER BY p.id ASC
If this statement were executed on another relational database (other than MySQL/MariaDB), it would throw an error with a message along the lines of "non-aggregates in SELECT list not included in GROUP BY".
But A MySQL specific extension to GROUP BY allows this query to execute in MySQL, but as you've noticed, the values returned for the non-aggregates in the SELECT list are indeterminate, MySQL will return a value from some row.
The normal pattern is to use a MAX() or MIN() aggregate function to "control" which value is returned.
In your case, that would work to return the minimum id value, but getting the other values on that same row is more problematic. If you only need to return a few columns, you can use a correlated subqueries in the SELECT list.
Another approach is to use an inline view and a join operation.
SELECT t1.title
, t1.id
, t1.hidden
, t1.categories
, t2.image
FROM products t1
JOIN ( SELECT n.productid
, MIN(n.id) AS min_id
FROM pimage n
GROUP BY n.productid
) m
ON m.productid = t1.id
JOIN pimage t2
ON t2.id = m.min_id
WHERE t1.categories LIKE '%$categoryId%'
AND t1.hidden='Y'
GROUP BY t1.id
ORDER BY t1.id ASC
This approach is useful when you need to return a additional columns from the row with the "minimum" id. For example, you also needed to include in the SELECT list:
, t2.fee
, t2.fi
, t2.fo
, t2.fum

create a result set difference between two tables in Mysql

I have two table suppose products and auto_assign_prod_list. I want to populate a dropdown list with the id of products table that are not present in auto_assign_prod_list table.
Suppose,
product table contain
Id
------
1
2
3
4
5
auto_assign_prod_list table contain
Id
-----
1
5
So, my result set will be
2
3
4
How is it possible using MySQL and PHP ?
Try this:
SELECT Id FROM product
WHERE Id NOT IN (SELECT Id FROM auto_assign_prod_list)
It will select the ids from product table which are not in auto_assign_prod_list table.
Result:
Id
------
2
3
4
See result in SQL Fiddle.
use a left join
select p.id
from products p
left join auto_assign_prod_list a on a.id = p.id
where a.id is null
SQLFiddle demo
See this great explanation of joins

Combining multiple mySQL rows without duplicates

I have 4 tables in a MySQL database, suppliers, categories, subcats & listings.
listings is a join table to allow the many to many relationships between suppliers, categories and subcats, the structure of each is as follows
suppliers
sp_id sp_name sp_email
1 Apple info#apple.co
2 Samsung info#samsung.co
categories
cat_id cat_name
3 Electronics
4 Software
subcats
subcat_id subcat_name cat_id
5 Mobiles 3
6 Computers 3
listings
list_id sp_id subcat_id
1 1 5
2 1 6
I am trying to combine and extract the data together so there is only one entry per supplier with multiple subcategories listed eg:
RESULT
sp_id sp_name sp_email cat_name / cats subcat_name / subcats
1 Apple info#apple.co Electronics, Software Mobiles, Computers
2 Samsung info#samsung.co Electronics Mobiles
Currently I have the following query
SELECT *
FROM suppliers as s
LEFT JOIN listings as l ON s.sp_id=l.sp_id
LEFT JOIN subcats as p ON p.subcat_id=l.subcat_id
LEFT JOIN categories as c ON c.cat_id=p.cat_id
ORDER BY s.sp_id
However this outputs multiple entries per supplier, one entry for each category or subcategory associated with it. Is there an easier way to do it via SQL or with multiple queries in PHP?
I am at the limits of my current knowledge of mySQL and any suggestions or prods in the right direction would be greatly appreciated.
use GROUP_CONCAT
SELECT s.*,
GROUP_CONCAT(c.cat_name) catName,
GROUP_CONCAT(p.subcat_name) subcatName
FROM suppliers as s
LEFT JOIN listings as l ON s.sp_id=l.sp_id
LEFT JOIN subcats as p ON p.subcat_id=l.subcat_id
LEFT JOIN categories as c ON c.cat_id=p.cat_id
GROUP BY s.sp_id, s.sp_name, s.sp_email
ORDER BY s.sp_id
You can use GROUP BY in combination with GROUP_CONCAT to only show unique values from a certain column with a comma-separated list as one of the values.
You want to use the GROUP BY expression along with the GROUP_CONCAT function. This will allow you to merge together rows which share certain values while concatenating the ones they don't. For example, in your case this may wind up being something like:
SELECT s.sp_id, s.sp_name, group_concat(', ', c.cat_name)
FROM suppliers as s
LEFT JOIN listings as l ON s.sp_id=l.sp_id
LEFT JOIN subcats as p ON p.subcat_id=l.subcat_id
LEFT JOIN categories as c ON c.cat_id=p.cat_id
ORDER BY s.sp_id
GROUP BY s.sp_id, s.sp_name;

Get a picture for each album

I have two table for gallery system :
gallery_cat(
gallery_cat_id PK,
gallery_cat_name
)
gallery(
gallery_id PK,
gallery_cat_id FK,
gallery_name,
gallery_file_name,
gallery_date
)
I need to write a SQL query that return one picture from gallery table for each album, the purpose of this that I need to list the albums with one picture for each.
gallery_name | gallery_cat_name| gallery_file_name
-------------+-----------------+------------------
pic1 | Album1 | pic1.jpg
This should do the trick:
SELECT g2.gallery_name, gc2.gallery_cat_name, g2.gallery_file_name
FROM gallery g2
INNER JOIN gallery_cat gc2 ON (g2.gallery_cat_id = gc2.gallery_cat_id)
WHERE g2.gallery_id IN (
SELECT g.gallery_id
FROM gallery g
GROUP BY g.gallery_cat_id)
Explanation:
At the end is a sub-select
IN (
SELECT g.gallery_id
FROM gallery g
GROUP BY g.gallery_cat_id) <<-- select 1 random g.id per gallery_cat.
Here I select all g.id, but because of the group by clause it will reduce the results to 1 row per grouped by item. I.e. 1 row (chosen more or less at random) per g.gallery_cat_id.
Next I do a normal select with a join:
SELECT g2.gallery_name, gc2.gallery_cat_name, g2.gallery_file_name
FROM gallery g2
INNER JOIN gallery_cat gc2 ON (g2.gallery_cat_id = gc2.gallery_cat_id)
WHERE g2.gallery_id IN (
Because I refer to the same table twice in the same query you have to use an alias(*).
I select all names and all catnames and all filenames.
However in the where clause I filter these so that only rows from the sub-select are shown.
I have to do it this way, because the group by mixes rows into one messed up ow, if I select from that directly I will get values from different rows mixed together, not a good thing.
By first selecting the id's I want and then matching full rows to those id I prevent this from happening.
*(in this case with this kind of subselect that's not really 100% true, but trust me on the point that it's always a good idea to alias your tables)
This attempts to select the most recent gallery_date for each category ID and join against gallery_cat
SELECT
c.gallery_cat_id,
c.gallery_cat_name,
i.lastimg
FROM
gallery_cat c
LEFT JOIN (
SELECT gallery_cat_id, gallery_filename AS lastimg, MAX(gallery_date)
FROM gallery
GROUP BY gallery_cat_id, gallery_filename
) i ON c.gallery_cat_id = i.gallery_cat_id
You can use SQL JOINS to do this, otherwise you would have to loop out all the albums and pick one random picture from each which would be less efficient.

Performing a search... one large query or a medium and many small queries?

Right now the below query returns 1 row for each result.... I need to show a list of tags for each row and I'm debating whether to change this query so it INNER JOINS the tags and parsing out the data on the PHP end, or if I should just run a separate query for each return result.... what are your thoughts on this? Should I pull the extra data and parse it on the PHP end or run additional queries (maybe 25-30)?
SELECT
content.id,
content_text.content
FROM content
INNER JOIN content_text ON (
content_text.content_id = content.id AND
content_text.language_id = 1
)
INNER JOIN tags_to_content ON (
tags_to_content.tag_id IN (1)
)
You could rely on a small command that is not used much in mysql : GROUP_CONCAT().
Official Doc for GROUP_CONCAT
Here is a small example (not based on your current question since I don't know the specifics, its a example that could apply to a blog):
SELECT
p.title as title,
p.content as content,
GROUP_CONCAT(c.name order by c.name SEPARATOR ', ') as categories
FROM
post as p
INNER JOIN
post_category as pc
ON
p.id = pc.post_id
INNER JOIN
category as c
ON
pc.category_id = c.id
GROUP BY
p.id
Which would return
+------------------+--------------------+------------------------------------+
| title | content | categories |
+------------------+--------------------+------------------------------------+
| Some title | Some content | category 1, category 2, category 3 |
| Some other title | Some other content | category 1, category 3, category 4 |
...
+------------------+--------------------+------------------------------------+
I always trade off the number of results to loop through vs the total number of rows, and the number of records in the joined tables.
Let's say you have 1000 records in your table "students", and all students fall in 3 categories (categorie_id left join categories on... ), I'd loop through the students and fetch (+ cache) the joined table (categories).
If you have 1000 students, and 1500 parents (parent_id from table parents), and your result for your query (eg "students outside of this city") returns all students, I'd go for the joined-tables. If you only expect 10% of the students to be returned (eg "students outside of the city"), I'd again go for small queries (like the first example).
If in doubt, run an example query and put a timer on it ... ?

Categories