I have a standard nested category tree:
| id | parent_id | name |
+----+-----------+----------------+
| 1 | 0 | Category 1 |
| 2 | 0 | Category 2 |
| 3 | 0 | Category 3 |
| 4 | 1 | Category 1.1 |
| 5 | 1 | Category 1.2 |
| 6 | 2 | Category 2.1 |
| 7 | 2 | Category 2.2 |
| 8 | 7 | Category 2.2.1 |
and now I need to get top parent of specified item so I do:
SELECT
cat.*
FROM
categories cat
LEFT JOIN
categories subCat
ON
subCat.parent_id = cat.id
AND cat.parent_id = 0
WHERE
subCat.id = 5;
and if item is first-level child, it's working ok but is item is second-level child (eg. 8) I'm not getting records - how to do this?
Here is SQlFiddle: http://sqlfiddle.com/#!9/5879bd/11
UPDATE
Here is real example: http://sqlfiddle.com/#!9/6f1d1c/1
I want to get parent category of Xiaomi
With MySQL 5.6 you cannot use recursive CTEs.
To do it properly, for an arbitrary tree depth, you need to write a function/procedure, that traverses the hierarchy and returns the top node once reached.
As a workaround, when the maximum number of level d is set, you can left join the parent (d - 1) times. Use coalesce() to get the first non null value along the path. So in your case, for d = 3:
SELECT c.*
FROM categories c
INNER JOIN (SELECT coalesce(c3.id, c2.id, c1.id) id
FROM categories c1
LEFT JOIN categories c2
ON c2.id = c1.parent_id
LEFT JOIN categories c3
ON c3.id = c2.parent_id
WHERE c1.id = 10) t
ON t.id = c.id;
(I first select the ID of the top node and inner join the rest, to avoid coalesce() on all columns. It might give a false result on nullable columns if the value for the column in the top node is null but not for any child node. It should display NULL then, but will falsely show the non value from the child node.)
But note: It will fail if the depth grows!
This answers the original version of the question.
To get the top level, you can use the name column:
SELECT c.*
FROM categories c JOIN
categories sc
ON sc.id = 10 AND
c.name = SUBSTRING_INDEX(sc.name, '.', 1);
Related
I'm displaying a tree of categories and, next to each branch I want a count of the number of listings which are the same level, or lower.
All listings have a catid which is at the lowest category level. i.e. you can't apply a particular catid to a listing if there are lower category levels available.
As an example, in the "listings" table below, you can't find a listing that has catid = 24 because this is not yet the lowest level in the tree along that branch.
In my categories database, there are a maximum 4 levels (0-3).
Here are the tables:
all_categories (table)
record_id parent_category_id parent_id title level
---------------------------------------------------------------------
24 NULL NULL Real Estate 0
5915 24 24 Residential 1
7569 5915 24 For sale 2
listings (table)
record_id cat_id
--------------------
1 7569
2 8847
So, my category tree in HTML should looks something like this:
HTML
Categories Listing count
-------------------------------------
24 1
5915 1
7569 1
So, in my html and jQuery code, I pass a particular catid of any level to the query and it should find listings that are at or below that level.
I've been trying for hours and my efforts so far aren't worth showing. But I will anyway...
EDIT: My effort so far (don't laugh, it's muddled after hours of trying different things):
select l.record_id
from listings l
where catid in (
select record_id
from all_categories
where record_id = 5915)
or catid in (
select parent_category_id
from all_categories
where parent_category_id = 5915)
or catid in (
select parent_id
from all_categories
where parent_id = 5915)
The level 0 parent_id for a child is kept in the "all_categories" table.
So a child can be linked to the other childs by their common parent_id.
And then include the level 0 at the same time.
The difficulty is when the given child id isn't in the "listings" table.
So to retrieve that listings record_id, it has to go via the categories.
A test on rextester can be found here
SELECT
cat.record_id as catId,
pl.listId AS ListingCount,
cat.level
FROM
(
SELECT
cat1.parent_id,
MAX(list.record_id) AS listId
FROM all_categories AS cat1
JOIN all_categories AS cat2 ON cat2.parent_id = cat1.parent_id
JOIN listings list ON list.cat_id = cat2.record_id
WHERE cat1.record_id = 5915
GROUP BY cat1.parent_id
) AS pl
LEFT JOIN all_categories AS cat ON (cat.parent_id = pl.parent_id OR cat.record_id = pl.parent_id)
ORDER BY cat.record_id, cat.level;
Result:
catId ListingCount level
24 1 0
5915 1 1
7569 1 2
The "level" is also included in the query, because that can be used to generate that category tree in HTML.
Note that the query could be simplified a lot if that "listings" table would only contain the cat_id for the level 0
Assuming that the tree is maximum four level deep you can use multiple LEFT JOINs to retrieve the full tree or a sub-tree. This won't be super efficient though. Consider the following query:
SET #subtree_id = 1;
SELECT
c0.category_id AS c0_id, c0.name AS c0_name,
c1.category_id AS c1_id, c1.name AS c1_name,
c2.category_id AS c2_id, c2.name AS c2_name,
c3.category_id AS c3_id, c3.name AS c3_name,
l.listing_id
FROM category AS c0
LEFT JOIN category AS c1 ON c1.parent_id = c0.category_id
LEFT JOIN category AS c2 ON c2.parent_id = c1.category_id
LEFT JOIN category AS c3 ON c3.parent_id = c2.category_id
LEFT JOIN listing AS l ON l.category_id = c0.category_id
OR l.category_id = c1.category_id
OR l.category_id = c2.category_id
OR l.category_id = c3.category_id
WHERE c0.category_id = #subtree_id;
It will produce results such as this:
| c0_id | c0_name | c1_id | c1_name | c2_id | c2_name | c3_id | c3_name | listing_id |
|-------|-------------|-------|-------------|-------|-----------|-------|------------|------------|
| 1 | Real Estate | 2 | Residential | 3 | House | NULL | NULL | NULL |
| 1 | Real Estate | 2 | Residential | 4 | Apartment | NULL | NULL | 1 |
| 1 | Real Estate | 2 | Residential | 4 | Apartment | NULL | NULL | 2 |
| 1 | Real Estate | 2 | Residential | 4 | Apartment | NULL | NULL | 3 |
| 1 | Real Estate | 2 | Residential | 5 | Condo | NULL | NULL | NULL |
| 1 | Real Estate | 6 | Commercial | 7 | Office | NULL | NULL | 4 |
| 1 | Real Estate | 6 | Commercial | 8 | Retail | NULL | NULL | 5 |
| 1 | Real Estate | 6 | Commercial | 9 | Other | 10 | Industrial | 6 |
Unfortunately it contains full paths only. To match the expected result just break each row into 4 rows:
SET #subtree_id = 1;
SELECT
CASE WHEN level >= 0 THEN c0_id END AS c0_id, CASE WHEN level >= 0 THEN c0_name END AS c0_name,
CASE WHEN level >= 1 THEN c1_id END AS c1_id, CASE WHEN level >= 1 THEN c1_name END AS c1_name,
CASE WHEN level >= 2 THEN c2_id END AS c2_id, CASE WHEN level >= 2 THEN c2_name END AS c2_name,
CASE WHEN level >= 3 THEN c3_id END AS c3_id, CASE WHEN level >= 3 THEN c3_name END AS c3_name,
COUNT(listing_id) AS lc
FROM (
SELECT
c0.category_id AS c0_id, c0.name AS c0_name,
c1.category_id AS c1_id, c1.name AS c1_name,
c2.category_id AS c2_id, c2.name AS c2_name,
c3.category_id AS c3_id, c3.name AS c3_name,
l.listing_id
FROM category AS c0
LEFT JOIN category AS c1 ON c1.parent_id = c0.category_id
LEFT JOIN category AS c2 ON c2.parent_id = c1.category_id
LEFT JOIN category AS c3 ON c3.parent_id = c2.category_id
LEFT JOIN listing AS l ON l.category_id = c0.category_id
OR l.category_id = c1.category_id
OR l.category_id = c2.category_id
OR l.category_id = c3.category_id
WHERE c0.category_id = #subtree_id
) AS paths
INNER JOIN (
SELECT 0 AS level UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3
) AS levels ON level = 0 AND c0_id IS NOT NULL
OR level = 1 AND c1_id IS NOT NULL
OR level = 2 AND c2_id IS NOT NULL
OR level = 3 AND c3_id IS NOT NULL
GROUP BY 1, 2, 3, 4, 5, 6, 7, 8
ORDER BY 2, 1, 4, 3, 6, 5, 8, 7
Demo on DB<>Fiddle
You have an arcane data structure, but the problem doesn't seem that hard. This seems to do what you want:
select c2.record_id, count(*)
from listings l join
all_categories c
on l.cat_id = c.record_id join
all_categories c2
on c2.record_id in (c.record_id, c.parent_category_id, c.parent_id)
group by c2.record_id, l.cat_id
order by l.cat_id, c2.record_id;
The idea is pretty simple. The table all_categories has the full hierarchy. Basically, you need to move all the levels of the hierarchy into separate rows so they can be aggregated.
That is what the join to c2 does. The rest is just aggregation.
well in mysql 8 there is a (with) function which run recursively you can get the level of the catId
structure -> (id cat parent_id)
->(1,24,NULL),(2,5915,1),(3,7569,2),(4,3,1)
WITH recursive parent(id,parent_id,Levels) AS
(
select id,parent_id, 0 as Levels from table where id =1 //condition
union all
select c.id,c.parent_id,(Levels+1) as Levels from table as c inner join parent as p1 on p1.id = c.parent_id
)
SELECT *
FROM parent
the only limitation of this is that it is in my sql version 8.x rest the code will run fine any problem you can ask this method is called cet hope it help
For each record_id, left-join the children, then count the unique values at each level and add them together.
select n.record_id
count(distinct n.record_id)+count(distinct n2.record_id)+count(distinct
n3.record_id)+count(distinct n4.record_id) listingcount
from all_categories n
left join all_categories n2 on n.record_id=n2.parent_category_id
left join all_categories n3 on n2.record_id=n3.parent_category_id
left join all_categories n4 on n3.record_id=n4.parent_category_id
group by n.record_id
I believe this is suited for a recursive sql.
CREATE FUNCTION f_total_listings(#root_id INT) RETURNS INT AS
BEGIN
DECLARE #listing_count INT;
WITH cat (record_id, parent_category_id) AS
(
SELECT root.record_id, root.parent_category_id
FROM all_categories AS root
WHERE root.parent_category_id = #root_id
UNION ALL
SELECT child.record_id, child.parent_category_id
FROM cat AS parent, all_categories AS child
WHERE parent.record_id = child.parent_category_id
)
SELECT #listing_count = count(*)
FROM listings l
JOIN cat ON l.cat_id = cat.record_id;
RETURN #listing_count;
END;
SELECT record_id, f_total_listings(record_id) FROM all_categories
I have two tables, products and categories, and a join table products_categories.
Categories are nested (via categories.parent_id). So, for any given product, it can belong to many, potentially nested categories.
The categories are structured like this:
department (depth = 0)
category (depth = 1)
class (depth = 2)
Each product will belong to one "department", one "category", and one "class".
So, a product like "Rad Widget", could belong to the "Electronics" department, the "Miscellaneous" category, and the "Widgets" class. My schema would look like this:
# products
id | name
---------------
1 | Rad Widget
# categories
id | parent_id | depth | name
--------------------------------------
1 | null | 0 | Electronics
2 | 1 | 1 | Miscellaneous
3 | 2 | 2 | Widgets
# products_categories
product_id | category_id
------------------------
1 | 1
1 | 2
1 | 3
I'd like to run a query that lists all of a product's departments into a single row, like this:
product.id | product.name | department | category | class
-----------------------------------------------------------------
1 | Rad Widget | Electronics | Miscellaneous | Widgets
I can't think of a way to do this, so I'm considering denormalizing my data, but I want to make certain I'm not missing something first.
Since each category (and by the way, you might want to rename either the table or the level so that "category" doesn't mean two different things) has a singular known parent, but an indeterminate number of unknown children, you need to "walk up" from the most specific (at depth = 2) to the most general category, performing a self-join on the category table for each additional value you want to insert.
If you're impatient, skip to the SQL Fiddle link at the bottom of the post. If you'd rather be walked through it, continue reading - it's really not that different from any other case where you have a surrogate ID that you want to replace with data from the corresponding table.
You could start by looking at all the information:
SELECT * FROM products AS P
JOIN
products_categories AS PC ON P.id = PC.product_id
JOIN
categories AS C ON PC.category_id = C.id
WHERE P.id = 1 AN D C.depth = 2;
+----+------------+------------+-------------+----+-----------+-------+---------+
| id | name | product_id | category_id | id | parent_id | depth | name |
+----+------------+------------+-------------+----+-----------+-------+---------+
| 1 | Rad Widget | 1 | 3 | 3 | 2 | 2 | Widgets |
+----+------------+------------+-------------+----+-----------+-------+---------+
First thing you have to do is recognize which information is useful and which is not. You don't want to be SELECT *-ing all day here. You have the first two columns you want, and the last column (recognize this as your "class"); you need parent_id to find the next column you want, and let's hold onto depth just for illustration. Forget the rest, they're clutter.
So replace that * with specific column names, alias "class", and go after the data represented by parent_id. This information is stored in the category table - you might be thinking, but I already joined that table! Don't care; do it again, only give it a new alias. Remember that your ON condition is a bit different - the products_categories has done its job already, now you want the row that matches C.parent_id - and that you only need certain columns to find the next parent:
SELECT
P.id,
P.name,
C1.parent_id,
C1.depth,
C1.name,
C.name AS 'class'
FROM
products AS P
JOIN
products_categories AS PC ON P.id = PC.product_id
JOIN
categories AS C ON PC.category_id = C.id
JOIN
categories AS C1 ON C.parent_id = C1.id
WHERE
P.id = 1
AND C.depth = 2;
+----+------------+-----------+---------------+---------+
| id | name | parent_id | name | class |
+----+------------+-----------+---------------+---------+
| 1 | Rad Widget | 1 | Miscellaneous | Widgets |
+----+------------+-----------+---------------+---------+
Repeat the process one more time, aliasing the column you just added and using the new C1.parent_id in your next join condition:
SELECT
P.id,
P.name,
PC.category_id,
C2.parent_id,
C2.depth,
C2.name,
C1.name AS 'category',
C.name AS 'class'
FROM
products AS P
JOIN
products_categories AS PC ON P.id = PC.product_id
JOIN
categories AS C ON PC.category_id = C.id
JOIN
categories AS C1 ON C.parent_id = C1.id
JOIN
categories AS C2 ON C1.parent_id = C2.id
WHERE
P.id = 1
AND C.depth = 2;
+----+------------+-----------+-------+-------------+---------------+---------+
| id | name | parent_id | depth | name | category | class |
+----+------------+-----------+-------+-------------+---------------+---------+
| 1 | Rad Widget | NULL | 0 | Electronics | Miscellaneous | Widgets |
+----+------------+-----------+-------+-------------+---------------+---------+
Now we're clearly done; we can't join another copy on C2.parent_id = NULL and we also see that depth = 0, so all that's left to do is get rid of the columns we don't want to display and double check our aliases. Here it is in action on SQL Fiddle.
If you want a list of all the categories, you can simply do a
Select Distinct p.category_id, c.name
From products_categories p Join
categories c On p.category_id = c.id
Where p.product_id = 1
The problem is you are putting Classes and Departments into your Category table. Technically you'd be correctly normalizing your data by moving each of these to their own tables. I know the overhead of creating more tables is a pain but it'll simplify your queries (saving processing power and potentially bandwidth).
i have the following situation.
Table "parent" has parent objects:
+-----+-------------------+
| UID | some_parent_stuff |
+-----+-------------------+
| 1 | value01 |
+-----+-------------------+
| 2 | value02 |
| | |
The table child has objects with a reference column to parents and a status:
+-----+-------------+------------+
| UID | parent_uid | status_uid |
+-----+-------------+------------+
| 1 | 2 | 1 |
+-----+-------------+------------+
| 2 | 2 | 5 |
+-----+-------------+------------+
| 3 | 1 | 2 |
| | | |
Now i'd like to select all parents, from which the child with the highest UID has a status_uid in a list (i.e. IN(1,5)).
I have something like this:
SELECT P.uid FROM parent P
INNER JOIN child C ON C.parent_uid = P.uid AND C.status_uid IN(1)
GROUP BY P.uid;
This returns parent with UID 2 too. But it has a child with a higher UID, which has a status not in the IN-Clause. So i dont want to select it.
I hope you understand my question, my english isn't the best... Thx for ideas and with best regards
Kjuuze
What I think you need is a query within your query (PreQuery in my sample). The inner query gets on a per parent id basis, the maximum child-level uid regardless of the qualifying status ID. Since this is grouped by parent, it will return two records per your example data.
Child Table
UID Parent Status
2 2 5
3 1 2
Now, you can join to both the parent table and the child table from this prequery on the respective IDs, and at the second CHILD-LEVEL join, add your criteria for the status in (1,5)
SELECT
p.uid,
p.some_parent_stuff,
c2.uid,
c2.status_uid
from
( select c.parent_uid,
MAX( c.uid ) as ChildUID
from
child c
group by
c.parent_uid ) PreQuery
JOIN Parent p
ON PreQuery.parent_uid = p.uid
JOIN Child c2
ON PreQuery.ChildUID = c2.uid
AND c2.status_id in ( 1, 5 )
This will return only the 1 record for Child ID 2, Parent 2, Status 5. Since the Child ID of 3 has a status of 2, it will NOT be returned in final result set.
SELECT P.uid
FROM parent P
INNER JOIN child C ON C.parent_uid = P.uid AND C.status_uid IN(1)
WHERE C.status_uid = (
SELECT MAX(status_uid)
FROM child)
GROUP BY P.uid;
If I understood what you are asking this should work, however this is not tested, you can also review mysql sub queriesmysql here
I'm sure that this has been asked before, but I don't know what to call it exactly to find the answer.
I have a table of categories and sub categories. They each have an id and a parent id. If it is a top level category, the parent id is 0. Sub categories have the parent id set to the
category id of it's parent.
category_id # The ID for this record
category_name # The name of the category
parent_id # The parent ID for this category
display_order # Order of categories within their grouping
1 A 0 0 # First primary category
2 a1 1 0 # Subcategory, parent is A, display_order is 0
3 a2 1 1
4 a3 1 2
5 B 0 1 # Second primary category
6 b1 5 0 # Subcategory, parent is B, display_order is 0
7 b2 5 1
8 b3 5 2
I'm trying to write an SQL query that will give me all of the categories in this order:
A, a1, a2, a3, B, b1, b2, b3
SELECT * FROM categories ORDER BY display_order
Is this possible in SQL, or will I need to use multiple queries
Thanks,
Brad
Something like this might maybe work:
SELECT *
FROM categories
ORDER BY IF(parent_id, parent_id, category_id), parent_id, display_order
but since it can't use an index, it'll be slow. (Didn't test though, might be wrong)
The first ORDER BY condition sorts parents and children together; then the second one ensures the parent precedes its children; the third sorts the children among themselves.
Also, it will obviously work only in the case you directly described, where you have a two-level hierarchy.
an answer has already been accepted, but i thought i would share my thoughts on this anyways. i tried to sort the main categories after their display_order column as well. here's my table
mysql> select * from categories;
+-------------+---------------+-----------+---------------+
| category_id | category_name | parent_id | display_order |
+-------------+---------------+-----------+---------------+
| 1 | B | 0 | 2 |
| 2 | C | 0 | 3 |
| 3 | b2 | 1 | 2 |
| 4 | b1 | 1 | 1 |
| 5 | c3 | 2 | 3 |
| 6 | A | 0 | 1 |
| 7 | c2 | 2 | 2 |
| 8 | b3 | 1 | 3 |
| 9 | a2 | 6 | 2 |
| 10 | a1 | 6 | 1 |
| 11 | c1 | 2 | 1 |
| 12 | a3 | 6 | 3 |
+-------------+---------------+-----------+---------------+
12 rows in set (0.00 sec)
as you see, i have taken great care to add the categories in a none linear order :)
my query:
SELECT
sub_id AS category_id,
sub_name AS category_name,
sub_parent_id AS parent_id,
main_order + sub_order AS display_order
FROM (
SELECT
c1.display_order + c1.display_order * (
SELECT
inner_c.display_order
FROM
categories AS inner_c
WHERE
inner_c.parent_id <> 0
ORDER BY
inner_c.display_order DESC
LIMIT 1) AS main_order,
c2.display_order AS sub_order,
c2.category_name AS sub_name,
c2.category_id AS sub_id,
c2.parent_id AS sub_parent_id
FROM
categories AS c1
JOIN
categories AS c2
ON
c1.category_id = c2.parent_id
WHERE
c1.parent_id = 0
) AS renumbered
UNION ALL
SELECT
category_id,
category_name,
parent_id,
display_order + display_order * (
SELECT
inner_c.display_order
FROM
categories AS inner_c
WHERE
inner_c.parent_id <> 0
ORDER BY
inner_c.display_order DESC
LIMIT 1) AS display_order
FROM
categories
WHERE
parent_id = 0
ORDER BY
display_order;
Sounds almost identical to another I've answered with similar parent/child hierarchy while retaining child elements at same grouped level as its corresponding parent...Check this thread
Whenever possible, I build SQL incrementally, not least because it gives me the option of testing as I go.
The first thing we need to be able to do is identify the top-level categories:
SELECT category_id AS tl_cat_id,
category_name AS tl_cat_name,
display_order AS tl_disp_order
FROM Categories
WHERE parent_id = 0;
Now we need to join that with the categories and subcategories to get the result:
SELECT t.tl_cat_id, t.cat_name, t.tl_disp_order, c.category_id, c.category_name,
CASE WHEN c.parent_id = 0 THEN 0 ELSE c.display_order END AS disp_order
FROM Categories AS c
JOIN (SELECT category_id AS tl_cat_id,
category_name AS tl_cat_name,
display_order AS tl_disp_order
FROM Categories
WHERE parent_id = 0) AS t
ON c.tl_cat_id = t.parent_id OR (c.parent_id = 0 AND t.tl_cat_id = c.category_id)
ORDER BY tl_disp_order, disp_order;
The join condition is unusual but should work; it collects rows where the parent ID is the same as the current category ID, or rows where the parent ID is 0 but the category ID is the same. The ordering is then almost trivial - except that when you are dealing with the sub-category ordering, you want the parent item at the front of the list. The CASE expression handles that mapping.
What I like to achieve is
a: display all Items that are in all of the selected category's
b: return / update the category list with category's available based on selection
I like items to be stored and be found by use of the adjacency list model or nested sets.
I've experimented with both and may use advice what would be the best for this case.
Currently I'm using (testing with) the adjacency list model like this:
items:
ID | item_name
====================
1 | car
2 | boat
3 | bike
items_cats: (many to many)
iid | cid
====================
1 | 1
1 | 2
1 | 4
1 | 7
2 | 1
2 | 3
2 | 4
2 | 7
3 | 1
3 | 3
3 | 4
3 | 8
categorys:
ID | cat_name | parent_id
========================
1 | safety: | 0 (0 = no parent)
2 | safe | 1
3 | dangerous | 1
4 | fun: | 0
5 | a bit | 5
6 | boring | 5
7 | funny | 5
8 | cool | 5
So its no problem to get items based on cid but how would you:
1st: selection:
1- Display all items who have cat id: cid 7 (funny)?
2- return (array/object) of all category's who have items that also contain cid 7?
Would you all do this in one query or would two be more efficient?
2nd: selection:
3- Display all items who have cat id: cid 7 and also contain cat id '3' (dangerous)
4- return (array/object) of all category's who have items that contain cid 7 and cid 3?
For selecting on multiple category's I found the flowing solution. Is this a good one and would there be to gain any performance especially when the number of category's grow?
SELECT
DISTINCT t1.product_id, t1.category_id
FROM
items_cats t1
INNER JOIN
items_cats t1b
ON t1.iid =t1b.iid
WHERE
t1.cid=3 AND
t1b.cid=7
To get a list of all items that have category ID = 7, start with your many:many table
select
i.item_name
from
items_cat ic
join items i
on ic.iid = i.id
where
ic.cid = 7
to get all categories associated with any item that has the category ID of 7, you can expand from the first and get categories associate for those item IDs
select DISTINCT
ic2.cid,
c.cat_name,
coalesce( CatParent.cat_name, "" ) as ParentCategoryName
from
( select distinct ic.iid
from items_cat ic
where ic.cid = 7 ) QualifiedItems
JOIN items_cat ic2
on QualifiedItems.iid = ic2.iid
JOIN categorys c
on ic2.cid = c.id
LEFT JOIN categorys CatParent
on c.parent_id = CatParent.ID
For 3 and 4, it would be similar, but to qualify BOTH (or anytime, more than one), you need to apply an OR, a GROUP BY and make sure that the final count matches those you were trying to qualify
select
i.item_name
from
items_cat ic
join items i
on ic.iid = i.id
where
ic.cid in( 3, 7 )
group by
i.item_name
having
count(*) = 2
So you can better understand and apply these principles, I'll leave the last one for you to try and implement... If you really get stuck, let me know... :)