2015-02-12 4 views
2

Недавно я начал переучивать синтаксис SQL примерно через 15 лет бездействия. Я знаком со старым синтаксисом ANSI-89 JOIN, и новый синтаксис немного привыкает.Обновление SQL на основе количества строк

В любом случае, что я пытаюсь сделать, это подсчитать количество элементов в двух запросах, а если они равны, установите флаг для строки.

Вот моя структура таблицы:

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. Поэтому я хотел бы добавить новое поле в таблице PERSON, в котором отслеживается, будут ли все ванные комнаты во всех их домах состоять из 3 штук. (Не спрашивайте, почему - это, очевидно, производственный сценарий, но он хорошо отображает сценарий реального мира, который я пытаюсь решить).

Итак, я начинаю со следующим:

ALTER TABLE person ADD All_Three tinyint(1) NOT NULL DEFAULT 1 AFTER ID; 

Теперь у меня есть поле, и это обязательство к 1.

Итак, мне нужно запустить запрос, который будет заполнять его. Вот моя попытка:

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; 

Это кажется мне вполне логичным; Первый подзапрос подсчитывает количество ванных комнат с тремя частями, а второй подсчитывает общее количество ванных комнат. Если они равны, (т. Е. Все ванные, принадлежащие этому человеку, состоят из 3 штук), тогда мы можем установить поле All_Three.

Это завершается с ошибкой

ERROR 1054 (42S22): Unknown column 'person.ID' in 'where clause' 

Итак, как же я ссылаться на person внутри подзапроса? Я попробовал наложить его на псевдонимы, но это тоже не с той же ошибкой.

EDIT: Оказалось, что цель была неправильной. Что мне нужно сделать, это установить поле all_three, если каждый дом, принадлежащий человеку, содержит только 3 штуки, , и каждый дом имеет по крайней мере один санузел (который, очевидно, должен быть 3 штуки).

ответ

1

Вы хотите обновить записи в таблице человека. Синтаксис для обновления в одной таблице:

UPDATE tablename SET ... WHERE ... 

Вместо этого вы пытаетесь соединить таблицу с двумя так называемыми производными таблицами (выберите заявления):

UPDATE tablename, (query 1), (query 2) SET ... WHERE ... 

Этот крест будет присоединиться к трем частей.Это то, что всегда происходит со старым синтаксисом соединения, разделенным запятой; вы объединяете все записи друг с другом. Затем вы обычно называете свои критерии присоединения в предложении WHERE, чтобы удалить записи из результатов. Так с новым sytax это будет:

UPDATE tablename CROSS JOIN (query 1) CROSS JOIN (query 2) SET ... WHERE ... 

Вы отдаете критерии на объединении двух полученных запросов, однако, что превращает их присоединиться к внутреннему соединению:

UPDATE tablename CROSS JOIN (query 1) INNER JOIN (query 2) ON ... SET ... 

Вы также пытаетесь связать человека таблицы с производными таблицами. Но внутри двух производных табличных запросов нет информации о других таблицах или производных таблицах вне. Вы не можете ссылаться на пользователя таблицы внутри производных табличных запросов.

Не допускается:

UPDATE tablename CROSS JOIN (select something from othertable where othertable.colx = tablename.coly) SET ... 

Вместо:

UPDATE tablename INNER JOIN (select something, colx from othertable where ...) AS query1 ON query1.colx = tablename.coly) SET ... 

Но, как уже упоминалось, вы не хотите, чтобы обновить более одной таблицы, во всяком случае, так что не нужно присоединиться. В отличие от производных таблиц, которые не могут быть связаны с таблицами вне, обычные подзапросы могут. Вы можете использовать такие подзапросы в предложении WHERE:

UPDATE person 
SET ... 
WHERE 
    (select count(*) from ... where ... = person.id) = 
    (select count(*) from ... where ... = person.id) 

Что касается проблемы вы уточнить в вашем редактирования: Вы хотите, чтобы найти людей, которые имеют только дома с тремя частями ванных ванной. Другими словами: все лица, которые имеют дом (это простой пример EXISTS или IN), но нет дома с другим видом ванной комнаты или отдельной ванной комнатой (это более сложная статья NOT EXISTS).

В предложении NOT EXIST для неподходящих домов человека у вас снова есть пункт НЕ СУЩЕСТВУЮЩИЙ (для случая, если в этом доме нет двухместного номера, если это возможно) и предложение EXISTS (для случая с ванной с неправильным типом или количество штук в этом доме).

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 
); 

ли использовать (NOT) EXISTS или (НЕ) В пунктах здесь в основном вопрос личного вкуса, кстати.

+0

Мне нравится объяснение - спасибо! – Trenin

1

Чтобы сделать это, мы будем производить список включение PERSON.ID сек, отвечающие критериям (BATHROOM.Type = 'ensuite' and BATHROOM.Pieces = 3), список исключений из PERSON.ID с не отвечающих критериям (BATHROOM.Type != 'ensuite' or BATHROOM.Pieces != 3), а также использовать in/not in фильтровать обновление:

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)); 

SQL FIDDLE

Смежные вопросы