Упражнение 57 (подсказки и решения)
Ниже представлено решение, в котором правильно определяется число потопленных кораблей (хотя, на наш взгляд, весьма громоздко), однако неправильно подсчитывается общее число кораблей в классе.
SELECT f.class, SUM(count_out) AS cnt
FROM (SELECT t.class, SUM(cnt) AS count_out
FROM (SELECT c.class, ship, COUNT(*) CNT
FROM Classes c LEFT JOIN
Ships s ON c.class = s.class INNER JOIN
Outcomes o ON o.ship = s.name AND
result = 'sunk'
GROUP BY c.class,ship
) AS t
GROUP BY t.class
UNION ALL
SELECT t.class, SUM(cnt) AS count_out
FROM (SELECT c.class, ship, COUNT(*) cnt
FROM Classes c INNER JOIN
Outcomes o ON c.class = o.ship AND
o.result = 'sunk' AND
NOT EXISTS (SELECT *
FROM Ships
WHERE o.ship = name
)
GROUP BY c.class,ship
) AS t
GROUP BY t.class
) AS f
GROUP BY f.class
HAVING 2 < (SELECT SUM(cnt)
FROM (SELECT COUNT(c.class) AS cnt
FROM Classes c, Ships s
WHERE c.class = s.class AND
c.class = f.class
UNION
SELECT COUNT(c.class) AS cnt
FROM Classes c, Outcomes o
WHERE c.class = o.ship AND
c.class = f.class AND
NOT EXISTS (SELECT *
FROM Ships
WHERE o.ship = name
)
) AS k
);
[[ column ]] |
---|
[[ value ]] |
Подсчет общего числа кораблей в классе выполняется здесь в предложении HAVING основного запроса. В подзапросе этого предложения для каждого класса из основного запроса выполняется объединение числа кораблей из таблицы Ships с числом кораблей (головных) из таблицы Outcomes при условии, что такие корабли не были учтены ранее (их нет в таблице Ships).
Видно, что поскольку объединяются одноатрибутные отношения посредством оператора UNION, то если у нас имеется по одному кораблю и в одном, и другом наборе, мы заведомо получаем неверный результат в результате устранения дубликатов. Однако здесь это как бы не должно являться ошибкой, так как мы отбираем классы, имеющие в сумме более двух кораблей. А других возможных вариантов быть не должно, поскольку головной корабль если и есть, то он только один (несмотря на излишнее использование COUNT во втором запросе). И все же ошибка кроется именно здесь. Дело в том, что если головной корабль принимал участие более чем в одном сражении, то мы его учитываем по числу сражений, разумеется, если его нет в таблице Ships.
Исправить это решение несложно, предлагаем вам сделать это самостоятельно. Однако можно написать и более простой (а также более эффективный) запрос.