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;
Related
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
I have 3 tables : videos, categories, video_categories.
In videos, I have id, title, and other fields.
In categories, I have id and name.
In video_categories, I have id, video_id, and category_id.
One video can have multiple categories. So the video_categories table will be something like this.
id video_id category_id
1 1 1
2 1 2
3 1 3
If I want to have a list of videos and display their categories, which would be preferred?
Via PHP, call 1 query to get all videos, then loop on that to query to get each video's categories, and another query to get the category name. This will be really slow if the table is huge, right?
Via MySQL joins (need help on this). If I left join videos to video_categories, there will be 3 results of the same video_id. I can use GROUP BY or SELECT DISTINCT to get unique result, but how can I now get the categories' names?
My expected result will be something like this:
id title categories
1 Video1 pop, rock, jazz
For option 2, use GROUP_CONCAT. It will be ok
SELECT v.id, v.title, GROUP_CONCAT(c.name)
FROM videos v
INNER JOIN video_categories vc ON vc.video_id = v.id
INNER JOIN categories c ON vc.category_id = c.id
GROUP BY v.id, v.title
For Group_Concat() function , is the default separator. That's why I don't use it here.
Guessing the names of your other columns..
SELECT v.video_id, v.title, GROUP_CONCAT(c.category_name SEPARATOR ', ')
FROM videos v
LEFT JOIN video_categories vc ON vc.video_id = v.video_id
LEFT JOIN categories c ON c.category_id = vc.category_id
GROUP BY v.video_id, v.title
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 ... ?
i got three tables
CATS
id name
------------------------------
1 category1
2 category2
3 category3
4 category4
PRODUCT
id name
------------------------------
1 product1
2 product2
ZW-CAT-PRODUCT
id_cats id_product
------------------------------
1 1
3 1
4 2
now i want to get my products and their categories
product1 => category1,category3
product2 => category4
is there a way to get this array (or object or something) with one mysql query?
i tried a bit with JOINS, but it seems thats this is not exactly what i need, or?
currently i'm using 3 querys (i think thats too much).
any suggestions?
edit
and on the other way, what if i want to get ALL products of a specific category?
can this also be done in one query?
You can use GROUP_CONCAT to get a separated list in your results.
SELECT p.*,
GROUP_CONCAT(c.name SEPARATOR ',') as cats
FROM PRODUCT p
LEFT JOIN ZW-CAT-PRODUCT l
ON l.id_product=p.id
LEFT JOIN CATS c
ON c.id=l.id_cats
GROUP BY p.id
So basically, this first does some joins to get all the data. If you were to replace the GROUP_CONCAT line with just c.name, you would see a row for each product_id/category pair. The GROUP BY tells it to group results based on product ID, and then GROUP_CONCAT(c.name..) is telling to it take all the different c.name values that occur in a group (so for each product ID, since you're grouping by product ID) and concatenate those values into one string, using , as the separator.
So to get all products for a each category in the same style, it would be like this,
SELECT c.*,
GROUP_CONCAT(p.name SEPARATOR ',') as products
FROM CATS c
LEFT JOIN ZW-CAT-PRODUCT l
ON l.id_cats=c.id
LEFT JOIN PRODUCT p
ON p.id=l.id_product
GROUP BY c.id
EDIT: To get just the product rows for a particular category (as requested in comment), it's this.
SELECT p.*
FROM PRODUCT p
LEFT JOIN ZW-CAT-PRODUCT l
ON l.id_product=p.id
LEFT JOIN CATS c
ON c.id=l.id_cats
WHERE c.name='xyz';
If you need just comma-separated list of categories for every product, look at MySQL's GROUP_CONCAT() aggregate function:
SELECT p.*, GROUP_CONCAT(c.name) AS categories
FROM PRODUCT p
LEFT JOIN ZW-CAT-PRODUCT cp ON p.id = cp.id_product
LEFT JOIN CATS c ON cp.id_cats = c.id
GROUP BY p.id
To get all products of a specific category (by category ID):
SELECT p.*
FROM PRODUCT p
INNER JOIN ZW-CAT-PRODUCT cp ON p.id = cp.id_product
WHERE cp.id_cats = 42
The same, but by category name:
SELECT p.*
FROM PRODUCT p
INNER JOIN ZW-CAT-PRODUCT cp ON p.id = cp.id_product
INNER JOIN CATS c ON cp.id_cats = c.id
WHERE c.name = 'category1'
You could fit all 3 into 1 query yes, but consider having 3 huges tables, i'd rather process them one by one instead of getting the whole bulk back in 1 time.
This takes longer, but is (in my opinion) more data friendly.
I have a set of data that models a hierarchy of categories. A root category contains a set of top-level categories. Each top-level category contains a set of sub-categories.
Each sub category has a set of organizations. A given organization can appear in multiple sub categories.
The leaf nodes of this hierarchy are organizations. An organization can potentially appear in multiple sub-categories.
The data is stored in three SQL tables:
organizations
organization_id organization_name
1 Org A
2 Org B
3 Org C
4 Org D
5 Org E
6 Org F
categories
category_id parent_id category_name
0 NULL Top Level Category
1 0 First Category
2 0 Second Category
3 1 Sub Category A
4 1 Sub Category B
5 1 Sub Category C
6 2 Sub Category D
organizations_categories -- Maps organizations to sub_categories
organization_id category_id
1 3
2 3
2 6
3 4
4 4
5 4
6 5
6 4
7 6
8 6
I would like to be able to select a list of all unique organizations under a given category or sub-category.
The way I'm doing it right now involves first figuring out which sub categories have been requested and then looping through each sub_category in code and performing a select to get all organizations mapped to that category. The results of each select are appended to an array. This array contains duplicates whenever an organization appears in multiple sub categories.
I would love to replace this kludge with a query that can efficiently select a list of distinct organizations given an id of one of the categories in the hierarchy.
I am devloping this solution using PHP and MySQL.
Thanks for your time and suggestions.
Assuming that your hierarchy is always exactly 3 levels deep:
SELECT DISTINCT
O.organization_id,
O.organization_name
FROM
Categories CAT
INNER JOIN Categories SUB ON
SUB.parent_id = CAT.category_id
INNER JOIN Category_Organizations CO ON
CO.category_id = SUB.category_id
INNER JOIN Organizations O ON
O.organization_id = CO.organization_id
WHERE
CAT.category_id = #category_id
You can modify that by one level to allow you to pass a sub category id. If you don't know at the time whether or not you have a category id or a sub category id then you can do the following:
SELECT DISTINCT
O.organization_id,
O.organization_name
FROM
Categories CAT
LEFT OUTER JOIN Categories SUB ON
SUB.parent_id = CAT.category_id
INNER JOIN Category_Organizations CO ON
CO.category_id IN (CAT.category_id, SUB.category_id)
INNER JOIN Organizations O ON
O.organization_id = CO.organization_id
WHERE
CAT.category_id = #category_id
If your hierarchy may have an unknown number of levels (or you think it might in the future) then check out Joe Celko's Trees and Hierarchies in SQL for Smarties for alternative ways to model a hierarchy. It's probably a good idea to do that anyway.
Not sure if your data model will allow it, but you can use a single index column and a Binary Tree to easily store this information in a single 'OrganizationTree' table. Also has the benefit you use a single query with no modifications to search at the category, subcategory, or organization levels (E.g. give me all results of X subcategory)
Hope this helps.
Adam.