Упражнение 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
            );
mssql
🚫
[[ error ]]
[[ column ]]
[[ value ]]

Подсчет общего числа кораблей в классе выполняется здесь в предложении HAVING основного запроса. В подзапросе этого предложения для каждого класса из основного запроса выполняется объединение числа кораблей из таблицы Ships с числом кораблей (головных) из таблицы Outcomes при условии, что такие корабли не были учтены ранее (их нет в таблице Ships).

Видно, что поскольку объединяются одноатрибутные отношения посредством оператора UNION, то если у нас имеется по одному кораблю и в одном, и другом наборе, мы заведомо получаем неверный результат в результате устранения дубликатов. Однако здесь это как бы не должно являться ошибкой, так как мы отбираем классы, имеющие в сумме более двух кораблей. А других возможных вариантов быть не должно, поскольку головной корабль если и есть, то он только один (несмотря на излишнее использование COUNT во втором запросе). И все же ошибка кроется именно здесь. Дело в том, что если головной корабль принимал участие более чем в одном сражении, то мы его учитываем по числу сражений, разумеется, если его нет в таблице Ships.

Исправить это решение несложно, предлагаем вам сделать это самостоятельно. Однако можно написать и более простой (а также более эффективный) запрос.

Перейти к обсуждению задачи 57

Решить задачу на SQL-EX.RU