MySQL nested set hierarchy pretty url menu array - php

I would like to create a pretty url menu array from my nested set list of categories.
The tables:
categories (lft, rgt, id)
categories_description (cat_id, lang_id, name)
seo_url (cat_id, prod_id, man_id, url)
Table categories holds all categories, category names come from table categories_description and pretty urls come from table seo_url.
Is there a way to combine all three tables and fetch the whole menu array in 1 query?
For example:
parent (with name of parent)
parent/subcat (with name of subcat)
parent/subcat2 (with name of subcat2)
parent2 (with name of parent2)
parent2/subcat32 (with name of subcat32)
parent2/subcat42/subcat23 (with name of subcat23)
parent3 (with name of parent3)
parent4 (with name of parent4)
parent4/subcat4 (with name of subcat4)

if they are only nested to a set depth (eg 3 levels max as per your example), then you should be able to. you would end up with some columns being null, which you would have to cater for in code, rather then the sql query.
it is hard to give you a concrete example with the data you have provided
but it wuld be something like this (note this is untested)
select parent_description.name as parent_name,
parent_url.url as parent_url,
child_description.name as child_name,
child_seo_url.url as child_url,
grandchild_description.name as grandchild_name,
grandchild_seo_url.url as grandchild_url,
from categories as parent
join category_description as parent_description on parent.id=parent_description.cat_id
join seo_url as parent_seo_url on parent.id=parent_seo_url.cat_id
left outer join categories as child on parent.id=child.parent_id
left outer join category_description as child_description on child.id=child_description.cat_id
left outer join seo_url as child_seo_url on child.id=child_seo_url.cat_id
left outer join categories as grandchild on grandchild.id=child.parent_id
left outer join category_description as grandchild_description on grandchild.id=grandchild_description.cat_id
join seo_url as grandchild_seo_url on grandchild.id=grandchild_seo_url.cat_id
which should give an out put like
parent_name | parent_url | child_name | child_url | grandchild_name | grandchild_url
parent | url | NULL | NULL | NULL | NULL
parent | url | child | url | NULL | NULL
parent | url | child | url | grandchild | url
you should be able to render the html from that

You could simply fetch all categories at once and build the hierarchy in code.
select *
from categories c
join categories_description d on d.cat_id = c.id
join seo_url u on d.cat_id = u.id;
I'm not very fluent in PHP anymore, but using a hash to lookup parent categories should work pretty well. Finally, you'll have to sort the whole list according to your needs.
Performance wise, I wouldn't worry too much. Building and sorting the menu should still be faster than the data fetching itself (given that the code is well written) as we're talking about a few ms here. The whole structure would certainly be a good candidate for caching though - but don't do it before you have to.

Thanks for the solutions. I've decided to use this query because it exports the menu array in the same way as I wrote in my question.
SELECT node.cat_id, node.lft, node.rgt, GROUP_CONCAT(su.url ORDER BY parent.lft SEPARATOR "/" ) AS path, (COUNT(parent.lft) - 1) AS depth, cd.name AS name
FROM categories AS node
JOIN categories AS parent ON node.lft BETWEEN parent.lft AND parent.rgt
JOIN seo_url AS su ON parent.cat_id = su.cat_id
JOIN categories_description AS cd ON node.cat_id = cd.cat_id
WHERE parent.lft >= 1
GROUP BY node.cat_id
ORDER BY node.lft

Related

order group_concat values in one column

I have a database with 4 tables related to this question. I have only provided the required columns for each table to not get things complicated.
reviews
ID | reviewID
themes
ID | themeID | name
reviewthemes
ID | reviewID | themeID
reviewsubthemes
ID | reviewID | themeID
So a review can have multiple themes and multiple sub-themes. Each sub-theme should correspond to a theme, meaning a sub-theme cannot exist without its main theme.
The themes table is the place which holds the name for either the theme or sub-theme.
At the minute i have made a query which grabs all the data i require. Because it makes multiple rows i have done a group_concat for the themes and sub-themes. This means for each individual review i have a column called "themes". In this column it contains all the themes and sub-themes separated by a comma.
This is my query:
select `reviews`.`reviewID`,
GROUP_CONCAT(`themes`.`name`) as `Name`
from `reviews`
left join `reviewthemes` on `reviewthemes`.`reviewId` =`reviews`.`reviewId`
left join `reviewsubthemes` on `reviewsubthemes`.`reviewid` = `reviews`.`reviewid`
left join `themes` on (`themes`.`themeid` = `reviewthemes`.`themeid` or `themes`.`themeid` = `reviewsubthemes`.`themeid`)
where `reviews`.`healthwatchID` = 32
and (`review_created` BETWEEN '2015-08-01 00:00:00' AND '2015-08-31 23:59:59')
group by `reviews`.`reviewID`
This brings back data which looks like this:
Now here is the tricky part, and i'm not sure if i will need to do this in PHP or MYSQl but its safe to say i'm am really confused.
I require the format to be like :
theme - subtheme; theme - subtheme; theme;
So you can see i need a theme and a subtheme to have a semi colon at the end. But some reviews can have a theme with no sub-theme followed by a theme with a sub-theme.
What is the best way to go about this?
You can use CONCAT to join the name and sub name together, and then GROUP_CONCAT on the results of that:-
select reviews.reviewID,
GROUP_CONCAT(CONCAT_WS(' - ', t1.name, t2.name) SEPARATOR ';') as Name
from reviews
left join reviewthemes on reviewthemes.reviewId =reviews.reviewId
left join reviewsubthemes on reviewsubthemes.reviewid = reviews.reviewid
left join themes t1 on (t1.themeid = reviewthemes.themeid)
left join themes t2 on (t2.themeid = reviewsubthemes.themeid)
where reviews.healthwatchID = 32
and (review_created BETWEEN '2015-08-01 00:00:00' AND '2015-08-31 23:59:59')
group by reviews.reviewID

Showing information of portfolio items and displaying several categories with MySQL PHP

Thank you for reading this and trying to help!
The problem I have is the following: I'm trying to create a portfolio page that displays every portfolio item using a quicksand library to sort the results on screen by categories. The tricky part is that some items can belong to more than one category.
So far what I have is this:
MySQL Tables:
portfolio
id | date_added | date_project | project_title | description | url | skills_used
categories
id | category_name
portfolio_categories
project_id | category_id
With this 3 tables I have been trying unsuccessfully to display the information using an INNER JOIN but I can't come up with the right query structure for this.
I basically want to display (echo) all the information in the table portfolio PLUS all the categories that every portfolio item is included in.
PLEASE if anyone can help me figure out the right query for this I would really appreciate it!
Thank you very much beforehand.
This type of question probably duplicated many times over....
SELECT p.*, GROUP_CONCAT(DISTINCT c.category_name SEPARATOR ',')
FROM portfolio
LEFT JOIN portfolio_categories pc ON pc.project_id = p.id
LEFT JOIN categories c ON c.id = pc.category_id
GROUP BY p.id
Check this query:
"SELECT * FROM portfolio, portfolio_categories, categories
WHERE portfolio.id = portfolio_categories.project_id
AND portfolio_categories.category_id = categories.id";

Mysql query to get parent details in a single query

I have a table where i store categories with its parent's id. Following is the table structure
What i need is to get the parent and details of id 4 in a single query without php recursion
id | CategoryName | parentid
1 Web 0
2 Software 0
3 PHP 1
4 Arrays 3
A self join should be sufficient here. Your goal is to join the table against itself, while relating the parentid of the main row to the id of its parent's row.
SELECT
me.id AS me_id,
me.CategoryName AS me_category,
parent.id AS parent_id,
parent.CategoryName AS parent_category
FROM
tablename me JOIN tablename parent ON me.parentid = parent.id
WHERE me.id = 4
Would a variation of
select * from categories t1 join categories t2 where t1.parentid=t2.id
work for you

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 ... ?

How can I select all leaf nodes in a SQL hierarchy under a given node?

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.

Categories