In SQL, how can I select all parents that have children? - sql

Let's say I have two related tables parents and children with a one-to-many relationship (one parent to many children). Normally when I need to process the information on these tables together, I do a query such as the following (usually with a WHERE clause added in):
SELECT * FROM parents INNER JOIN children ON (parents.id = children.parent_id);
How can I select all parents that have at least one child without wasting time joining all of the children to their parents?
I was thinking of using some sort of OUTER JOIN but I am not sure exactly what to do with it.
(Note that I am asking this question generally, so don't give me an answer that is tied to a specific RDBMS implementation unless there is no general solution.)

As I put earlier in comments:
Solution with LEFT JOIN and GROUP BY:
SELECT p.parents.id FROM parents p
LEFT JOIN children c ON (p.parents.id = c.children.parent_id)
WHERE children.parent_id IS NOT NULL
GROUP BY p.parents_id
The same with DISTINCT:
SELECT DISTINCT p.parents.id FROM parents p
LEFT JOIN children c ON (p.parents.id = c.children.parent_id)
WHERE children.parent_id IS NOT NULL
It should work in most SQL dialects, though some require as when assigning table aliases.
The above is not tested. Hopefully I made no typos.

I think that the simplest solution that avoids a JOIN would be:
SELECT * FROM parents WHERE id IN (SELECT parent_id FROM children);

try this
select parent_id,(select count(1) from children where parent_id = x.parent_id)
from parent x where
(select count(1) from children where parent_id = x.parent_id) > 0

Related

SQL loop. I want to iterate through a loop containing SELECT results

From a table with column structure (parent, child) I need:
For a particular parent I need all children.
From the result of (1) I need the children's children too.
For example for parent=1:
parent|child parent|child parent|child
1 a a d b f
b e g
This gets you the information you say you want, I think. Two columns: child and grandchild (if any, or else NULL). Not sure if it's the schema you'd like, since you don't specify. You may add JOINs to increase the recursion depth.
select t1.child, t2.child
from T as t1 left join T as t2
on t1.child = t2.parent
where t1.parent = 1
This works on SQLite; I think it's quite standard. Regarding schema if this one doesn't serve you, hopefully it may give you ideas; or else please specify more.

Access SQL Select value with more rows many to many relationship

I'm using Access for a cookbook database, an excercise for Uni.
I'm trying to make a query for a many-to-many relationship.
I have ricette and ingredienti tables, and a junction table named ricetta_ingrediente. Now I should make a query that retrieves each ricette associated with ingredienti used.
EDIT: Part of the question got deleted, I need to retrieve the recipe's ingredients of the one with the most ingredients used, it's the result I have to obtain.
Every single try got me either a syntax error or an empty result - how can I achieve this query?
MORE INFOS
The relationship schema
[
I've tried to implement this suggestion, failing at it, how should it be?
Also here's my try and Access error :
[
Use the query builder DesignView to assist in building SQL statement. Result should look like:
SELECT ricette.nome, ingredienti.nome
FROM ingredienti
RIGHT JOIN (ricette RIGHT JOIN ricetta_ingrediente
ON ricette.ID = ricetta_ingrediente.id_ricetta)
ON ingredienti.ID = ricetta_ingrediente.id_ingrediente;
To retrieve recipe with the most ingredients as well as the ingredients, like:
SELECT TOP 1 ricette.nome, ingredienti.nome
FROM (SELECT id_ricetta, Count([id_ingrediente]) AS CountIng
FROM ricetta_ingrediente GROUP BY id_ricetta) AS Q1
RIGHT JOIN (ricette RIGHT JOIN (ingredienti RIGHT JOIN ricetta_ingrediente
ON ingredienti.ID = ricetta_ingrediente.id_ingrediente)
ON ricette.ID = ricetta_ingrediente.id_ricetta)
ON Q1.id_ricetta = ricetta_ingrediente.id_ricetta
ORDER BY Q1.CountIng DESC;
This will not resolve ties. All recipes with the number of ingredients matching the TOP 1 count will return. How should the query know you want only 1 and which one?
Your query is fine. You just need parentheses, because this is MS Access.
I would also use table aliases:
SELECT r.nome, i.nome
FROM (ricette as r INNER JOIN
ricetta_ingrediente as ri
ON r.ID = ri.id_ricetta
) INNER JOIN
ingredienti as i
ON i.ID = ri.id_ingrediente;
EDIT:
For the revised question:
SELECT TOP (1) r.nome
FROM (ricette as r INNER JOIN
ricetta_ingrediente as ri
ON r.ID = ri.id_ricetta
) INNER JOIN
ingredienti as i
ON i.ID = ri.id_ingrediente
GROUP BY r.nome
ORDER BY COUNT(*) DESC;

SELECT When child record doesn't exist

I have two tables parent and children. The parent.mopid and children.mopid is the connection between the two tables. How would I write a SELECT that would end result show me just the parent records where there are no children records?
Use the NOT IN function:
SELECT * from parent
where parent.mopid NOT IN (SELECT mopid from children)
This will return all rows from the parent table that do not have a corresponding mopid in the childrens table.
If you have a lot of rows, a LEFT JOIN is often quicker than a NOT IN. But not always - it depends on the data so please try this answer and the one from #aktrazer and see which works best for you.
SELECT parent.*
FROM parent
LEFT JOIN children ON parent.mopid = children.mopid
WHERE children.mopid IS NULL
If there isn't a children row for the mopid, parent.mopid will have a value but child.mopid will be null.
SELECT * from parent p where NOT EXISTS
( select mopid from children c where p.mopid = c.mopid)
This should take care of the nulls as well
This link will explain you the difference between NOT IN and NOT EXISTS
NOT IN vs NOT EXISTS

JOIN statement not working with where clause

I'm trying to do
select *
from buildings
where levels = 3
join managers;
but it says error at join. I want to match the the id of the building with the id in the managers table so I think I want natural join.
If you put the JOIN in the right place (between FROM and WHERE) and it were legal to write JOIN without ON then the result would be a cross join - a cartesian product that you then filter in WHERE.
That's a perfectly valid thing to do, though not probably what you intended in this case. It can be achieved by adding comma-separate tables to the FROM clause, eg:
FROM buildings, managers
It's generally better style to write an explicit inner join when you intend to join two tables on a condition:
SELECT *
FROM buildings b INNER JOIN managers m ON (b.manager_id = m.manager_id)
WHERE b.levels = 3;
... because it makes it clear to someone else reading the statement that the ON clause is a join condition, and the bl.levels=3 is a filter. The SQL implementation generally doesn't care, and quite likely transforms the above into:
SELECT *
FROM buildings b, managers m
WHERE b.levels = 3 AND b.manager_id = m.manager_id;
internally anyway, but it's easier (IMO) to understand complex queries with many joins when they're written using explicit join syntax.
There's another way to write what you want, but it's dangerous and IMO shouldn't be used:
SELECT *
FROM buildings bl
NATURAL JOIN managers m
WHERE bl.levels = 3;
That JOINs on any columns that're named the same. It's a nightmare to debug, you have to look up the table structures to understand what it does, it breaks if someone renames a column, and it's just painful. Do not use. See Table expressions in the PostgreSQL manual.
More acceptable is the USING syntax also discussed above:
SELECT *
FROM buildings bl
INNER JOIN managers m USING (manager_id)
WHERE bl.levels = 3;
which matches the columns named manager_id in both columns and JOINs on them, combining them into a single column, but unlike NATURAL JOIN does so explicitly and without scary magic.
I still prefer to write INNER JOIN ... ON (...) but it's reasonable to use USING and, IMO, never reasonable to use NATURAL.
Test table structures were:
create table managers ( manager_id integer primary key );
create table buildings (
manager_id integer references manager(manager_id),
levels integer
);
JOIN operator applied to the tables, you should provide it in the FROM clause. If you do so you will get a new error claiming that there is no JOIN condition, because you have to provide the JOIN condition after the ON clause (it is not optional):
SELECT *
FROM buildings b JOIN managers m ON b.managerid = m.id
WHERE b.levels = 3
if you want to do that, you have to write something like:
select *
from bulidings b
join managers m on b.id=m.id
where levels = 3
The request seems odd however, I believe that one of them should have a foreign key into the other. So a more appropriate query would be:
select *
from buidings b
join managers m on b.id = m.building_id //// or b.manager_id = m.id
where levels = 3
I think which query you are written is wrong.
So please try this
select * from buildings as bl
join managers as mn on bl.F_ManagerID = mn.ManagerID
where bl.levels = 3
I think this will help you...

SQL Query to retrieve data while excluding a set of rows

I have basically four tables (SQL Server):
Objects:
id
ObjectName
Components
id
ComponentName
ObjectsDetails:
ObjectID
ComponentID
ExclusionTable
id
ComponentID
Basically, these tables describe Objects and what Objects are made of (what components)
For example, Object "A" may be made out of component "A" and component "B".
In this case, the tables would be populated this way:
Objects:
id ObjectName
1 A
Components:
id ComponentName
1 A
2 B
ObjectDetails:
ObjectID ComponentID
1 1
1 2
Now, "ExclusionTable" may have a list of components that are to be excluded from a search (therefore, excluding entire objects if the object is made out of at least one of those components).
For example, I would like to ask:
"Give me all the Objects that are not made out of components A and B".
Therefore, my question is:
Is there a way to write a query for that ? No views, no stored procedures please.. my SQL engine does not support that.
I tried something like:
SELECT DISTINCT ObjectName FROM Objects INNER JOIN ObjectsDetails ON Objects.id =
ObjectDetails.ObjectID WHERE ObjectsDetails.ComponentID NOT IN (1,2)
in case ExclusionTable tells us that Components A and B needs to be excluded.
Of course, that doesn't work...
I tried a few variations using WHERE NOT EXISTS (SELECT * FROM ExclusionTable) but I am not proficient enough in SQL to understand how to get it to work using one query only (if it is even possible).
Thanks!
You should avoid doing queries with [not] in (select ...)
SELECT DISTINCT ObjectName
FROM Objects
INNER JOIN ObjectsDetails ON Objects.id = ObjectDetails.ObjectID
LEFT JOIN ExclusionTable on ExclusionTable.ComponentId = ObjectsDetails.ComponentID
where ExclusionTable.ComponentId is null;
This will retrieve only rows for which the ComponentID is not in ExclusionTable.
Update:
SELECT ObjectName
FROM Objects
INNER JOIN ObjectsDetails ON Objects.id = ObjectDetails.ObjectID
LEFT JOIN ExclusionTable on ExclusionTable.ComponentId = ObjectsDetails.ComponentID
group by ObjectName
having count(distinct ObjectsDetails.ComponentID) = sum(case when ExclusionTable.id is null then 1 else 0 end)
New approach, I think the only other way I could do it is basically to compare the number of components per object with the number of components in the object not included on the list. When these number are equal, no component is on the excluded list and we can show the object.
I'm sorry I can't make a test right now, please use EXPLAIN select ... to compare the queries, if they work.
Basically, if you need to get all objects not made from A or B, you need to get all objects EXCEPT those made from A or B.
SELECT DISTINCT Id, ObjectName
FROM Objects
WHERE Id NOT IN (
SELECT DISTINCT ObjectDetails.ObjectID
FROM ObjectDetails
INNER JOIN Components ON ObjectDetails.ComponentID = Components.Id
WHERE Components.ComponentName = 'A' OR Components.ComponentName = 'B'
)
Would that be what you're looking for?
EDIT: Of course, you can omit the join if you already have the component ids - then just put those in the where clause to filter them out.
select id, objectname
from Objects
left outer join
( select objectid from ObjectsDetails od inner join Exclusiontable et
on od.ComponentID= et.ComponentID) excludedid
on Objects.ID = excludedid.ObjectID and excludedid.ObjectID is null