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

  • Оператор PIVOT

  • Оператор UNPIVOT

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


    Скачать 7.88 Mb.
    НазваниеУчебник по sql если вы хотите узнать, что такое sql этот сайт для вас
    Анкорлекция
    Дата21.12.2022
    Размер7.88 Mb.
    Формат файлаpdf
    Имя файлаMoiseenko_SQL.pdf
    ТипУчебник
    #857633
    страница34 из 47
    1   ...   30   31   32   33   34   35   36   37   ...   47
    Функция
    CONCAT
    Для конкатенации
    строк в
    SQL
    Server используется оператор "+".
    Т.е. если операнды являются числовыми, то выполняется операция сложения, а если – строковыми, то конкатенация:
    1.
    SELECT
    1+2+3+4
    a,
    '1'
    +
    '2'
    +
    '3'
    +
    '4'
    b;
    a
    b
    10 1234
    Если же операнды являются значениями разных типов, то SQL Server выполняет неявное преобразование типов. Выполняя следующий запрос
    1.
    SELECT
    hd +
    ' Gb'
    volume
    FROM
    PC
    WHERE
    model =
    1232
    ; мы получим сообщение об ошибке:
    Error converting data type varchar to real.(Ошибка при преобразовании типа
    данных varchar к real.)
    Существует приоритет типов при их неявном преобразовании, и в соответствии с этим приоритетом сервер пытается преобразовать строку ' Gb' к типу данных столбца hd (real).
    Разумеется, явное преобразование типа решает проблему:
    1.
    SELECT
    CAST
    (
    hd
    AS
    VARCHAR
    )
    +
    ' Gb'
    volume
    FROM
    PC
    WHERE
    model =
    1232
    ;
    volume
    5 Gb

    10 Gb
    8 Gb
    10 Gb
    В SQL Server 2012 появилась функция CONCAT, которая выполняет конкатенацию, неявно преобразуя типы аргументов к строковому типу данных. С помощью этой функции предыдущий запрос можно переписать так:
    1.
    SELECT
    CONCAT
    (
    hd,
    ' Gb'
    )
    volume
    FROM
    PC
    WHERE
    model=
    1232
    ;
    Еще одна полезная особенность функции CONCAT состоит в том, что NULL-значения неявно преобразуются в пустую строку- ''. Обычная же конкатенация с NULL-значением дает NULL. Вот пример, который это демонстрирует.
    1.
    SELECT
    NULL
    +
    'concatenation with NULL'
    plus,
    2. CONCAT
    (
    NULL
    ,
    'concatenation with NULL'
    )
    concat;
    plus
    concat
    NULL concatenation with NULL
    Следует отметить, что у функции CONCAT может быть произвольное число аргументов, но не менее двух.
    1.
    SELECT
    1+2+3+4
    a, CONCAT
    (
    1
    ,
    2
    ,
    3
    ,
    4
    )
    b;
    a
    b
    10 1234

    MySQL
    В MySQL также имеется функция CONCAT, вернее, даже две функции.
    Первая из них – CONCAT – возвращает NULL, если среди аргументов функции встречается NULL, вторая – CONCAT_WS – опускает аргумент, если его значение NULL. Кроме того, эта функция первым аргументом имеет разделитель, используемый при конкатенации.
    1.
    SELECT
    CONCAT
    (
    NULL
    ,
    'concatenation with NULL'
    )
    concat,
    2. CONCAT_WS
    (
    ''
    ,
    NULL
    ,
    'concatenation with NULL'
    )
    concat_ws,
    3. CONCAT_WS
    (
    ', '
    ,
    1
    ,
    2
    ,
    NULL
    ,
    4
    )
    concat_ws_null;
    concat
    concat_ws
    concat_ws_null
    (NULL)
    concatenation with NULL 1, 2, 4
    Операторы
    PIVOT и
    UNPIVOT
    Чтобы объяснить, что такое PIVOT, я бы начал с электронных таблиц EXCEL. В версии MS Excel 5.0 появились так называемые сводные таблицы. Сводные таблицы представляют собой двумерную визуализацию многомерных структур данных, применяемых в технологии OLAP для построения хранилищ данных.
    Правильней даже сказать двумерные сечения трехмерных
    OLAP-кубов, если иметь в виду наличие на сводной таблице элемента, который называется «страница».
    Сводные таблицы позволяют выполнять стандартные операции с многомерными структурами, например, упоминавшееся уже сечение куба, свертку и детализацию
    – операцию обратную свертке.
    Следует сказать, что сводная таблица не является реляционной, поскольку имеет не только заголовки столбцов, но и заголовки строк, при этом и те и другие
    формируются из данных, находящихся в столбцах обычных реляционных таблиц. Последнее, кстати, означает, что число строк и столбцов заранее неизвестно, т.к. они формируются динамически при выполнении запроса к реляционным данным. Кроме того, заголовки могут иметь многоуровневые подзаголовки, что и позволяет выполнять операции свертки (переход на более высокий уровень иерархии) и детализации (переход на более низкий уровень иерархии).
    Такие свойства сводных таблиц позволяют их использовать, наряду со сводными диаграммами, в качестве клиента для визуального отображения многомерных данных, находящихся в хранилищах, поддерживаемых различными
    СУБД
    (например,
    MS
    SQL
    Server Analysis Services).
    Чтобы пояснить сказанное примером, давайте рассмотрим такой запрос к одной из учебных баз на sql- ex.ru:
    1.
    SELECT
    maker, type
    2.
    FROM
    product; результатом которого является такая таблица:
    maker
    type
    B
    PC
    A
    PC
    A
    PC
    E
    PC
    A
    Printer
    D
    Printer

    A
    Laptop
    C
    Laptop
    A
    Printer
    A
    Printer
    D
    Printer
    E
    Printer
    B
    Laptop
    A
    Laptop
    E
    PC
    E
    PC
    Пусть теперь нам требуется получить таблицу со следующими заголовками:
    Типы продукции
    П р о и з в о д и т е л и
    Laptop PC Printer
    A
    B
    C
    D
    E

    Заголовками строк здесь являются уникальные имена производителей, которые берутся из столбца maker вышеприведенного запроса, а заголовками столбцов – уникальные типы продукции (соответственно, из столбца type). А что должно быть в середине? Ответ очевиден – некоторый агрегат, например, функция count(type), которая подсчитает для каждого производителя отдельно число моделей ПК, ноутбуков и принтеров, которые и заполнят соответствующие ячейки этой таблицы.
    Это простейший вариант сводной таблицы, который имеет всего два уровня иерархии по столбцам и строкам. Т.е. выполняя свертку по вертикали, мы получаем количество моделей каждого вида продукции для всех производителей, а по горизонтали – общее число моделей независимо от типа для каждого производителя. Можно было бы добавить дополнительные уровни иерархии, например, для градации принтеров по цветности, a ноутбуков по размеру экрана и т.д.
    Можно сказать, что pivot-таблица в SQL – это одноуровневая сводная таблица.
    Оператор PIVOT не является стандартным (я не уверен, что он когда-нибудь будет стандартизован ввиду нереляционной природы pivot-таблицы), поэтому я буду использовать в примерах его реализацию в языке
    T-SQL
    (SQL Server
    2005/2008).
    Я могу и ошибиться в хронологии, но мне представляется, что успех реализации сводной таблицы в Excel привел к появлению так называемых перекрестных запросов в Access, и, наконец, к оператору PIVOT в T-SQL.
    Особенности: ORACLE
    В Oracle операции PIVOT и DRILLDOWN (UNPIVOT) были отданы на откуп средствам визуализации OLAP, т.е. MS Excel, BusinessObjects и пр.
    В Oracle 11g появились pivot/unpivot, но в pivot есть "тонкость" - нужно
    ЯВНО указывать столбцы, если результат нужен в ТАБЛИЧНОМ виде.
    Получение данных для столбцов, не указанных явно, также возможно, но только в виде XML.
    Оператор
    PIVOT

    Давайте рассмотрим такую задачу.
    Пример 1.
    Для каждого производителя из таблицы Product
    определить число моделей каждого типа продукции.
    Задачу можно решить стандартными средствами с использованием оператора CASE:
    1.
    SELECT
    maker,
    2.
    SUM
    (
    CASE
    type
    WHEN
    'pc'
    THEN
    1
    ELSE
    0
    END
    )
    PC
    3. ,
    SUM
    (
    CASE
    type
    WHEN
    'laptop'
    THEN
    1
    ELSE
    0
    END
    )
    Laptop
    4. ,
    SUM
    (
    CASE
    type
    WHEN
    'printer'
    THEN
    1
    ELSE
    0
    END
    )
    Printer
    5.
    FROM
    Product
    6.
    GROUP
    BY
    maker;
    Теперь решение через PIVOT:
    1.
    SELECT
    maker,
    -- столбец (столбцы), значения из которого
    формируют заголовки строк
    2.
    [
    pc
    ]
    ,
    [
    laptop
    ]
    ,
    [
    printer
    ]
    -- значения из столбца, который
    указан в предложении type,
    3.
    -- формирующие заголовки столбцов
    4.
    FROM
    Product
    -- здесь может быть подзапрос
    5.
    PIVOT
    -- формирование пивот-таблицы
    6.
    (
    COUNT
    (
    model
    )
    --
    агрегатная
    функция,
    формирующая
    содержимое сводной таблицы
    7.
    FOR
    type
    -- указывается столбец,
    8.
    -- уникальные значения в котором будут являться
    заголовками столбцов
    9.
    IN
    ([
    pc
    ]
    ,
    [
    laptop
    ]
    ,
    [
    printer
    ])
    --указываются конкретные
    значения в столбце type,
    10.
    -- которые следует использовать в качестве
    заголовков,
    11.
    -- т.к. нам могут потребоваться не все
    12.
    )
    pvt ;
    -- алиас для сводной таблицы
    Надеюсь, что комментарии к коду достаточно понятны для того, чтобы написать оператор PIVOT без шпаргалки. Давайте попробуем.

    Пример 2.
    Посчитать среднюю цену на ноутбуки в зависимости от размера
    экрана.
    Задача элементарная и решается с помощью группировки:
    1.
    SELECT
    screen,
    AVG
    (
    price
    )
    avg_
    2.
    FROM
    Laptop
    3.
    GROUP
    BY
    screen;
    screen
    avg_
    11
    700.00
    12
    960.00
    14
    1175.00
    15
    1050.00
    А вот как можно повернуть эту таблицу с помощью PIVOT:
    1.
    SELECT
    [
    avg_
    ]
    ,
    2.
    [
    11
    ]
    ,
    [
    12
    ]
    ,
    [
    14
    ]
    ,
    [
    15
    ]
    3.
    FROM
    (
    SELECT
    'average price'
    AS
    'avg_'
    , screen, price
    FROM
    Laptop
    )
    x
    4.
    PIVOT
    5.
    (
    AVG
    (
    price
    )
    6.
    FOR
    screen
    7.
    IN
    ([
    11
    ]
    ,
    [
    12
    ]
    ,
    [
    14
    ]
    ,
    [
    15
    ])
    8.
    )
    pvt;
    avg_
    11
    12
    14
    15
    average price 700.00 960.00 1175.00 1050.00

    В отличие от сводных таблиц, в операторе PIVOT требуется явно перечислить столбцы для вывода. Это серьезное ограничение, т.к. для этого нужно знать характер данных, а значит и применять в приложениях этот оператор мы сможем, как правило, только к справочникам (вернее, к данным, которые берутся из справочников).
    Если рассмотренных примеров покажется недостаточно, чтобы понять и использовать без затруднений этот оператор, я вернусь к нему, когда придумаю нетривиальные примеры, где использование оператора PIVOT позволяет существенно упростить код.
    Я написал этот опус в помощь тем, кому оператор PIVOT интуитивно непонятен. Могу согласиться с тем, что в реляционном языке
    SQL
    он выглядит инородным телом. Собственно, иначе и быть не может ввиду того, что поворот
    (транспонирование) таблицы является не реляционной операцией, а операцией работы с многомерными структурами данных.
    Оператор
    UNPIVOT
    Как следует из названия оператора, UNPIVOT выполняет обратную по отношению к PIVOT операцию, т.е. представляет данные, записанные в строке таблицы, в одном столбце. В примере, рассмотренном в предыдущем параграфе
    , мы с помощью оператора PIVOT разворачивали в строку таблицу, полученную с помощью следующего запроса:
    1.
    SELECT
    screen,
    AVG
    (
    price
    )
    avg_
    2.
    FROM
    Laptop
    3.
    GROUP
    BY
    screen;
    screen
    avg_
    11
    700.00
    12
    960.00
    14
    1175.00

    15
    1050.00
    В результате было получено следующее представление:
    avg_
    11
    12
    14
    15
    average price 700.00 960.00 1175.00 1050.00
    Исходный результат мы можем получить, если применим к pivot-запросу unpivot-преобразование:
    1.
    SELECT
    screen
    -- заголовок столбца, который будет
    содержать заголовки
    2.
    -- строк исходной таблицы
    3. ,avg__
    AS
    avg_
    4.
    -- заголовок столбца, который будет содержать значения из
    строки исходной таблицы
    5.
    FROM
    (
    -- pivot-запрос из предыдущего примера
    6.
    SELECT
    [
    avg_
    ]
    ,
    [
    11
    ]
    ,
    [
    12
    ]
    ,
    [
    14
    ]
    ,
    [
    15
    ]
    7.
    FROM
    (
    SELECT
    'average price'
    AS
    'avg_'
    , screen, price
    8.
    FROM
    Laptop
    )
    x
    9.
    PIVOT
    (
    AVG
    (
    price
    )
    FOR
    screen
    IN
    ([
    11
    ]
    ,
    [
    12
    ]
    ,
    [
    14
    ]
    ,
    [
    15
    ])
    )
    10. pvt
    11.
    -- конец pivot-запроса
    12.
    )
    pvt
    13.
    UNPIVOT
    (
    avg__
    -- заголовок столбца, который будет
    содержать значения
    14.
    -- из столбцов исходной таблицы, перечисленных ниже
    15.
    FOR
    screen
    IN
    ([
    11
    ]
    ,
    [
    12
    ]
    ,
    [
    14
    ]
    ,
    [
    15
    ])
    16.
    )
    unpvt;
    Заметим, что имя avg_ нельзя использовать в операторе UNPIVOT, поскольку оно уже использовалось в операторе PIVOT, поэтому я использовал новое имя avg__, которому затем присвоил алиас, чтобы полностью воссоздать результат, полученный с помощью группировки.

    Рассмотрим теперь более содержательный пример. Пусть требуется информацию о рейсе 1100 представить в следующем виде:
    trip_no
    spec
    info
    1100
    id_comp
    4
    1100
    plane
    Boeing
    1100
    town_from Rostov
    1100
    town_to
    Paris
    1100
    time_out
    14:30:00
    1100
    time_in
    17:50:00
    Поскольку информация из строки таблицы трансформируется в столбец, то напрашивается использование оператора UNPIVOT. Здесь следует сделать одно замечание. Значения в этом столбце должны быть одного типа.
    Поскольку в этот столбец в нашем примере собираются значения из разных столбцов исходной таблицы, то нужно преобразовать их к единому типу.
    Более того, должны совпадать не только типы, но и размер.
    Общим типом в нашем случае является строковый тип. Поскольку столбцы town_from и town_to уже имеют тип char(25), то приведем все к этому типу:
    1.
    SELECT
    trip_no,
    CAST
    (
    id_comp
    AS
    CHAR
    (
    25
    ))
    id_comp,
    2.
    CAST
    (
    plane
    AS
    CHAR
    (
    25
    ))
    plane,town_from,town_to,
    3.
    CONVERT
    (
    CHAR
    (
    25
    )
    ,time_out,
    108
    )
    time_out,
    4.
    CONVERT
    (
    CHAR
    (
    25
    )
    ,time_in,
    108
    )
    time_in
    5.
    FROM
    Trip
    WHERE
    trip_no =
    1100
    ;

    trip_
    no
    id_co
    mp
    pla
    ne
    town_fr
    om
    town
    _to
    time_
    out
    time
    _in
    1100
    4
    Boei ng
    Rostov
    Paris
    14:30:0 0
    17:50:
    00
    Здесь мы заодно преобразовали время вылета/прилета, убрав из него составляющую даты:
    1.
    CONVERT
    (
    CHAR
    (
    25
    )
    ,time_out,
    108
    )
    Остальное, я надеюсь, понятно из кода:
    1.
    SELECT
    trip_no, spec, info
    FROM
    (
    2.
    SELECT
    trip_no,
    CAST
    (
    id_comp
    AS
    CHAR
    (
    25
    ))
    id_comp,
    3.
    CAST
    (
    plane
    AS
    CHAR
    (
    25
    ))
    plane,
    4.
    CAST
    (
    town_from
    AS
    CHAR
    (
    25
    ))
    town_from,
    5.
    CAST
    (
    town_to
    AS
    CHAR
    (
    25
    ))
    town_to,
    6.
    CONVERT
    (
    CHAR
    (
    25
    )
    ,time_out,
    108
    )
    time_out,
    7.
    CONVERT
    (
    CHAR
    (
    25
    )
    ,time_in,
    108
    )
    time_in
    8.
    FROM
    Trip
    9.
    WHERE
    trip_no =
    1100
    )
    x
    10.
    UNPIVOT
    (
    info
    11.
    FOR
    spec
    IN
    (
    id_comp, plane, town_from, town_to, time_out, time_in
    )
    12.
    )
    unpvt;
    Столбец с именем spec используется для вывода названий параметров, а столбец info содержит сами параметры. Результат выполнения запроса уже был представлен в условии задачи.
    Пусть нам требуется повернуть строку, содержащую NULL-значение в одном из столбцов.

    1.
    WITH
    utest
    AS
    2.
    (
    SELECT
    1
    a,
    2
    b,
    NULL
    c
    )
    3.
    SELECT
    *
    FROM
    utest;
    Т.е. вместо результата
    a
    b
    c
    1
    2
    NULL мы хотим получить
    a 1
    b 2
    c NULL
    Применим оператор UNPIVOT:
    1.
    WITH
    utest
    AS
    2.
    (
    SELECT
    1
    a,
    2
    b,
    NULL
    c
    )
    3.
    SELECT
    col, value
    FROM
    utest
    4.
    UNPIVOT
    (
    5. value
    FOR
    col
    IN
    (
    a,b,c
    )
    6.
    )
    AS
    unpvt;
    Первая неожиданность - ошибка компиляции:
    Тип столбца "c" конфликтует с типами других столбцов, указанных в списке
    UNPIVOT.
    Это означает, что сервер неявно не преобразовал тип столбца "с", содержащий NULL, к типу первых двух столбцов, которые могут быть оценены как целочисленные.

    Давайте сделаем это явно:
    1.
    WITH
    utest
    AS
    2.
    (
    SELECT
    1
    a,
    2
    b,
    CAST
    (
    NULL
    AS
    INT
    )
    c
    )
    3.
    SELECT
    col,value
    FROM
    utest
    4.
    UNPIVOT
    (
    5. value
    FOR
    col
    IN
    (
    a,b,c
    )
    6.
    )
    AS
    unpvt;
    col
    value
    a
    1
    b
    2
    Вторая неожиданность - оказывается UNPIVOT игнорирует NULL-значения, не выводя их в результирующем наборе.
    Первое, что приходит в голову всем, это заменить NULL каким-нибудь валидным значением, заведомо отсутствующим в столбце. Если, в соответствии с ограничениями предметной области, значения с не могут быть отрицательными, заменим NULL на -1:
    1.
    WITH
    utest
    AS
    2.
    (
    SELECT
    1
    a,
    2
    b, COALESCE
    (
    CAST
    (
    NULL
    AS
    INT
    )
    ,
    -1
    )
    c
    )
    3.
    SELECT
    col, value
    FROM
    utest
    4.
    UNPIVOT
    (
    5. value
    FOR
    col
    IN
    (
    a, b, c
    )
    6.
    )
    AS
    unpvt;
    1   ...   30   31   32   33   34   35   36   37   ...   47


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