I have mysql tables defined as:
categories: category_id, category_name, parent_category_id
I'm looking for a nice sql query that would retrieve all the DESCENDANTS of a given category_id. That means, its children, and its children's children.
If that helps, we can assume a maximum number of levels (3). This query could be sent at any level (root, level 2, level 3).
Thanks!
Nathan
There are a few ways to store trees in a database. There's a fantastic article on sitepoint describing all the techniques:
http://articles.sitepoint.com/article/hierarchical-data-database/2
The one that is most appropriate if you want to be able to get an entire section of a tree in one query is Modified Preorder Tree Traversal.
This technique is also known as Nested Sets. There's more information here if you want more literature on the subject:
http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
It can be done in a single query and a piece of recursive backend code logic: Formatting a multi-level menu using only one query.
If you also do PHP, this article comes with a PHP example as bonus, but translating to another language isn't that hard. I can't give any hints about that as you didn't mention the server side language you're using.
Hope this helps.
If you want to use this structure with max 3 levels you can join the table to itself three times:
SELECT
c1.id AS level_1,
c2.id AS level_2,
c3.id AS level_3
FROM categories c1
LEFT JOIN categories c2 ON c1.id = c2.parent_id
LEFT JOIN categories c3 ON c2.id = c3.parent_id
WHERE c1.parent_id IS NULL
I assume, that parent categories have NULL in parent_id.
Some example:
DECLARE #categories TABLE
(
id INT,
parent_id INT
)
INSERT INTO #categories(id,parent_id) VALUES(1,NULL)
INSERT INTO #categories(id,parent_id) VALUES(4,1)
INSERT INTO #categories(id,parent_id) VALUES(5,1)
INSERT INTO #categories(id,parent_id) VALUES(6,5)
INSERT INTO #categories(id,parent_id) VALUES(2,NULL)
SELECT * FROM #categories
SELECT c1.id AS level_1, c2.id AS level_2,
c3.id AS level_3
FROM #categories c1
LEFT JOIN #categories c2 ON c1.id = c2.parent_id
LEFT JOIN #categories c3 ON c2.id = c3.parent_id
WHERE c1.parent_id IS NULL
Returns:
level_1 | level_2 | level_3
---------------------------
1 | 4 | NULL
1 | 5 | 6
2 | NULL | NULL
MySQL does not support recursive queries, but you can find all descendants using a stored procedure with a WHILE loop. See the The edge list section of this book sample.
Related
I have a table for categories. This has a recursive relationship so that a category can become a subcategory of another category. The table looks like this:
id name short_desc long_desc tag_id parent_id
I wrote simple to get sql to find all level 1 categories:
SELECT * FROM category WHERE parent_id =0
Then I wrote a query to get all of the level 2 categories (where parent category doesn't have a parent)
SELECT * FROM category WHERE parent_id IN (SELECT id FROM category WHERE parent_id =0)
What I would like to do, is produce a column where is shows all category data and any relevant parent category.
Logically like this:
select all from category
if parent_id != 0, add the parent as a new row
repeat 2 until all parents have been accounted for.
The result should look something like this:
id name short_desc long_desc tag_id parent_name parent_name_2
if the parent_name is null / empty, then parent_name should remain empty. if there is a parent_name id in the field, then check to see if there is a parent_name_2 and if so, populate both columns, if not then only populate parent_name.
I do have the option of coding this in jquery or php which I have a good idea how to do. However, I am sure that I can get the data I need from a good SQL query.
Any help would be greatly appreciated.
Kind Regards
Nick
Here's one option using multiple outer joins:
select c.*,
case when c2.id is not null then c2.name end parent_name,
case when c3.id is not null then c3.name end parent_name_2
from category c
left join category c2 on c.parent_id = c2.id
left join category c3 on c2.parent_id = c3.id
SQL Fiddle Demo
Suppose I have the following table structure:
TABLE 1
main_id | type | information
first segway excellent
second car mercedes
third bike sliceofwind
TABLE segway
id | grade
1 excellent
2 bad
3 (...)
TABLE car
id | brand
1 mercedes
2 honda
3 (...)
TABLE bike
id | tires
1 sliceofwind
2 flatasfaque
3 (...)
What I'd like to do is dinamically obtain information from different tables based on type from table1.
Here's the generic example of a query that I've tried
SELECT (CASE
WHEN table1.type = 'segway' AND segway.grade = table1.information
THEN segway.id,
WHEN table1.type = 'car' AND car.brand = table1.information
THEN car.id,
WHEN table1.type = 'bike' AND bike.tires = table1.information
THEN bike.id
END) AS information
FROM table1,segway,bike,car WHERE table1.main_id IN ("ids")
The result of this query is a cartesian product because all the data from all tables will be retrieved despite the restrictions inside the case because not all tables have restrictions.
I'd like to know if there is a way to work around this without changing the table structure, and if not plea for some hints! (I'm up to some kinky sql stuff, what I'm asking here if it is indeed possible to do this, despite it being advised or not and why!).
This might be one way to do it.
SELECT t1.*
FROM table1 t1
LEFT JOIN segway s
on T1.main_id = s.id and T1.type= 'segway'
LEFT JOIN car c
on T1.main_id = c.id and T1.type= 'car'
LEFT JOIN bike b
on T1.main_id = b.id and T1.type= 'bike'
WHERE t1.main_ID in (SomeList)
segway, car, and bike table columns will be null when the Table1's type doesn't match.
However this seems like it would give you back more data/columns than you need. I think you'd be better off writing separate queries outside the database and call them depending on the value they select. OR using a procedure within the database and conditional logic to return the desired result set. (again 3 separate queries and conditional logic in the database) but without understanding use case, I can't really say which would be better.
We could further coalese the brand, tires and grade into a "Value" field as in
Select t1.*, coalese(s.grade,c.brand,b.tires) as value but I'm not sure this offers any help either.
if we needed to only return table 1 values and set values from the other tables... you said kinky, not me.
I can't see how the Cartesian would occur this way.
This will be your expected result, try this query..
SELECT (CASE WHEN s.`id` IS NOT NULL THEN s.`id`
WHEN c.`id` IS NOT NULL THEN c.`id`
WHEN b.`id` IS NOT NULL THEN b.`id`
END) AS information FROM table1 AS t1
LEFT JOIN segway s ON t1.type= 'segway'
LEFT JOIN car c ON t1.type= 'car'
LEFT JOIN bike b ON t1.type= 'bike'
WHERE t1.main_ID IN (1, 2, 3) AND (t1.information = s.`grade` OR
t1.`information`=c.brand OR
t1.`information`=b.tires);
I have one supertype table where I have to pick 1 subtype table from 2 subtypes a,b. A subtype cannot go with the other one so for me to query I have to check whether if the supertype id is contained on one of the subtypes. I have been doing experiment queries but cannot get it right.
This is what somehow I thought of:
SELECT * from supertypetable INNER JOIN
IF (a.id = given.id) then a ON a.id = supertypetable.id
ELSE b ON b.id = supertetable.id
job Table
________________________________
|job_id| blach2x....
________________________________
| 1 |
| 2 |
| 3 |
________________________________
partime Table
________________________________
|job_id| blach2x....
________________________________
| 2 |
| 3 |
________________________________
fulltime Table
________________________________
|job_id| blach2x....
________________________________
| 1 |
| |
________________________________
I want to join tables that satisfy my given id
This looks a lot like a polymorphic join in rails/activerecord. The way it's implemented there, the 'supertype' table has two fields: subtype_id and subtype_type. The subtype_type table has a string that can be easily turned into the name of the right subtype table; subtype_id has the id of the row in that table. Structuring your tables like this might help.
The next question you have to ask is what exactly are you expecting to see in the results? If you want to see the supertype table plus ALL of the subtype tables, you're probably going to have to join them one at a time, then union them all together. In other words, first join against just one of the subtype tables, then against the next one, etc. If this isn't what you're going for, maybe you could clarify your question further.
If a.id can never equal b.id you could do joing on both tables and then do a UNION and only the table where the id matched would return results:
SELECT * from supertypetable
INNER JOIN
a ON a.id = supertypetable.id
UNION
SELECT * from supertypetable
INNER JOIN
b ON b.id = supertypetable.id
If a.id can equal b.id, then this would not work. But it's an idea
EDITTING PER COMMENTS:
This approach only works if the structures of a and b are identical.
So one simple suggestion might be just:
SELECT * FROM job
left join parttime on parttime.job_id = job.job_id
left join fulltime on fulltime.job_id = job.job_id
where job.job_id = #job_id
And then let your application figure out which of the two joined tables doesn't have NULL data and display that.
If you don't mind inconsistent datasets and just always want the correct returned set regardless (although you're still going to need some kind of application logic since as you said, the structures of parttime and fulltime are different, so how are you going to display/utilize their data conditionally without some kind of inspection? And if you're going to do that inspection, you might as well do it up front, figure out for your given job_id what the subtype is, and then just pick the appropriate query to run there.)
Sorry! Digression!
A stored procedure can do this logic for you (removed all the joins, just an example):
CREATE PROCEDURE getSubTypeDATA (#job_id int)
BEGIN
IF (SELECT EXISTS(SELECT 1 FROM parttime WHERE job_id = #job_id)) = 1
BEGIN
SELECT * from parttime
END
ELSE
BEGIN
SELECT * from fulltime
END
END
Alternatively, if you want a consistent dataset (ideal for application logic), why not put the common columns between fulltime and parttime into a UNION statement, and create hard-coded NULLs for the columns they don't share. For example, if fullTime looked like
EmployeeID, DepartmentID, Salary
and partTime looked like
EmployeeID, DepartmentID, HourlyRate
you could do
SELECT job.*, employeeid, departmentid, salary, null as hourlyrate FROM job inner join fulltime on fulltime.job_id = job.job_id
where job.job_id = ?
union
SELECT job.*, employeeid, departmentid, null as salary, hourlyrate FROM job inner join parttime on parttime.job_id = job.job_id
where job.job_id = ?
If there are hundred different columns, this might be unwieldy. Also, in case this didn't make it obvious, having subtypes with completely different structures but using the same foreign key is a very good clue that you're breaking third normal form.
I have a mysql table named "category". the basic structure looks like this-
------ -------- ------
cat_id cat_name parent
------ -------- ------
1 test1 NULL
2 test2 NULL
3 test3 2
4 test4 1
5 test5 3
now i want all the category data with parent category name (not only id) in a single query. is that possible? i could do it by using a second query (getting child's parent name) in a while loop and merging data as a whole. but is it possible to do this with a single query?
Join the table with itself, using the parent column to link to the cat_id of the parent.
SELECT c1.cat_id as childID, c1.cat_name ChildName, c2.cat_name as ParentName
from category c1
LEFT OUTER JOIN category c2
ON c1.parent = c2.cat_id
Be careful: since some elements have no parents (NULL), I put a LEFT
OUTER JOIN so those rows are displayed as well. If you don't want
that, use a JOIN instead of LEFT OUTER JOIN.
You can also show the lines, but display something else (empty or a
text or ...) instead of the NULL by using COALESCE.
You can consider the result as one (big) new table, so you can add WHERE clauses as you usually do, for example filtering on the parent name: WHERE c2.cat_name = 'test2'
Select p.cat_id, p.cat_name, c.cat_id, c.cat_name, c.parent
From category Left Join category On p.cat_id = c.parent
Where p.cat_name = 'name'
SELECT c1.category_name AS category, c2. category_name AS sub_category
FROM (
SELECT *
FROM category
) AS c1
INNER JOIN (
SELECT *
FROM category
) AS c2 ON c1.category_id = c2.category_id
I got an existing products database which I'm writing an administration tool for (in PHP).
The database contains the following "categories" table:
Table Categories
--------------------
PK | id
FK | parent_id
| title
Now the foreign key "parent_id" contains an id taken from the same table, or "0" if it's a topmost category.
For creating an overview I now need a mysql statement which results in the following data:
id | parent_id | title | parent_title
The parent_title is where I've no idea. I created the following statement:
SELECT
c1.id,
c1.parent_id,
c1.title,
c2.title as `parent_title`
FROM
categories c1,
categories c2
WHERE
c1.parent_id = c2.id
I now only get all categories which have got a parent category.
Should be simple, and might have already been answered here. I think I only didn't find the right words to search for to find it by searching existing articles.
Thanks for your help,
Daniel
You can use a LEFT OUTER JOIN for this:
SELECT c1.id,
c1.parent_id,
c1.title,
c2.title as `parent_title`
FROM categories c1
left outer join categories c2 on c1.parent_id = c2.id
you're looking for an OUTER JOIN :)
See here: http://www.quackit.com/sql/tutorial/sql_outer_join.cfm