Recursive retrieve of child entities but with exact property match - sql

I have the following tables:
Companies table:
CompanyId MotherCompanyId CompanyName
---------- ------------ ------------
1 NULL HpTopMother
2 1 HpTopDaughter1
3 2 HpTopDaughter2
4 3 HpTopDaughter3
CompanyCategories table:
CompanyCategoryId CompanyId Category
----------------- -------- -----------
1 1 Car
2 1 Lorry
3 2 Car
4 2 Lorry
5 2 Plane
6 3 Car
7 3 Lorry
8 4 Car
What I want to do is to display all the daughter companies of the head company (the Id will be passed as paramter), that have the exact match in the CompanyCategories table.
For example, in the case above only the head company, HpTopMother, and daughter company with Id 3, HpTopDaughter2, will be displayed
as both have the categories Car and Lorry.
HpTopDaughter1 will not be retrieved since it has the Plane category.
HpTopDaughter3 will not be retrieved since it does not have the Car category.
I have done the below to get all the daughters/grand-daughters of the head company:
DECLARE #companyId BIGINT
SET #companyId = 1;
WITH CTE AS
(
SELECT COM.CompanyId, COM.CompanyName
FROM Companies COM
WHERE COM.CompanyId = #companyId
UNION ALL
SELECT COM_CHILD.CompanyId, COM_CHILD.CompanyName
FROM Companies COM_CHILD JOIN cte c ON COM_CHILD.MotherCompanyId = c.CompanyId
INNER JOIN CompanyCategories CC ON CC.CompanyId = c.CompanyId
INNER JOIN CompanyCategories CC_CHILD on CC_CHILD.CompanyId = COR_CHILD.CompanyId and CC.Category = CC_CHILD.Category
)
SELECT CompanyId, CompanyName
FROM CTE
However, this is returning all the companies. Any idea of how I can achieve the listing of all the daughters/grand-daughters companies but only those having an exact category match?

Check if category sets are the same for the top level company and its descendants
DECLARE #companyId BIGINT
SET #companyId = 1;
WITH rcats as(
select null a, 1 b, category
from CompanyCategories CC
where CC.companyId = #companyId
), CTE AS
(
SELECT COM.CompanyId, COM.CompanyName
FROM Companies COM
WHERE COM.CompanyId = #companyId
UNION ALL
SELECT COM_CHILD.CompanyId, COM_CHILD.CompanyName
FROM Companies COM_CHILD
JOIN cte c ON COM_CHILD.MotherCompanyId = c.CompanyId
)
SELECT CompanyId, CompanyName
FROM CTE c
where not exists (
select 1
from
( select 1 a, null b, category
from CompanyCategories CC
where CC.CompanyId = c.CompanyId
union all
select a, b, category
from rcats
) t
group by category
having count(a) <> count(b)
)
db<>fiddle

Use CTE to search for parent-child chains and then filter out companies according to your condition using Not Exist and Except.
Declare #ID Int = 1;
With A As
( Select CompanyId, CompanyName, Row_Number() Over (Order by CompanyId) As Num
From Companies
Where CompanyId = #ID
Union All
Select Companies.CompanyId, Companies.CompanyName, A.Num
From Companies Inner Join A On (Companies.MotherCompanyId=A.CompanyId)
)
Select A.Num, A.CompanyName, String_Agg(C.Category,',') As Categories
From A Inner Join A As A_1 On (A.Num=A_1.Num)
Inner Join CompanyCategories As C On (A.CompanyId=C.CompanyId)
Where A.CompanyId<>A_1.CompanyId And
Not Exists (Select Category From CompanyCategories
Where A_1.CompanyId=CompanyCategories.CompanyId
Except
Select Category From CompanyCategories
Where A.CompanyId=CompanyCategories.CompanyId) And
Not Exists (Select Category From CompanyCategories
Where A.CompanyId=CompanyCategories.CompanyId
Except
Select Category From CompanyCategories
Where A_1.CompanyId=CompanyCategories.CompanyId)
Group by A.Num, A.CompanyName
Order by A.Num, A.CompanyName Desc
db<>fiddle
Result
Num
CompanyName
Categories
1
HpTopMother
Car,Lorry
1
HpTopDaughter2
Car,Lorry
to get companies with the same categories as the company associated with #ID, use the following query:
Declare #ID Int = 1;
With A As
( Select CompanyId, CompanyName, Row_Number() Over (Order by CompanyId) As Num
From Companies
Where CompanyId = #ID
Union All
Select Companies.CompanyId, Companies.CompanyName, A.Num
From Companies Inner Join A On (Companies.MotherCompanyId=A.CompanyId)
)
Select A.Num, A.CompanyName, String_Agg(C.Category,',') As Categories
From A Inner Join A As A_1 On (A.Num=A_1.Num)
Inner Join CompanyCategories As C On (A_1.CompanyId=C.CompanyId)
Where A_1.CompanyId = #ID
And Not Exists (Select Category From CompanyCategories
Where A_1.CompanyId=CompanyCategories.CompanyId
Except
Select Category From CompanyCategories
Where A.CompanyId=CompanyCategories.CompanyId)
And Not Exists (Select Category From CompanyCategories
Where A.CompanyId=CompanyCategories.CompanyId
Except
Select Category From CompanyCategories
Where A_1.CompanyId=CompanyCategories.CompanyId)
Group by A.Num, A.CompanyName
Order by A.Num, A.CompanyName Desc

Related

SQL query to find a record which has all matching records in another table

I have below 3 tables and I want to write a SQL query which will list the store present in all city: (here the result should be "Walmart")
Stores:
ID Name
1 Walmart
2 Target
3 Sears
Stores_City
ID Store_id City ID
1 1 10
2 1 20
3 2 10
4 1 30
City
ID Name
10 NewYork
20 Boston
30 Eagan
I am unable to find a query that works. Any help is appreciated!
select s.Name
from Stores s
inner join
(
select store_id, count(distinct city_id)
from stores_city
group by store_id
having count(distinct city_id) = (select count(*) from City)
) x
on x.store_id = s.id;
You can do it by grouping on store_id and checking for the count from stores table.
A straight join would work
Select distinct s.name from stores s inner join store _city SC on s.id=sc.id
Inner join city c on
Sc.city_id = c.id
Here is another way that will work:
select s.*
from stores s
where not exists (
select c.id
from city c
except
select sc.city_id
from stores_city sc
where sc.store_id = s.id
)
Try this:
SELECT
s.Name
FROM Stores s
WHERE NOT EXISTS (SELECT TOP 1
1
FROM City c
LEFT JOIN Stores_City sc
ON c.ID = sc.CityID
AND sc.Store_id = s.ID
WHERE sc.ID IS NULL)

SQL Server - For each distinct company count the number of employees

I am trying to create some SQL that will count the number of employees within each company and return only those companies with greater than or equal to n employees.
I have the following tables (simplified):
CompanyEmployee Table
ID Name IsCompany
1 John Joe 0
2 Company Y 1
3 Company X 1
4 Sally Jeff 0
5 James Peach 0
Employment Table
ID EmployeeID CompanyID
1 1 2
2 4 3
3 5 3
My desired result for n=2:
ID Name IsCompany
3 Company X 1
I have the following SQL:
SELECT t.* FROM CompanyEmployee AS t
WHERE t.ID IN (
SELECT DISTINCT (t.ID)
FROM CompanyEmployee AS t
INNER JOIN Employment AS t0 ON t.ID = t0.CompanyID
WHERE t.IsCompany = 1
GROUP BY t0.CompanyID
HAVING COUNT(t0.EmployeeID) >= n)
But it generates the following error:
Column 'CompanyEmployee.ID' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Any help or advice would be greatly appreciated!
Mixing companies and employees this way is a bad idea. Using a straight inner join may work as long as the id values between the two different types of rows never intersect. I guess if it's an identity column then that's not supposed to happen.
with Companies as (
select ID as CompanyID, Name
from CompanyEmployee
where IsCompany = 1
), Employees as (
select CompanyID, Name
from CompanyEmployee where
IsCompany = 0
)
select c.CompanyID, Name, 1 as IsCompany
from
Companies as c
inner join Employment as ec on ec.CompanyID = c.CompanyID
inner join Employees as e on e.EmployeeID = ce.EmployeeID
group by
c.CompanyID
having count(*) >= n
There is still a straightforward way to do it:
select *
from CompanyEmployee
where ID in (
select CompanyID
from Employment
group by CompanyID
having count(*) >= n)
)
Try this:
SELECT t1.CompanyId, t2.CompanyName, COUNT(t1.CompanyId)
FROM Employment AS t1 INNER JOIN CompanyEmployee AS t2 ON t1.CompanyId = t2.Id
GROUP BY t1.CompanyId, t2.CompanyName
HAVING COUNT(t1.CompanyId)>=n
where n is a number of employees...

SQL Server inner join after Count and GroupBy

I'm trying to make an eCommerce web site. And at ProductCategory page I have to do a list about Companies that included by Products.
I have a Product table that contains:
ProductID
ProductName
...
MarkID
And I have a Company table that contains:
CompanyID
CompanyName
I want to mix them in a query.
After that code block,
SELECT
CompanyID,
count(CompanyID) as CompanyCount
FROM
Products
GROUP by
CompanyID
I get this result:
CompanyID CompanyCount
-------------------------
1 2
3 1
4 4
after that I just want to inner join that with CompanyName
And want a result like this:
CompanyName CompanyCount
---------------------------
1 2
3 1
4 4
How can I do that?
With a subquery:
SELECT * FROM
(
SELECT CompanyID, count(CompanyID) as CompanyCount
FROM Products
GROUP by CompanyID
) CompanyCounts
INNER JOIN Companies
on CompanyCounts.CompanyId = Companies.CompanyID
If you need this in more places, you may want to create a view for company count.
This can be achieved without a subquery.
SELECT C.CompanyID, C.CompanyName, COUNT(*)
FROM Products P INNER JOIN Companies C ON P.CompanyId = C.CompanyID
GROUP BY C.CompanyID, C.CompanyName
You can use your first select as a subquery to join with Companies table:
SELECT C.CompanyName, Q1.CompanyCount
FROM Companies C
JOIN (
SELECT CompanyID, count(CompanyID) as CompanyCount
FROM Products
GROUP by CompanyID
) Q1
ON Q1.CompanyID = C.CompanyId

SQL First row only following aggregation Rules

I need to select only the most significant value from a table. Using Postgre SQL (last version) Follows the data sample:
Table Company
Id, Name, ExternalId, StartAt
1 Comp1 54123 21/05/2000
2 Comp2 23123 21/05/2000
Table Address
Id, Company, Address_Type, City
1 1 7 A
2 2 2 B
3 2 62 C
Table Adress_Type
Id, Name, importance_order
62 Adt1 1
7 Adt2 2
2 Adt3 2
What i need to do is to get the company and its major Address, based on the "importance_order". There is already a function that returns this result:
Create function~~~~
Select * from Company c
join Address a on c.address_id = a.id
Join AddressType at on a.adresstype_id = at.id
ORDER by at.importance_order
Limit 1
My problem now is that this function is called one time for every row in the query, and it take so much time (about 20 min.). Should it be possible to do this similar aproach by joinning tables? I need this join to get the First "most important"address, and then get the City name, but need to do this in a "faster" way. I need to reduce subquery`s number to its minimal.
Select * from table t
inner join Company c on t.company_id = c.id
left join address a on (c.company_id = c.id)
left join addresstype at on (a.adresstype_id = at.id)
where at.id = (
select max(id) from addresstype
where adresstype in (
select adresstype from adress where company_id = c.id
)
)
If it is not clear tell me that i get more into details.
Thanks.
For this you need PostgreSQL 8.4+ I suppose
SELECT T.*
FROM TABLE AS T
INNER JOIN
(
SELECT * FROM
(
SELECT C1.*, ROW_NUMBER() OVER(partition by C1.ID ORDER BY T.IMPORTANCE_ORDER) AS RN
FROM COMPANY AS C1
INNER JOIN ADDRESS AS A
ON C1.ID = A.COMPANY
INNER JOIN ADDRESS_TYPE AS T
ON T.ID = A.ADDRESS_TYPE_ID
) A
WHERE RN = 1
) AS B
ON B.ID= T.COMPANY_ID

SQL - Count on recursive category

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)