Главная страница
Навигация по странице:

  • Bruce Willis Don_avia 2 Bruce Willis Aeroflot 2 Bruce Willis

  • База данных «Окраска»

  • Ошибки в задачах DML

  • 1916 1913 1920 Тогда строки, удовлетворяющие предикату, будут выглядеть так: country x launched Gt.Britain

  • лекция. Учебник по sql если вы хотите узнать, что такое sql этот сайт для вас


    Скачать 7.88 Mb.
    НазваниеУчебник по sql если вы хотите узнать, что такое sql этот сайт для вас
    Анкорлекция
    Дата21.12.2022
    Размер7.88 Mb.
    Формат файлаpdf
    Имя файлаMoiseenko_SQL.pdf
    ТипУчебник
    #857633
    страница11 из 47
    1   ...   7   8   9   10   11   12   13   14   ...   47
    Решение 3.5.2

    1.
    SELECT
    name
    2.
    FROM
    Ships
    3.
    WHERE
    launched <
    1941 4.
    UNION
    5.
    SELECT
    ship
    6.
    FROM
    Outcomes, Battles
    7.
    WHERE
    name = battle
    AND
    8. DATEPART
    (
    YEAR, date
    )
    <
    1941 9.
    UNION
    10.
    SELECT
    ship
    11.
    FROM
    Outcomes
    12.
    WHERE
    ship
    IN
    (
    SELECT
    class
    13.
    FROM
    Ships
    14.
    WHERE
    launched <
    1941 15.
    )
    ;
    Решение 3.5.2 учитывает:
    1. Корабли из таблицы Ships c известным годом спуска на воду до 1941 года.
    2. Корабли, которые принимали участие в сражениях до 1941 года
    (естественно, такие корабли должны были быть спущены на воду до сражения, в котором они принимали участие).
    3. Корабли из таблицы Outcomes, имена которых совпадает с именем класса какого-нибудь корабля из Ships, спущенного на воду до 1941 года.
    Следует заметить, что возможные дубликаты устраняются использованием объединения посредством UNION.
    Последний вариант учитывает и те случаи, когда головной корабль принимал участие в сражениях только после 1941 года, так как более ранние сражения учтены предыдущим запросом. Осталось выяснить, зачем это нужно. Ответ на этот вопрос следует искать в «висящих» головных кораблях. Итак, корабль из
    Outcomes, имя которого совпадает с именем одного из классов (головной корабль), отсутствует в таблице Ships или присутствует, но с неизвестным годом спуска на воду. Пусть в таблице Ships есть корабль того же класса с известным годом спуска на воду. Если этот год окажется меньше 1941, то головной корабль следует включать в результирующий набор наряду с упомянутым кораблем. Это следует из того факта, что головной корабль — это
    первый корабль в своем классе и, следовательно, должен был быть спущен на воду не позднее любого другого корабля своего класса.
    Примечание:
    Во втором из объединяемых запросов решения 3.5.2 используется
    специфическая для
    SQL
    Server функция DATEPART. Она необходима, так
    как из даты сражения (поле date имеет темпоральный тип данных —
    datetime) нужно извлечь год сражения; в противном случае предикат.
    1. date <
    1941
    будет давать сравнение с датой 1905 года, в результате преобразования целого числа к типу дата-время (как число дней прошедших от начала 1900 года, что является точкой отсчета дат для настроек по умолчанию в SQL Server 2000). В этом легко убедиться, если выполнить запрос:
    1.
    SELECT
    CAST
    (
    1941
    AS
    DATETIME
    )
    ; результатом которого будет
    1.
    1905-04-26 00
    :
    00
    :
    00.000
    Чтобы оставаться в рамках стандарта, можно было бы использовать следующий предикат:
    1. date <
    '1941'
    Тогда неявное преобразование типа дало бы нужный результат. Опять проверим:

    1.
    SELECT
    CAST
    (
    '1941'
    AS
    DATETIME
    )
    ; что дает
    1.
    1941-01-01 00
    :
    00
    :
    00.000
    Естественно, правильным будет и предикат, содержащий полную дату начала
    1941 года (1 января):
    1. date <
    '19410101'
    Однако вернемся к нашему решению. Оно неверно. Как было сказано, здесь не учитывается ситуация, когда головной корабль присутствует только в Ships, и год его спуска на воду неизвестен. Учесть эту ситуацию можно, добавив к объединению еще один запрос.
    Решение 3.5.3
    1.
    SELECT
    name
    2.
    FROM
    Ships
    3.
    WHERE
    launched <
    1941 4.
    UNION
    5.
    SELECT
    ship
    6.
    FROM
    Outcomes, Battles
    7.
    WHERE
    name = battle
    AND
    8. DATEPART
    (
    YEAR, date
    )
    <
    1941 9.
    UNION
    10.
    SELECT
    ship
    11.
    FROM
    Outcomes
    12.
    WHERE
    ship
    IN
    (
    SELECT
    class
    13.
    FROM
    Ships
    14.
    WHERE
    launched <
    1941 15.
    )
    16.
    UNION
    17.
    SELECT
    name
    18.
    FROM
    Ships

    19.
    WHERE
    name
    IN
    (
    SELECT
    class
    20.
    FROM
    Ships
    21.
    WHERE
    launched <
    1941 22.
    )
    ;
    Сколько интересной информации можно извлечь из этой задачи, всего лишь поменяв год!
    Однако и это еще не все. Предлагаем самостоятельно найти дополнительные корабли, отвечающие условиям задачи. Проверить себя вы сможете, заглянув в
    ПиР
    Упражнение 124
    Среди пассажиров, которые пользовались
    услугами не менее двух авиакомпаний,
    найти тех, кто совершил одинаковое
    количество полётов самолетами каждой
    из этих авиакомпаний. Вывести имена
    таких пассажиров.
    Эта задача порождает массу ошибочных решений, которые я разделяю на две группы. К первой группе относятся решения, связанные с неверным прочтением формулировки.
    Например, пытаются найти двух пассажиров, которые летали бы одинаково двумя или большим числом компаний.
    Поясню, что рассматривать следует отдельного пассажира и подсчитываеть число полетов, которое он сделал каждой из авиакомпаний, которыми летал.
    Что дальше? Рассмотрим теперь пример из второй группы ошибочных решений:

    1.
    SELECT
    DISTINCT
    name
    2.
    FROM
    (
    SELECT
    id_psg, id_comp,
    COUNT
    (
    pt.trip_no
    )
    AS
    CNT
    3.
    FROM
    pass_in_trip pt
    JOIN
    trip t
    ON
    pt.trip_no=t.trip_no
    4.
    GROUP
    BY
    id_comp,id_psg
    )
    a,
    5.
    (
    SELECT
    id_psg, id_comp,
    COUNT
    (
    pt.trip_no
    )
    AS
    CNT
    6.
    FROM
    pass_in_trip pt
    JOIN
    trip t
    ON
    pt.trip_no=t.trip_no
    7.
    GROUP
    BY
    id_comp,id_psg
    )
    b,
    8. passenger p
    9.
    WHERE
    a.id_psg=b.id_psg
    AND
    a.id_comp<>b.id_comp
    AND
    a.cnt=b.cnt
    10.
    AND
    p.id_psg=b.id_psg;
    Сразу отметим ошибочное DISTINCT name, которое устраняет возможных однофамильцев. Однако не это здесь главное. В предложении FROM соединяются два одинаковых запроса
    1.
    SELECT
    id_psg, id_comp,
    COUNT
    (
    pt.trip_no
    )
    AS
    CNT
    2.
    FROM
    pass_in_trip pt
    JOIN
    trip t
    ON
    pt.trip_no=t.trip_no
    3.
    GROUP
    BY
    id_comp,id_psg; которые, как и сказано выше, подсчитывают для каждого пассажира число полетов, которое он совершил самолетами каждой из компаний.
    Соединяются эти запросы по следующим условиям:

    пассажир тот же самый;

    компании разные;

    число полетов совпадает.
    Итак, если пассажир совершил, скажем, компанией Aeroflot 3 полета, и также
    3 полета он совершил самолетами компании Don_avia, то такой пассажир удовлетворяет условиям соединения и будет выведен в результатах запроса.
    Если пассажир летал всего двумя компаниями, то это - правильный результат.
    А если компании три?
    Если в результате рассмотренного выше подзапроса мы получим

    Bruce Willis
    Don_avia 2
    Bruce Willis
    Aeroflot 2
    Bruce Willis Dale_avia 1 то пассажир Bruce Willis не отвечает условиям задачи, хотя рассматриваемый запрос выведет его, поскольку в запросе будут соединены первые две строки.
    Итак, число полетов пассажира каждой из компаний, которыми он летал, должно находиться в пропорции 1:1:...1.
    База данных
    «Окраска»
    Схема базы данных состоит из трех таблиц:
    1. utQ
    (
    Q_ID int, Q_NAME varchar
    (
    35
    ))
    ;
    2. utV
    (
    V_ID int, V_NAME varchar
    (
    35
    )
    , V_COLOR char
    (
    1
    ))
    ;
    3. utB
    (
    B_Q_ID int, B_V_ID int, B_VOL tinyint, B_DATETIME datetime
    )
    Таблица utQ содержит идентификатор и название квадрата, цвет которого первоначально черный.
    Таблица utV содержит идентификатор, название и цвет баллончика с краской.
    Таблица utB содержит информацию об окраске квадрата баллончиком: идентификатор квадрата, идентификатор баллончика, количество краски и время окраски.
    При этом следует иметь в виду, что:


    баллончик с краской может быть одного из трех цветов - красный
    V_COLOR='R', зеленый V_COLOR='G', синий V_COLOR='B'
    (латинские буквы).

    объем баллончика равен 255 и первоначально он полный;

    цвет квадрата определяется по правилу RGB, т.е. R=0,G=0,B=0 - черный, R=255, G=255, B=255 - белый;

    запись в таблице закрасок utB уменьшает количество краски в баллончике на величину B_VOL и соответственно увеличивает количество краски в квадрате на эту же величину;

    значение 0 < B_VOL <= 255;

    количество краски одного цвета в квадрате не превышает 255, а количество краски в баллончике не может быть меньше нуля;

    время окраски B_DATETIME дано с точностью до секунды, т.е. не содержит миллисекунд.
    Рис. Схема базы данных "Окраска"
    Некоторые пояснения к схеме.
    Идентификаторы черных квадратов отсутствуют в таблице utB. Это следует из того, что B_VOL не допускает NULL-значений и строго больше нуля.
    Таким образом, каждая запись в таблице utB описывает факт окраски одного квадрата одним баллоном, черный же квадрат (R=0,G=0,B=0) не подвергался окраске вовсе.
    Ограничения схемы допускают одновременную окраску одного квадрата несколькими баллонами, как и одновременную окраску одним баллоном
    нескольких квадратов. Однако запрещена одновременная окраска одного квадрата одним и тем же баллоном.
    Ошибки в
    задачах DML
    В этой главе я разберу некоторые ошибки, которые допускаются в задачах DML. Номера задач, как и их формулировки, не приводятся, т.к. пока задачи DML не разделяются на учебные и рейтинговые, ввиду их малочисленности.
    Проблема. Требуется определить
    максимальную скорость имеющихся CD-ROM.
    Ошибка состоит в использовании
    1.
    SELECT
    MAX
    (
    cd
    )
    FROM
    Дело в том, что скорость CD-ROM хранится в текстовом представлении
    (например, '12x'). При сравнении текстовых констант '4x' оказывается больше, чем '24x'. А если потребуется посчитать СРЕДНЮЮ скорость?!
    Проблема. Требуется округлить среднее значение года спуска на
    воду кораблей.
    Типичной ошибкой здесь является, например, такой прием:
    1. round
    (
    AVG
    (
    launched
    )
    ,
    0
    )
    Дело в том, что среднее значение приводится к типу аргумента. В данном случае оно приводится к целому числу, т.к. столбец launched имеет тип int. При этом SQL Server не округляет результат, а отбрасывает дробную часть. Это легко проверить, выполнив запрос:

    1.
    SELECT
    AVG
    (
    launched
    )
    FROM
    (
    VALUES
    (
    9
    )
    ,
    (
    10
    )
    ,
    (
    10
    ))
    X
    (
    launched
    )
    ;
    Математически 29/3 - это почти 10. Однако получаем 9. В результате функция ROUND здесь совершенно лишняя, т.к. округлять уже нечего.
    Так как же здесь следует поступить? Очень просто. Нужно привести аргумент к вещественному типу, по нему считать среднее, а уже затем округлять.
    Упражнение 128
    Определить лидера по сумме выплат в
    соревновании между каждой парой
    пунктов с одинаковыми номерами из двух
    разных таблиц - outcome и outcome_o - на
    каждый день, когда осуществлялся прием
    вторсырья хотя бы на одном из них.
    Вывод: Номер пункта, дата, текст:-
    "once a day", если сумма выплат больше у
    фирмы с отчетностью один раз в день;-
    "more than once a day", если - у фирмы с
    отчетностью несколько раз в день;-
    "both", если сумма выплат одинакова.
    Основная сложность в понимании условия этой задачи заключается в том, как выполнять сравнение, если в некий день в одной таблице есть строка ( строки), а в другой - нет.
    Как написано в описании предметной области, таблицы с суффиксом "_o" и без него - это разные схемы. Т.е. мы можем считать, что они описывают деятельность разных фирм.
    Поскольку сравниваются пункты с одинаковыми номерами, то из результата следует исключить вариант, когда в одной таблице есть
    пункт с номером, совсем отсутствующим в другой таблице. Поскольку пункта с таким номером на одной из фирм не существует, то мы не можем сравнивать что-то с тем, чего нет.
    Например, если в забеге выступал один спортсмен, нельзя утверждать, что он был лучше второго.
    Совершенно другую ситуацию мы имеем, когда в определенный день в одной из таблиц строка для некоторого пункта отсутствует, но сам пункт существует (т.е. имеются соответствующие ему записи за другие дни). Такую ситуацию мы можем трактовать так, что пункт просто не работал или работал вхолостую. В этом случае победа аналогичному пункту другой фирмы присуждается закономерно. Возвращаясь к примеру о бегунах, мы можем утверждать, что спортсмен, пришедший к финишу первым был лучше того, который сошёл с дистанции.
    Если при проверке на сайте вашего решения вы получили сообщение о большем числе строк, которые вернул ваш запрос, то причина может заключаться как раз в сравнении с несуществующим пунктом.
    Упражнение (-2)
    Для каждой страны определить год, когда на воду было спущено максимальное количество ее кораблей. В случае, если окажется несколько таких лет, взять минимальный из них. Вывод: страна, количество кораблей, год
    Решение 3.6.1. Вот характерное решение для начинающих:
    1.
    SELECT
    country,
    MAX
    (
    x
    )
    ,
    MIN
    (
    launched
    )

    2.
    FROM
    (
    SELECT
    country,
    COUNT
    (
    *
    )
    x , launched
    3.
    FROM
    Ships b, Classes a
    4.
    WHERE
    a.class = b.class
    5.
    GROUP
    BY
    country, launched
    6.
    )
    s
    7.
    WHERE
    launched = ANY
    (
    SELECT
    MIN
    (
    launched
    )
    8.
    FROM
    Ships bb, Classes aa
    9.
    WHERE
    bb.class = aa.class
    10.
    GROUP
    BY
    country, launched
    11.
    )
    12.
    GROUP
    BY
    country;
    Подзапрос в предложении FROM определяет количество строк для каждой уникальной пары значений {страна, год спуска на воду}. На языке предметной области это означает, что определяется число кораблей, спущенных на воду каждой страной в каждом году. Пусть результатом выполнения подзапроса s будет следующая таблица:
    country
    x
    launched
    Gt.Britain 6 1916
    Gt.Britain 1 1917
    Japan
    1 1913
    Japan
    2 1914
    Japan
    2 1915
    Japan
    1 1916
    Japan
    1 1941
    Japan
    1 1942
    USA
    1 1920

    USA
    1 1921
    USA
    3 1941
    USA
    2 1943
    USA
    2 1944
    Далее (в предложении WHERE) отбираются только те строки, у которых год спуска на воду совпадает хотя бы с одним годом, определяемым следующим подзапросом:
    1.
    SELECT
    MIN
    (
    launched
    )
    2.
    FROM
    Ships bb, Classes aa
    3.
    WHERE
    bb.class = aa.class
    4.
    GROUP
    BY
    country, launched;
    Что же дает этот подзапрос? Все годы из каждой строки приведенной выше таблицы, так как группировка опять таки выполняется по стране и году. В результате используемый подзапрос никак не ограничивает выборку, и, следовательно, является ненужным.
    Видимо, подразумевалось удовлетворение условия задачи по минимальному году. Однако речь идет о минимальном годе из тех лет, когда на воду было спущено максимальное количество кораблей данной страны.
    Чтобы исправить эту ошибку, недостаточно убрать из группировки столбец launched:
    1.
    SELECT
    MIN
    (
    launched
    )
    2.
    FROM
    Ships aa, Classes bb
    3.
    WHERE
    bb.class = aa.class
    4.
    GROUP
    BY
    country; в результате чего будут получены минимальные годы по каждой стране:

    1916
    1913
    1920
    Тогда строки, удовлетворяющие предикату, будут выглядеть так:
    country
    x
    launched
    Gt.Britain 6 1916
    Japan
    1 1913
    Japan
    1 1916
    USA
    1 1920
    Таким образом, мы уже потеряли правильные строки для Японии и США.
    Следует обратить внимание еще и на то, что мы получили строку для Японии:
    Japan 1 1916 только на основании того, что год 1916 совпал с минимальным годом для
    США. Дальнейший код не имеет смысла. Однако и там имеется принципиальная ошибка. В основном запросе
    1.
    SELECT
    country,
    MAX
    (
    x
    )
    ,
    MIN
    (
    launched
    )
    2. …
    3.
    GROUP
    BY
    country выполняется группировка по стране с определением двух агрегатных показателей — максимума по количеству кораблей и минимума по году.

    Использование в данном случае группировки ошибочно. Действительно, нужные нам строки уже находятся в таблице, получаемой в предложении
    FROM (например, строка {Gt.Britain, 6, 1916}). Зачем же здесь группировка, когда требуется лишь критерий, который поставит фильтр, отсекающий лишние строки. В результате же группировки образуется одна строка, содержащая статистические показатели для всей группы. При этом максимум и минимум в общем случае достигаются в разных строках группы. Это хорошо видно из таблицы на примере кораблей США, когда минимальному году отвечает далеко не максимальное значение (x=1), а максимальное значение (x=3) достигается совсем в другом году (1941). Поэтому такая группировка может дать правильный результат (в смысле условия задачи) только в том случае, если все значения x для страны совпадают.
    Все в этом решении поставлено с ног на голову. Тем не менее, выяснив причины ошибок и заблуждений, попытаемся исправить его без радикальной переработки.
    Чтобы все же связать год со страной, можно использовать коррелирующий подзапрос в предложении WHERE (AND aa.country = s.country):
    1.
    WHERE
    launched = ANY
    (
    SELECT
    MIN
    (
    launched
    )
    2.
    FROM
    Ships bb, Classes aa
    3.
    WHERE
    aa.class = bb.class
    AND
    4. aa.country = s.country
    5.
    GROUP
    BY
    country
    6.
    )
    Это правильно, но пока ничего не меняет, разве что исключит неправильную строку:
    Japan 1 1916
    Чтобы двигаться дальше, нужно вычислять минимальный год среди лет с максимальным количеством кораблей для каждой страны. Здесь первичным является максимальное количество кораблей. Ведь выбирая лишь
    минимальный год, мы можем потерять правильные строки. Поэтому в предикате нужно оценивать не год, а количество кораблей:
    1.
    WHERE
    x >=
    ALL
    (
    SELECT
    COUNT
    (
    launched
    )
    2.
    FROM
    Ships bb, Classes aa
    3.
    WHERE
    bb.class = aa.class
    AND
    4. s.country=aa.country
    5.
    GROUP
    BY
    country, launched
    6.
    )
    Обратите внимание на предикат >=
    1   ...   7   8   9   10   11   12   13   14   ...   47


    написать администратору сайта