Reputation: 2063
I have recently started relearning SQL syntax after about 15 years of inactivity. I am familiar with the old ANSI-89 JOIN syntax and the new syntax is taking some getting used to.
Anyway, what I am trying to do is count the number of elements in two queries, and if they are equal, set a flag for a row.
Here is my table structure:
PERSON
ID -- unique ID for the person
HOUSE
ID -- unique ID for the house
PersonID -- person who owns the house
BATHROOM
HouseID -- House that the room is in
Type -- The type of bathroom - ensuite, powder, etc.
Pieces -- The number of pieces in the bathroom.
OK. So I would like to add a new field in the PERSON
table that keeps track of whether or not all the bathrooms of all their houses are 3 piece ensuites. (Don't ask why - this is obviously a manufactured scenario, but it maps well to the real world scenario I am trying to solve).
So, I start with the following:
ALTER TABLE person ADD All_Three tinyint(1) NOT NULL DEFAULT 1 AFTER ID;
Now I have the field and it is defaulted to 1
.
So, I need to run a query that will populate it. Here is my attempt:
UPDATE IGNORE person,
(SELECT COUNT(*) AS count
FROM house, bathroom
WHERE house.PersonID=person.ID
AND bathroom.HouseID=house.ID
AND bathroom.Type='ensuite'
AND bathroom.Pieces=3) num_three,
(SELECT COUNT(*) AS count
FROM house, bathroom
WHERE house.PersonID=person.ID
AND bathroom.HouseID=house.ID) all_bathrooms
SET person.All_Three=2
WHERE num_three.count=all_bathrooms.count;
This seems pretty logical to me;
The first sub-query counts the number of ensuites that have three pieces, and the second counts the total number of bathrooms. If they are the equal, (i.e. all the bathrooms owned by that person are 3 piece ensuites) then we can set the All_Three
field.
This fails with the error
ERROR 1054 (42S22): Unknown column 'person.ID' in 'where clause'
So, how do I reference the person
inside the sub-query? I tried aliasing it, but that also fails with the same error.
EDIT: It turns out my objective was wrong. What I need to do is set the all_three
field if every house owned by the person contains only 3 piece ensuites, and every house has at least one ensuite (which obviously must be 3 piece).
Upvotes: 2
Views: 865
Reputation: 94913
You want to update records in table person. The syntax for updates in a single table is:
UPDATE tablename SET ... WHERE ...
Instead you are trying to join the table with two so-called derived tables (select statements):
UPDATE tablename, (query 1), (query 2) SET ... WHERE ...
This would cross join the three parts. This is what always happens with the old comma-separated join syntax; you combine all records with each other. Then you'd usually name your join criteria in a WHERE clause to remove records from the results. So with the new sytax this would be:
UPDATE tablename CROSS JOIN (query 1) CROSS JOIN (query 2) SET ... WHERE ...
You give criteria on the join of the two derived queries, however, which turns their join into an inner join:
UPDATE tablename CROSS JOIN (query 1) INNER JOIN (query 2) ON ... SET ...
You are also trying to relate the table person to the derived tables. But inside the two derived table queries there is no knowledge of other tables or derived tables outside. You cannot reference the table person inside the derived table queries.
Not allowed:
UPDATE tablename CROSS JOIN (select something from othertable where othertable.colx = tablename.coly) SET ...
Instead:
UPDATE tablename INNER JOIN (select something, colx from othertable where ...) AS query1 ON query1.colx = tablename.coly) SET ...
But as mentioned, you don't want to update more than one table anyhow, so no need to join. Unlike derived tables, which cannot be related to tables outside, normal subqueries can. You can use such subqueries in the WHERE clause:
UPDATE person
SET ...
WHERE
(select count(*) from ... where ... = person.id) =
(select count(*) from ... where ... = person.id)
As to the problem you clarify in your edit: You want to find persons that only have houses with three-piece ensuite bathrooms. In other words: All persons that have a house (that's a simple EXISTS or IN clause), but no house with another bathroom type or another-piece ensuite batchroom (that's a more complicated NOT EXISTS clause).
In your NOT EXIST clause for a person's unsuitable houses you have again a NOT EXISTS clause (for the case no bothroom exists in that house, if such is possible) and an EXISTS clause (for the case a bathroom with wrong type or pieces number exists in that house).
UPDATE person p
SET all_three = 2
WHERE id IN (select personid from house)
AND NOT EXISTS -- an unsuitable house
(
select *
from house h
where not exists -- a bathroom for that house
(
select *
from bathroom b
where b.houseid = h.id
)
or exists -- bad bathroom for that house
(
select *
from bathroom b
where b.houseid = h.id
and (b.type <> 'ensuite' or b.pieces <> 3)
)
where h.personid = p.id
);
Whether to use (NOT) EXISTS or (NOT) IN clauses here is mainly a matter of personal taste by the way.
Upvotes: 2
Reputation: 3266
To do this, we will produce an inclusion list of PERSON.ID
s meeting the criteria (BATHROOM.Type = 'ensuite' and BATHROOM.Pieces = 3
), an exclusion list of PERSON.ID
s not meeting the criteria (BATHROOM.Type != 'ensuite' or BATHROOM.Pieces != 3
), and use in
/not in
to filter the update:
update PERSON
set PERSON.All_Three = 2
where PERSON.ID in(select HOUSE.PersonID
from HOUSE
inner join BATHROOM
on BATHROOM.HouseID = House.ID
and BATHROOM.Type = 'ensuite'
and BATHROOM.Pieces = 3)
and PERSON.ID not in(select HOUSE.PersonID
from HOUSE
inner join BATHROOM
on BATHROOM.HouseID = House.ID
and (BATHROOM.Type != 'ensuite'
or BATHROOM.Pieces != 3));
Upvotes: 1