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

  • Сортировка и NULL- значения Если столбец, по которому выполняется сортировка, допускает NULL

  • Найти модели ПК, имеющих минимальную цену.

  • Агрегатная функция от агрегатной функции

  • Oracle

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


    Скачать 7.88 Mb.
    НазваниеУчебник по sql если вы хотите узнать, что такое sql этот сайт для вас
    Анкорлекция
    Дата21.12.2022
    Размер7.88 Mb.
    Формат файлаpdf
    Имя файлаMoiseenko_SQL.pdf
    ТипУчебник
    #857633
    страница17 из 47
    1   ...   13   14   15   16   17   18   19   20   ...   47
    Вывести номер модели и цену принтера, а также максимальную и
    минимальную цену на принтеры того же типа.
    Попытаемся адаптировать для этой задачи рассмотренные выше подходы.
    В решении 1 подзапросы следует сделать коррелирующими
    :

    Решение 1M
    1.
    SELECT
    model, price, type,
    2.
    (
    SELECT
    MIN
    (
    price
    )
    FROM
    Printer P1
    WHERE
    P1.type=P.type
    )
    min_price,
    3.
    (
    SELECT
    MAX
    (
    price
    )
    FROM
    Printer P1
    WHERE
    P1.type=P.type
    )
    max_price
    4.
    FROM
    printer P;
    В решении
    2 мы можем воспользоваться нестандартным соединением
    CROSS APPLY
    (SQL Server), использующим коррелирующий подзапрос в предложении FROM.
    Решение 2M
    1.
    SELECT
    model, price, P.type, min_price, max_price
    2.
    FROM
    Printer P
    CROSS
    APPLY
    3.
    (
    SELECT
    MIN
    (
    price
    )
    min_price,
    MAX
    (
    price
    )
    max_price
    4.
    FROM
    Printer P1 5.
    WHERE
    P1.type=P.type
    )
    X;
    Для наглядности в решения 1M и 2M добавлен столбец type.
    Сортировка
    и NULL-
    значения
    Если столбец, по которому выполняется сортировка, допускает NULL-значения, то при использовании
    SQL
    Server следует иметь в виду, что при сортировке по возрастанию NULL-значения будут идти в начале списка, а при сортировке по убыванию - в конце.
    Поскольку в доступных в учебнике базах NULL- значения отсутствуют в представленных данных (коль скоро они согласованы с данными в открытых базах, используемых на сайте sql-ex.ru), я создал копию
    таблицы PC с именем PC_, в которую добавил строку, содержащую NULL в столбце price:
    1.
    INSERT
    INTO
    PC_
    2.
    VALUES
    (
    13
    ,
    2112
    ,
    600
    ,
    64
    ,
    8
    ,
    '24x'
    ,
    NULL
    )
    ;
    Следует отметить, что это не противоречит схеме данных
    Теперь вы сами можете убедиться в сказанном, выполнив пару приведенных ниже запросов.
    1.
    SELECT
    *
    FROM
    PC_
    ORDER
    BY
    price;
    1.
    SELECT
    *
    FROM
    PC_
    ORDER
    BY
    price
    DESC
    ;
    Почему это важно? Дело в том, что при поиске экстремальных значений часто используют метод, основанный на сортировке. Рассмотрим, например, такую задачу.
    Найти модели ПК, имеющих минимальную цену.
    Иногда эту задачу решают следующим образом:
    1.
    SELECT
    TOP
    1
    WITH
    ties model
    2.
    FROM
    PC_
    3.
    ORDER
    BY
    price;
    Конструкция WITH TIES используется для того, чтобы вывести все модели с наименьшей ценой, если их окажется несколько. Однако в результате мы получим модель 2112, цена которой неизвестна, в то время как должны получить модели 1232 и 1260, имеющих действительно минимальные цены.
    Мы их и получим, если исключим из рассмотрения модели с неизвестными ценами:

    1.
    SELECT
    TOP
    1
    WITH
    ties model
    2.
    FROM
    PC_
    3.
    WHERE
    price
    IS
    NOT
    NULL
    4.
    ORDER
    BY
    price;
    Но тут появляется еще одна проблема, связанная с дубликатами. Поскольку есть два ПК модели 1232 с минимальной ценой, то обе они будут выводиться в результирующем наборе. DISTINCT без указания в списке столбцов предложения SELECT тех, по которым выполняется сортировка, использовать мы не можем, о чем и сообщает ошибка, если мы попытаемся это сделать
    1.
    SELECT
    DISTINCT
    TOP
    1
    WITH
    ties model
    2.
    FROM
    PC_
    3.
    WHERE
    price
    IS
    NOT
    NULL
    4.
    ORDER
    BY
    price;
    (ORDER BY items must appear in the select list if SELECT DISTINCT is specified.)
    Чтобы получить решение в требуемом виде, мы можем добавить price в список выводимых столбцов, а потом использовать полученный запрос в качестве подзапроса. Итак,
    1.
    SELECT
    model
    FROM
    (
    2.
    SELECT
    DISTINCT
    TOP
    1
    WITH
    ties model, price
    3.
    FROM
    PC_
    4.
    WHERE
    price
    IS
    NOT
    NULL
    5.
    ORDER
    BY
    price
    6.
    )
    X;
    Примечание:
    При использовании
    агрегатных функций
    проблемы с NULL-значениями
    не возникает, т.к. они автоматически исключаются из рассмотрения.
    Хотя при этом тоже придется использовать подзапрос:

    1.
    SELECT
    DISTINCT
    model
    FROM
    PC_
    2.
    WHERE
    price =
    (
    SELECT
    MIN
    (
    price
    )
    FROM
    PC_
    )
    ;
    Заметим также, что это стандартное решение будет работать под любыми
    СУБД, т.к. не использует специфических особенностей диалекта.
    А как, кстати, обстоят дела с использованием метода на основе сортировки в других СУБД?
    В MySQL мы можем использовать DISTINCT без обязательного указания в списке SELECT столбцов, по которым выполняется сортировка. Однако здесь нет аналога конструкции
    WITH TIES
    , чтобы решить задачу максимально просто.
    Поэтому в методе, основанном на сортировке, нам придется использовать подзапрос, чтобы вывести все модели с минимальной ценой:
    1.
    SELECT
    DISTINCT
    model
    FROM
    PC_
    2.
    WHERE
    price =
    (
    SELECT
    price
    FROM
    PC_
    WHERE
    price
    IS
    NOT
    NULL
    ORDER
    BY
    price
    LIMIT
    1
    )
    ;
    Такое же решение будет работать и в PostgreSQL, однако он имеет одну особенность, о которой полезно знать. А именно, при сортировке можно указать, где будут выводиться NULL-значения - в начале или в конце результирующего набора. Нам для решения задачи требуется, чтобы NULL выводились в конце отсортированного списка. Тогда не придется выполнять лишнюю операцию по отфильтровыванию NULL-значений:
    1.
    SELECT
    DISTINCT
    model
    FROM
    PC_
    2.
    WHERE
    price =
    (
    SELECT
    price
    FROM
    PC_
    ORDER
    BY
    price nulls last
    LIMIT
    1
    )
    ;

    Кстати, при сортировке по возрастанию NULL-значения в PostgreSQL идут в конце результирующего рабора. Поэтому конструкция NULLS LAST, которую мы использовали выше, можно опустить при решении нашей задачи:
    1.
    SELECT
    DISTINCT
    model
    FROM
    PC_
    2.
    WHERE
    price =
    (
    SELECT
    price
    FROM
    PC_
    ORDER
    BY
    price
    LIMIT
    1
    )
    ;
    Для того чтобы NULL-значения шли в начале результирующего набора при выполнении сортировки, нужно написать NULLS FIRST.
    К слову, мы можем смоделировать в MySQL использование конструкций
    NULLS FIRST/LAST. Для этого воспользуемся тем фактом, что значения логического типа в этой СУБД представляют собой TINYINT(1). Конкретно это означает, что 0 соответствует истинностному значению FALSE (ложь), а ненулевое значение эквивалентно TRUE (истина). При этом логическое выражение, оцениваемое как TRUE будет представлено единицей, т.е.
    1.
    SELECT
    a
    IS
    NULL
    AS
    a, b
    IS
    NULL
    AS
    b
    FROM
    (
    SELECT
    NULL
    AS
    a,
    1
    AS
    b
    )
    x; даст нам
    a
    b
    1
    0
    Учитывая то, что 0 при сортировке по возрастанию идет раньше, чем 1, мы можем решение для PostgreSQL адаптировать для MySQL:
    1.
    SELECT
    DISTINCT
    model
    FROM
    PC_
    2.
    WHERE
    price =
    (
    SELECT
    price
    FROM
    PC_
    ORDER
    BY
    price
    IS
    NULL
    , price
    LIMIT
    1
    )
    ;

    Oracle, как и PostgreSQL, при сортировке по возрастанию помещает NULL- значения в конец результирующего набора. Здесь также имеют место конструкции NULLS FIRST/LAST, но отсутствует аналог LIMIT/TOP N для ограничения количества выводимых строк.
    Чтобы смоделировать в Oracle использованный выше подход к решению задачи, можно воспользоваться встроенной функцией ROWNUM. Эта функция нумерует строки, но делает это она после выполнения предложений
    FROM и WHERE, т.е. перед предложениями SELECT и ORDER BY. Такое поведение иллюстрирует результат следующего запроса:
    1.
    SELECT
    code, model,price, ROWNUM rn
    FROM
    PC_
    ORDER
    BY
    price;
    CODE
    MODEL
    PRICE
    RN
    10
    1260 350 10
    9
    1232 350 9
    8
    1232 350 8
    7
    1232 400 7
    3
    1233 600 3
    1
    1232 600 1
    5
    1121 850 5
    2
    1121 850 2
    4
    1121 850 4
    6
    1233 950 6
    12
    1233 970 12
    11
    1233 980 11

    13
    2112
    NULL
    13
    Как видно, номер строки не соответствует порядку сортировки. Нетрудно убедиться в том, что нумерация выполнена в соответствии со столбцом code.
    Это объясняется тем, что оптимизатор использует индекс по этому столбцу при выполнении запроса.
    Итак, чтобы найти минимальную цену на основе сортировки, придется использовать подзапрос:
    1.
    SELECT
    price
    FROM
    (
    2.
    SELECT
    model,price
    FROM
    PC_
    ORDER
    BY
    price
    3.
    )
    X
    4.
    WHERE
    ROWNUM =
    1
    ;
    Теперь, как и в случае MySQL и PostgreSQL, будем использовать этот запрос для получения моделей, которые продаются по цене, найденной с его помощью:
    1.
    SELECT
    DISTINCT
    model
    FROM
    PC_
    WHERE
    price =
    2.
    (
    SELECT
    price
    FROM
    (
    3.
    SELECT
    model,price
    FROM
    PC_
    ORDER
    BY
    price
    4.
    )
    X
    5.
    WHERE
    ROWNUM =
    1 6.
    )
    ;
    Как говорил Соломон, от многой мудрости много скорби, и умножающий знание умножает печаль.
    Используйте стандартные решения, сказал бы я. :-)
    В заключение не могу не сказать о способе, использующем ранжирующие функции

    Идея решения cостоит в ранжировании (функция RANK) строк по возрастанию цены и выборке (уникальных) строк, для которых ранг равен 1.
    Чтобы запрос работал под всеми СУБД, которые поддерживают оконные функции, этот алгоритм можно записать следующим образом:
    1.
    SELECT
    DISTINCT
    model
    FROM
    (
    2.
    SELECT
    model, Rank
    ()
    OVER
    (
    ORDER
    BY
    price
    )
    rn
    FROM
    PC_
    3.
    WHERE
    price
    IS
    NOT
    NULL
    4.
    )
    X
    WHERE
    rn =
    1
    ;
    Тот факт, что при сортировке по возрастанию NULL-значения идут в начале
    (SQL Server) можно использовать в "полезных целях".
    Пусть нам требуется вывести список рейсов, в котором рейсы из Ростова должны идти первыми, а затем остальные в алфавитном порядке города отправления.
    Здесь весьма кстати пригодится функция
    NULLIF(town_from,'Rostov')
    , которая будет возвращать NULL, если городом отправления является 'Rostov'.
    Задачу решает следующий запрос:
    1.
    SELECT
    trip_no, town_from, town_to
    2.
    FROM
    Trip
    3.
    ORDER
    BY
    NULLIF
    (
    town_from,
    'Rostov'
    )
    , trip_no;
    Агрегатная функция
    от агрегатной
    функции
    Давайте рассмотрим такую задачу:
    Найти максимальное значение
    среди средних цен ПК, посчитанных
    для каждого производителя
    отдельно.

    Посчитать средние значения стоимости по производителям труда не составляет:
    1.
    SELECT
    AVG
    (
    price
    )
    avg_price
    2.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model = PC.model
    3.
    GROUP
    BY
    maker;
    Однако стандарт запрещает использовать подзапрос в качестве аргумента агрегатной функции, т.е. нельзя решить задачу следующим способом:
    1.
    SELECT
    MAX
    (
    2.
    SELECT
    AVG
    (
    price
    )
    avg_price
    3.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model = PC.model
    4.
    GROUP
    BY
    maker
    5.
    )
    ;
    В подобных случаях используется подзапрос в предложении FROM:
    1.
    SELECT
    MAX
    (
    avg_price
    )
    2.
    FROM
    (
    SELECT
    AVG
    (
    price
    )
    avg_price
    3.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model = PC.model
    4.
    GROUP
    BY
    maker
    5.
    )
    X;
    С помощью новых возможностей языка – оконных функций
    - эту задачу можно решить без подзапроса:
    1.
    SELECT
    DISTINCT
    MAX
    (
    AVG
    (
    price
    ))
    OVER
    ()
    max_avg_price
    2.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model = PC.model
    3.
    GROUP
    BY
    maker;

    Обратите внимание, что оконные функции допускают использование агрегатной функции в качестве аргумента.
    Ключевое слово DISTINCT необходимо здесь, поскольку максимальное значение, подсчитанное по всему набору средних значений, будет «приписано» каждому производителю.
    Стандарт также запрещает использовать агрегатную функцию как аргумент другой агрегатной функции. Т.е. мы не можем решить нашу задачу следующим образом:
    1.
    SELECT
    MAX
    (
    AVG
    (
    price
    ))
    max_avg_price
    2.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model = PC.model
    3.
    GROUP
    BY
    maker;
    Но не бывает правил без исключений. Как ни странно, но в Oracle подобные конструкции работают, и вышеприведенный запрос даст результат:
    MAX_AVG_PRICE
    850
    Чтобы убедиться в этом, зайдите на страницу задач обучающего этапа на сайте sql-ex.ru, выберите Oracle в списке СУБД и выполните запрос с флажком "Без проверки".
    Кстати говоря, решение с использованием оконной функции также будет работать в Oracle. Могу предположить, что решение без оконной функции фактически её и использует, неявно подразумевая предложение OVER().
    Наверняка, вам встретятся решения подобных задач на основе сортировки с ограничением на число строк результирующего набора. Однако такие решения не являются легитимными с точки зрения стандарта языка и, как следствие, имеют различный синтаксис в разных реализациях. В качестве примера приведу решения нашей задачи в диалектах
    SQL
    Server и MySQL.

    SQL Server
    1.
    SELECT
    TOP
    1
    AVG
    (
    price
    )
    avg_price
    2.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model = PC.model
    3.
    GROUP
    BY
    maker
    4.
    ORDER
    BY
    avg_price
    DESC
    ;
    MySQL
    1.
    SELECT
    AVG
    (
    price
    )
    avg_price
    2.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model = PC.model
    3.
    GROUP
    BY
    maker
    4.
    ORDER
    BY
    avg_price
    DESC
    5.
    LIMIT
    1
    ;
    Оба этих решения берут только первую строку из отсортированного по убыванию набора средних цен.
    У начинающих изучать SQL зачастую вызывает проблему определение производителя, для которого достигается искомый максимум/минимум.
    Другими словами, требуется найти максимальную среднюю цену и производителя, средняя цена ПК которого совпадает с этой максимальной средней ценой.
    Нестандартными средствами эта задача решается фактически рассмотренным выше запросом:
    1.
    SELECT
    TOP
    1
    maker,
    AVG
    (
    price
    )
    avg_price
    2.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model = PC.model
    3.
    GROUP
    BY
    maker
    4.
    ORDER
    BY
    avg_price
    DESC
    ;
    Использование maker в списке столбцов предложения SELECT вполне допустимо, т.к. по этому столбцу выполняется группировка. Однако тут имеется одна «ловушка». Она связана с тем, что максимум может достигаться для нескольких производителей, и в данной постановке задачи их нужно выводить всех, в то время как мы ограничиваем выборку только одной

    (первой) строкой. На этот случай диалект
    T-SQL
    имеет дополнительную конструкцию
    WITH TIES
    . Логически правильное решение будет иметь вид:
    1.
    SELECT
    TOP
    1
    WITH
    TIES maker,
    AVG
    (
    price
    )
    avg_price
    2.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model = PC.model
    3.
    GROUP
    BY
    maker
    4.
    ORDER
    BY
    avg_price
    DESC
    ;
    Однако, если иметь в виду проблему переносимости кода, то следует предпочесть решение, использующее стандартные средства.
    Примечание:
    На сайте SQL-EX.RU проблема переносимости кода возникла в связи с
    нашим намерением реализовать упражнения для различных СУБД.
    Реализация потребовала бы минимальных средств, если бы тестовые
    решения, используемые для проверки, работали бы на всех предполагаемых
    СУБД без изменения своего кода. Поэтому следование стандарту может
    являться одним из требований тех. задания на проект.
    Приведем ниже несколько стандартных решений рассматриваемой задачи.
    1. Использование предиката ALL
    в предложении WHERE
    1.
    SELECT
    maker, avg_price
    2.
    FROM
    (
    SELECT
    maker,
    AVG
    (
    price
    )
    avg_price
    3.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model=PC.model
    4.
    GROUP
    BY
    maker
    5.
    )
    X
    6.
    WHERE
    avg_price >=
    ALL
    (
    SELECT
    AVG
    (
    price
    )
    avg_price
    7.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model=PC.model
    8.
    GROUP
    BY
    maker
    9.
    )
    ;

    На естественном языке этот запрос звучит следующим образом: «Найти производителей, средняя цена на ПК у которых не меньше, чем средние цены у КАЖДОГО из производителей ПК».
    2. Использование внутреннего соединения
    1.
    SELECT
    maker, avg_price
    2.
    FROM
    (
    SELECT
    maker,
    AVG
    (
    price
    )
    avg_price
    3.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model=PC.model
    4.
    GROUP
    BY
    maker
    5.
    )
    X
    JOIN
    6.
    (
    SELECT
    MAX
    (
    avg_price
    )
    max_price
    7.
    FROM
    (
    SELECT
    maker,
    AVG
    (
    price
    )
    avg_price
    8.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model=PC.model
    9.
    GROUP
    BY
    maker
    10.
    )
    X
    11.
    )
    Y
    ON
    avg_price = max_price;
    Здесь мы соединяем подзапрос, определяющий производителей и средние цены на их ПК, с подзапросом, в котором определяется максимальная средняя цена. Соединение выполняется по условию равенства средней цены из первого подзапроса с максимальной ценой из второго.
    3. Использование предиката ALL в предложении HAVING
    1.
    SELECT
    maker,
    AVG
    (
    price
    )
    avg_price
    2.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model=PC.model
    3.
    GROUP
    BY
    maker
    4.
    HAVING
    AVG
    (
    price
    )
    >=
    ALL
    (
    SELECT
    AVG
    (
    price
    )
    5.
    FROM
    Product P
    JOIN
    PC
    ON
    P.model=PC.model
    6.
    GROUP
    BY
    maker
    7.
    )
    ;
    Это решение отличается от первого варианта отсутствием «лишнего» запроса, который пришлось написать лишь затем, чтобы была возможность использовать алиас avg_price в предложении WHERE (смотри порядок обработки предложений оператора SELECT
    ); с другой стороны,
    использование в предложении WHERE агрегатной функции также запрещено правилами языка.
    Все приведенные стандартные решения выглядят тяжеловесными, хотя и будут работать практически во всех СУБД. Эта громоздкость объясняется повторением в коде фактически одного и того же запроса. Однако общие табличные выражения – CTE
    , которые были введены в последних версиях стандарта, позволяют многократно ссылаться на один раз сформулированный запрос. Например, решения 1, 3 с помощью
    1   ...   13   14   15   16   17   18   19   20   ...   47


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