На главную страницу
Характерные ошибки при решении упражнений.
Задача 39
Моисеенко С.И.
Найдите корабли, "сохранившиеся для будущих сражений"; т.е. выведенные из строя в
одной битве (damaged), они участвовали в другой.
Вот пример неправильно понятого условия:
SELECT DISTINCT ship FROM Outcomes os
WHERE EXISTS(
SELECT ship FROM Outcomes oa
(WHERE oa.ship=os.ship AND
result='damaged'
)
AND EXISTS(
SELECT SHIP
FROM Outcomes ou
WHERE ou.ship=os.ship
GROUP BY ship
HAVING COUNT(battle)>1
)
Это решение исполнено в стиле реляционного исчисления, а именно, разыскиваются
такие корабли в таблице Outcomes, которые были повреждены (первый EXISTS) и
которые участвовали более чем в одной битве (второй EXISTS).
Ошибка же состоит в том, что проигнорировано условие "сохранившиеся для будущих
сражений", которое означает, что после битвы, в которой корабль получил
повреждение, он принимал участие в БОЛЕЕ ПОЗДНЕМ сражении. Таким образом, для
получения правильного решения этой задачи нужно анализировать даты сражений,
которые содержатся в таблице сражений Battles.
Нижеприведенный запрос ранее принимался системой:
SELECT s.name
FROM Ships s JOIN Outcomes o ON s.name = o.ship
JOIN Battles b ON o.battle = b.name
GROUP BY s.name HAVING COUNT (s.name) = 2
AND (MIN(result) = 'damaged' or MAX(result) =
'damaged')
UNION
SELECT o.ship
FROM Classes c JOIN Outcomes o ON c.class = o.ship
JOIN Battles b ON o.battle = b.name
WHERE o.ship NOT IN(SELECT name FROM Ships)
GROUP BY o.ship HAVING COUNT (o.ship) = 2
AND (MIN(result) = 'damaged' OR MAX(result) =
'damaged')
Во-первых, объединяются запросы, которые выполняют соединение участвующих в
сражениях кораблей (таблица Outcomes) с таблицами Ships и Classes
соответственно. Кстати говоря, предикат o.ship NOT IN(SELECT
name FROM Ships) во втором запросе явно лишний, т.к. UNION исключит
возможные дубликаты.
Эти соединения не просто избыточны, они ошибочны, т.к. в описании БД сказано,
что в таблице Outcomes могут быть корабли, отсутствующие в Ships. Т.е. если
найдется не головной корабль, которого нет в таблице Ships и который отвечает
условиям задачи, то он не попадет в результирующий набор вышеприведенного
запроса.
Во-вторых, предикат HAVING COUNT (o.ship) = 2 ограничивает
возможные варианты только двумя сражениями корабля. А почему корабль не может
принимать участие более чем в двух сражениях? Он же не обязательно был потоплен
после того, как получил повреждение.
В третьих, мне не вполне понятно условие:
(min(result) = 'damaged' or max(result) = 'damaged')
В соответствии с описанием предметной области корабль может быть:
Damaged (поврежден)
Ok (невредим)
Sunk (потоплен)
Поэтому условие min(result) = 'damaged' будет
выполнено, если в одной из битв корабль был поврежден (при естественной
сортировке текстовых строк буква "D" идет раньше, чем буквы "O" и "S"). Однако
это совсем не означает, что поврежден он был прежде, чем принял участие в
следующем сражении, что требуется по условиям задачи. Здесь уже нужно оценивать
дату сражения. Что же касается max(result) = 'damaged',
то это условие не будет выполняться, если результаты сражений были разные; а
если одинаковые, то это не даст ничего нового по сравнению с первым условием на
минимум.
Вот такое наложение ошибок давало правильный результат на обеих базах.
Меры были приняты, т.е. добавлены проверочные данные, на которых данное
решение дает неверный результат.
Приведенные здесь примеры можно выполнить непосредственно на сайте, установив
флажок "Без проверки" на странице с упражнениями
на SELECT.
Перейти к
решению задачи #39
На главную страницу