SQL - Count on recursive category - sql

I'm stucked into a count query.
I have 3 tables:
Articoli
ID | Title | ecc...
Categories
ID | Name | Parent
articles_category
category_id | article_id
Category are recursive, for example I have a main category "News" with 3 sub cat.
I need to count how many article are in "News", but my article are tagged in the "articles_category" table with the subcat ID (if there is a SubCat) or with main Cat ID, if it have no subcat. So far i tried:
SELECT count(a.id), child.name AS child, parent.name AS parent
FROM categories parent
JOIN categories child ON child.parent = parent.tid
JOIN categories_articoli ca ON child.tid = ca.category_id
OR parent.tid = ca.category_id
JOIN articoli a ON a.id = ca.articolo_id
GROUP BY parent.tid
But this return me only the parent cat that have subcategory, but this is everytime true. Any suggestion?

You need to use recursive sql on Categories table.
Try this:
Count(*) of articles in News category:
with category_tree(id) as
(select c.id
from Categories c
where c.name='News'--category tree starting with 'News'
union all
select c.id
from category_tree ct
inner join Categories c
on c.parent = ct.id)
select count(distinct ac.article_id) from category_tree ct
inner join articles_category ac on ac.category_id = ct.id
Count(*) of articles by category:
with category_tree(id, root_category_name) as
(select c.id, c.name
from Categories c
where c.parent is null
union all
select c.id, ct.root_category_name
from category_tree ct
inner join Categories c
on c.parent = ct.id)
select ct.root_category_name, count(distinct ac.article_id) from category_tree ct
inner join articles_category ac on ac.category_id = ct.id
group by ct.root_category_name
http://sqlfiddle.com/#!4/d42aa/12

Thanks a lot!
Unlucky I can't use the "WITH" statement in mysql (sorry I didn't specify this), but i solve my issue in this way:
create a dataset in wich every ID is associated with his parent_category name
join it on the categories_articoli table
group by parent_category name.
Here is the query if someone may need something like this:
SELECT count(distinct ca.articolo_id), cat.name
FROM categories_articoli ca
JOIN(
SELECT c.tid AS ID, c.name AS name, c.parent
FROM categories c
WHERE c.parent = 0 AND c.tid <> 6
UNION
SELECT c.tid AS ID, parent.name AS name, c.parent
FROM categories c
JOIN categories parent ON c.parent = parent.tid
WHERE c.parent <> 0 AND c.tid <> 10
) cat
ON cat.ID = ca.category_id
GROUP BY cat.name

i think that it is wrong, becouse your solution dont write "top" categories (fe.: you have cat number 3 in 2 in 1 and items only in category 3 - your solution will return right count of items in category 3 and 2, but category 1 wont be in result and it should be there)

Related

SQL - how to get all rows in parent based on number of children of that parent?

I have 2 tables:
parent table: id, name
child table: id, name, id_parent
Now, I want to list rows in parent table have the number rows of children is 3.
Can be done in several ways. Easiest is perhaps a correlated sub-query in the WHERE clause to count the number of children:
select *
from parents p
where (select count(*) from children c
where c.id_parent = p.id) = 3
Or a GROUP BY, with HAVING:
select p.*
from parents p
join children c
on c.id_parent = p.id
group by p.id
having count(*) = 3
I edit my query like this.
select p.*, count(c.id)
from parents p
join children c
on c.id_parent = p.id
group by p.id
having count(*) = 3
is it ok ? jarlh

SQL ManyToMany. How select product from category '1'

Hello i'm beginner in SQL and i do not understand how to:
select product from category where id='1'.
I have 3 tables:
product: id | name
category: id | name
category_product: product_id | category_id
Is this you want?
Make use of join
select
name
from product pr
join category_product cp on (cp.product_id = pr.id)
where cp.catagory_id = '1'
Join your tables on their keys.
SELECT p.name
FROM product p
INNER JOIN category_product cp ON p.id = cp.product_id
INNER JOIN category c ON cp.category_id - c.id
WHERE c.id = 1

SQL Server recursive CTE query to find all items belonging to all categories and their descendants

I am a bit confused trying to use a recursive CTE to list all the items in a product catalogue for all the categories and their respective parent categories that each item belongs to.
The tables are really simple...
Table Category; Columns: Id, ParentID, Title
Table itemCategory; Columns: ItemID, CategoryID
I am just struggling to work out how to get the results I want. My best attempt is not right:
WITH
CTE (itemID, categoryID, title) AS (
SELECT itemID, categoryID, title
FROM itemcategory
INNER JOIN category ON category.ID = itemcategory.categoryID
UNION ALL
SELECT iI.ItemID, iI.categoryID, i.title
FROM itemcategory iI INNER JOIN category i ON i.ID = iI.categoryID
INNER JOIN CTE ON CTE.categoryID = i.ParentID)
SELECT * FROM CTE
I have a similar query which counts the number of items under each category:
WITH cte_count_category(id, parentid, c)
AS (SELECT c1.id,
c1.parentid,
(SELECT Count(*)
FROM (SELECT DISTINCT itemid
FROM itemcategory AS iI
WHERE iI.categoryid = c1.id) AS t1) AS c
FROM category AS c1
UNION ALL
SELECT c2.id,
c2.parentid,
d.c
FROM category c2
INNER JOIN cte_count_category d
ON c2.id = d.parentid)
SELECT cte_count_category.id,
cte_count_category.parentid,
title,
Sum(c) itemCount
FROM cte_count_category
LEFT JOIN category
ON category.id = cte_count_category.id
GROUP BY cte_count_category.id,
cte_count_category.parentid,
title
HAVING Sum(c) > 0
ORDER BY itemcount DESC;
I just can't work out how to get it to list all the items. Any help would be appreciated.
Edit: The categories need to be n deep although it is unlikely that it will go beyond 4 levels most of the time.
The output I am looking for is one row per itemID per category it falls under so, an item in the 'music book' category would appear under both the 'music book' category and the 'book' category.
I think the logic you need is confused by the items. You don't actually need them in the CTE (you can join them in afterwards). Alternatively, you can put them in the "base" part of the construct. Only the categories are needed in the recursive part.
This may be what you are looking for:
WITH CTE (itemID, categoryID, title, parentid, lev) AS (
SELECT ic.itemID, c.categoryID, c.title, c.parentid, 0
FROM itemcategory ic INNER JOIN
category c
ON c.ID = ic.categoryID
UNION ALL
SELECT cte.ItemID, c.categoryID, c.title, c.parentid, cte.lev + 1
FROM CTE INNER JOIN
category c
ON CTE.ParentID = c.categoryID
)
SELECT * FROM CTE

SQL Query / Herigate data join

I have two tables, the first with person data
ID
Name
The second has each person relatives
Primary Key Field
Person (references person.id)
RelativeType (mother or father)
Parent (references person.id)
I'm trying to get all the sons and grandsons of a specific person and I'm stuck integrating the grandsons in the query results. I'm using SQL Server.
Any ideas?
The query you need depends highly of how many level you have of the Parent-Child relationship. If you can change the schema, I would recommend you turn into using HierarchyId which is an SQL Server specific data type. Have a look here.
In the following, I assume you only have 2 levels. Father - Son - GrandSon
;WITH Sons AS (
SELECT pdf.Id, pdf.Name, pdd.Id ParentId, pdd.Name Parent FROM PersonData pdf
JOIN PersonRelative pr ON pdf.Id = pr.Parent
JOIN PersonData pdd ON pr.Person = pdd.Id //Selecting all Parents
)
SELECT pd.Name, s.Name Son, 'Son' Type FROM PersonData pd
JOIN Sons s on pd.Id = s.ParentId
UNION
SELECT pd.Name, gs.Name Son, 'GrandSon' Type FROM PersonData pd
JOIN Sons s on pd.Id = s.ParentId
JOIN Sons gs on s.Id = gs.ParentId
Try something like this (untested):
declare #person bigint = 123
;with cte as
(
select p.id, p.name, r.relativetype, r.parent, 0 decendantLevel
from person p
inner join relatives r
on p.id = r.person
where p.id = #person --select the ancestor who's tree we're interested in (if we don't want to include the person themselves change p.id to r.parent)
union all
select p.id, p.name, r.relativetype, r.parent, c.decendantLevel + 1
from person p
inner join relatives r
on p.id = r.person
inner join cte c
on c.id = r.parentid
)
select * from cte order by decendantLevel
Like mentioned recursive CTE is way to achieve this:
DECLARE #PersonToFind INT
SET #PersonToFind = 1
;WITH RCTE AS
(
SELECT Person, CAST('Child' AS NVARCHAR(MAX)) AS Relation
FROM PersonRelations
WHERE Father = #PersonToFind OR Mother = #PersonToFind
UNION ALL
SELECT pr.Person, 'Grand' + r.Relation
FROM PersonRelations pr
INNER JOIN RCTE r ON r.Person = pr.Mother OR r.Person = pr.Father
)
SELECT r.*, p.Name
FROM RCTE r
LEFT JOIN Person p ON r.Person = p.ID
SQLFiddle DEMO
(this is using mother and father columns before question was edited, so just change to Parent where there are Mother OR Father checks)
Thanks for all the answers, CTE was the way to go. I taked parts from every answer and ended with this
with Sons as (select P1.Person as PersonSon from Persons
join relatives as P1 on P1.Parent = Persons.Id
where Persons.Name = 'Mary')
select Id from Persons
join (
select P2.Person as PersonSon from Relatives as P2
join Sons on P2.Parent = Sons.PersonSon ) as P3 on Persons.Id = P3.PersonSon
union all select Sons.PersonSon from Sons

Optimal SQLite Query Remove Duplicates

I have two tables with the following setup:
category: (id, name)
item: (id, name, category_id) - category_id is foreign key to category table
Now I am writing a query to retrieve a subset from the category table of only used categories:
SELECT c.id, c.name
FROM category c
WHERE c.id IN (SELECT DISTINCT category_id FROM item)
The above query works fine. I'm just wondering if this is the most optimal way of doing the query or if there's something else that I could do via a join or something
Transforming the IN (SELECT) to EXISTS (SELECT ... WHERE ) might help:
SELECT c.id, c.name
FROM category c
WHERE EXISTS (SELECT 1 FROM item WHERE item.category_id = c.id)
Another possibility (I expect it to be slower, but it always depends on your db):
SELECT c.id, c.name
FROM category c
INNER JOIN item ON item.category_id = c.id
GROUP BY c.id
Or you could use DISTINCT instead of GROUP BY:
SELECT DISTINCT c.id, c.name
FROM category c
INNER JOIN item ON item.category_id = c.id
And if speed is that important, don't forget to call ANALYZE from time to time:
http://www.sqlite.org/lang_analyze.html
Some other variants for fun:
SELECT c.id, c.name
FROM category c
INNER JOIN (SELECT DISTINCT item.category_id ) AS i_c ON i_c.category_id = c.id
Another:
SELECT c.id, c.name
FROM category c
EXCEPT
SELECT c.id, c.name
FROM category c
LEFT JOIN item ON item.category_id = c.id
WHERE item.category_id IS NULL
Use Join:
SELECT c.id, c.name
FROM category c
JOIN item i on c.id=i.category_id
GROUP BY c.id, c.name