Reputation: 70139
Given this schema:
CREATE TABLE t (
id int,
name varchar(200),
primary key (id)
);
CREATE TABLE t2 (
id int,
name varchar(200),
primary key (id)
);
CREATE TABLE rel (
id1 int,
id2 int,
value int,
primary key (id1, id2),
foreign key (id1) REFERENCES t(id),
foreign key (id2) REFERENCES t2(id)
);
How should I query rows from t
that satisfy more than 1 relationship with t2
?
This sounds easy enough but I'm not sure what is the best practice in this case. I'll explain better with queries:
SELECT t.id, t.name
FROM t
INNER JOIN rel ON (t.id = rel.id1)
INNER JOIN t2 ON (t2.id = rel.id2)
WHERE (rel.id2 = 1 AND rel.value = 1)
The above works nicely when I need to find rows that satisfy one relationship. But now:
SELECT t.id, t.name
FROM t
INNER JOIN rel ON (t.id = rel.id1)
INNER JOIN t2 ON (t2.id = rel.id2)
WHERE (rel.id2 = 1 AND rel.value = 1)
AND (rel.id2 = 2 AND rel.value = 2)
With 2 relationships this query will never work as the joined rows will never have 2 different relationships in one row, so it will always return 0
rows.
The solution I've been using so far is looking for t.id
s that are IN
a result set from the relation:
SELECT t.id, t.name
FROM t
WHERE (t.id IN (SELECT id1 FROM rel WHERE rel.id2 = 1 AND rel.value = 1))
AND (t.id IN (SELECT id1 FROM rel WHERE rel.id2 = 2 AND rel.value = 2))
This works, but isn't there a better way around? I feel that I'm writing too much SQL and doing one sub-query for each item seems overkill for something so simple.
Here's the SQL Fiddle
Upvotes: 1
Views: 3284
Reputation: 69759
I could be over simplifying the issue, but could you not change an AND
to an OR
in your where clause and use GROUP BY/HAVING
to get the number of relationships for a given criteria. e.g.
SELECT t.id, t.name
FROM t
INNER JOIN rel
ON t.id = rel.id1
INNER JOIN t2
ON t2.id = rel.id2
WHERE (rel.id2 = 1 AND rel.value = 1)
OR (rel.id2 = 2 AND rel.value = 1)
GROUP BY t.Id, t.Name
HAVING COUNT(*) = 2;
Although I would rewrite the where clause as:
WHERE rel.Value = 1
AND rel.ID2 IN (1, 2)
EDIT
I would no longer rewrite the where clause above with the edited question. It would become
WHERE (rel.id2 = 1 AND rel.value = 1)
OR (rel.id2 = 2 AND rel.value = 2)
Upvotes: 3
Reputation: 263713
This is called Relational Division.
SELECT t.id, t.name
FROM t
INNER JOIN rel
ON t.id = rel.id1
WHERE rel.id2 IN (1,2) AND
rel.value = 1
GROUP BY t.id, t.name
HAVING COUNT(rel.id2) = 2
Upvotes: 2