For the first query, you want rows that answer to both of the following criteria:
- The
Name
in the row appears in the table in the same row in which LeftId
and RightId
are both NULL.
- The
Name
in the row appears in the table in same row where at at least one of LeftId
and RightId
is not NULL.
Well, #1 is done by:
SELECT Name FROM Tbl WHERE (LeftId IS NULL) AND (RightId IS NULL)
And #2 is done by:
SELECT Name FROM Tbl WHERE (LeftId IS NOT NULL) OR (RightId IS NOT NULL)
You could intersect them to see which Name
s appear in both lists:
SELECT Name FROM Tbl WHERE (LeftId IS NULL) AND (RightId IS NULL)
INTERSECT
SELECT Name FROM Tbl WHERE (LeftId IS NOT NULL) OR (RightId IS NOT NULL)
Which returns:
Name
----
Cat
Cow
But you want the LeftId
and RightId
, and you don't care which, so I guess we'll aggregate on the Name:
SELECT Name, MIN(LeftId) AS LeftId, MIN(RightId) AS RightId
FROM Tbl WHERE Tbl.Name IN (
SELECT Name FROM Tbl WHERE (LeftId IS NULL) AND (RightId IS NULL)
INTERSECT
SELECT Name FROM Tbl WHERE (LeftId IS NOT NULL) OR (RightId IS NOT NULL)
)
GROUP BY Name
Which returns
Name LeftId RightId
---- ------ -------
Cat 1
Cow 6 7
lc already suggested using COALESE to turn those two IDs to a single one. So how about this:
SELECT Name, COALESCE(MIN(LeftId),MIN(RightId)) AS Id
FROM Tbl WHERE Tbl.Name IN (
SELECT Name FROM Tbl WHERE (LeftId IS NULL) AND (RightId IS NULL)
INTERSECT
SELECT Name FROM Tbl WHERE (LeftId IS NOT NULL) OR (RightId IS NOT NULL)
)
GROUP BY Name
Which returns:
Name Id
---- --
Cat 1
Cow 6
For the second query, you want rows that obey the following criteria:
- The
Name
appears only in rows that have no LeftId
and RightId
I can't think of a way to do that sort of self-referencing query in SQL in a single set of criteria, so I'll break it down to two criteria. Both must be obeyed to be acceptable:
- The
Name
appears in rows that have no LeftId
and RightId
- The
Name
does not appear in rows that have either LeftId
or RightId
Doing #1 is simply:
SELECT Name FROM Tbl WHERE (LeftId IS NULL) AND (RightId IS NULL)
But #2 is tricky. Of course doing the opposite of #2 ("all the Name
that appear in rows that have either LeftId
or RightId
) is just like before:
SELECT Name FROM Tbl WHERE (LeftId IS NOT NULL) OR (RightId IS NOT NULL)
Now comes the tricky bit - we want all the rows that obey #1 but don't obey the opposite of #2. This is where EXCEPT is useful:
SELECT Name FROM Tbl WHERE (LeftId IS NULL) AND (RightId IS NULL)
EXCEPT
SELECT Name FROM Tbl WHERE (LeftId IS NOT NULL) OR (RightId IS NOT NULL)
Which returns:
Name
----
Bird
Which is what we wanted!