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

  • Функция EOMONTH

  • CHOOSE Нестандартная функция CHOOSE

  • Заметки о типах данных

  • CHAR и VARCHAR

  • CHAR/VARCHAR

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


    Скачать 7.88 Mb.
    НазваниеУчебник по sql если вы хотите узнать, что такое sql этот сайт для вас
    Анкорлекция
    Дата21.12.2022
    Размер7.88 Mb.
    Формат файлаpdf
    Имя файлаMoiseenko_SQL.pdf
    ТипУчебник
    #857633
    страница37 из 47
    1   ...   33   34   35   36   37   38   39   40   ...   47
    iteration
    Exact
    Res1
    Res2
    1
    1.7320508075688772 3.0 3.0
    2
    1.7320508075688772 2.0 1.999992
    3
    1.7320508075688772 1.75 1.7499920000160001
    4
    1.7320508075688772 1.7321428571428572 1.7321358469571777
    5
    1.7320508075688772 1.7320508100147274 1.7320438814531474
    6
    1.7320508075688772 1.7320508075688772 1.7320438793794952
    7
    1.7320508075688772 1.7320508075688772 1.7320438793795034
    Видно, что уже на 6-й итерации вычисления в третьем столбце [Res1] привели к совпадению со значением встроенной функции [Exact] в пределах точности FLOAT(53) для квадратного корня из трех. Вычисления в четвертом столбце [Res2] – нет. В чем же причина таких различий? Не сразу очевидно, но причина в том, что выражение (1./6.) вычисляется с большой ошибкой, так как операнды не приведены к 8-байтовому представлению вещественных чисел (двойная точность). Это повлияло на все вычисления, и мы получили
    только 5-6 правильных значащих цифр в результате, что согласуется с теорией вычислений в вещественной арифметике с одинарной точностью.
    Функция
    EOMONTH
    Как узнать последний день месяца по заданной дате, например, текущего месяца?
    Текущую дату мы можем узнать, используя встроенную функцию current_timestamp:
    1.
    SELECT
    current_timestamp;
    Чтобы узнать последний день предыдущего месяца, мы можем от текущей даты отнять номер текущего дня месяца, т.е. количество дней, прошедших от начала месяца:
    1.
    SELECT
    dateadd
    (
    dd,
    -day
    (
    current_timestamp
    )
    , current_timestamp
    )
    ;
    Тогда для текущего месяца нам потребуется предварительно добавить один месяц к текущей дате:
    1.
    SELECT
    dateadd
    (
    dd,
    -day
    (
    dateadd
    (
    mm,
    1
    , current_timestamp
    ))
    ,
    2. dateadd
    (
    mm,
    1
    , current_timestamp
    ))
    ;
    Уберем, наконец, из полученного результата компоненту времени:

    1.
    SELECT
    CAST
    (
    2. dateadd
    (
    dd, -day
    (
    dateadd
    (
    mm,
    1
    , current_timestamp
    ))
    , dateadd
    (
    mm,
    1
    , current_timestamp
    ))
    3.
    AS
    date
    )
    ;
    В
    SQL
    Server 2012 появилась функция EOMONTH, которая позволяет сделать то же самое без применения "процедурной" логики:
    1.
    SELECT
    CAST
    (
    2. dateadd
    (
    dd, -day
    (
    dateadd
    (
    mm,
    1
    , current_timestamp
    ))
    ,
    3. dateadd
    (
    mm,
    1
    , current_timestamp
    ))
    AS
    date
    4.
    )
    old_way, eomonth
    (
    current_timestamp
    )
    new_way;
    Если вы получаете ошибку при выполнении последнего запроса, значит учебник еще не переехал на версию SQL Server, поддерживающую
    EOMONTH.
    Ну, а для времени, когда я написал этот запрос, результаты, естественно, совпали:
    old_way
    new_way
    2016-07-31 2016-07-31
    Мы уже знаем, что функция EOMONTH имеет аргументом выражение типа даты. Кроме того, функция имеет также второй (необязательный) целочисленный аргумент, представляющий число месяцев, которые, при наличии, будут добавлены к дате, представленной первым аргументом.
    Например, следующий запрос даст нам последние дни предыдущего, текущего и следующего месяца для даты '2016-01-28':
    1.
    SELECT
    eomonth
    (
    '2016-01-28'
    ,
    -1
    )
    prev_month,
    2. eomonth
    (
    '2016-01-28'
    )
    this_month,
    3. eomonth
    (
    '2016-01-28'
    ,
    1
    )
    next_month;

    prev_month
    this_month
    next_month
    2015-12-31
    2016-01-31 2016-02-29
    Функция
    STRING_AGG
    Агрегация текстовых данных
    Рассмотрим такую задачу.
    Перечислить через запятую все корабли из
    таблицы Ships, которые принадлежат Японии.
    Получить список кораблей Японии труда не составляет:
    1.
    SELECT
    name
    FROM
    Ships s
    JOIN
    Classes c
    ON
    s.class=c.class
    2.
    WHERE
    country=
    'Japan'
    3.
    ORDER
    BY
    name;
    В MySQL есть замечательная агрегатная функция GROUP_CONCAT, которая решает поставленную задачу:
    1.
    SELECT
    GROUP_CONCAT
    (
    name
    )
    ships_list
    FROM
    Ships s
    JOIN
    Classes c
    ON
    s.class=c.class
    2.
    WHERE
    country=
    'Japan'
    3.
    ORDER
    BY
    name;
    ships_list
    haruna,hiei,kirishima,kon,musashi,yamato

    По умолчанию в качестве разделителя как раз используется запятая, хотя мы можем выбрать любой символ.
    Если выполнить группировку, то легко получить список кораблей для каждой страны:
    1.
    SELECT
    country, GROUP_CONCAT
    (
    name
    )
    ships_list
    2.
    FROM
    Ships s
    JOIN
    Classes c
    ON
    s.class=c.class
    3.
    GROUP
    BY
    country
    4.
    ORDER
    BY
    country, name;
    country
    ships_list
    gt.britain
    renown,repulse,resolution,ramillies,revenge,royal oak,royal sovereign
    japan
    haruna,hiei,kirishima,kongo,musashi,yamato
    usa
    iowa,missouri,new jersey,wisconsin,north carolina,south dakota,washington,california,tennessee
    Для SQL Server решение нашей задачи можно получить менее естественным способом - через представление результата выборки в форме XML:
    1.
    SELECT
    STUFF
    (
    2.
    (
    SELECT
    ','
    +name
    AS
    'data()'
    FROM
    Ships s
    JOIN
    Classes c
    ON
    s.class=c.class
    3.
    WHERE
    country=
    'Japan'
    4.
    ORDER
    BY
    name
    FOR
    XML PATH
    (
    ''
    )
    5.
    )
    ,
    1
    ,
    1
    ,
    ''
    )
    ;
    Группировка по стране еще добавит сложности. Но мы не будем этого делать, поскольку в SQL Server, начиная с версии 2017, появилась функция STRING_AGG, позволяющая конкатенировать строки. Эта функция
    имеет два обязательных аргумента - строковое выражение, которое и будет использоваться для сцепления, и разделитель.
    1.
    SELECT
    country, STRING_AGG
    (
    name,
    ','
    )
    ships_list
    2.
    FROM
    Ships s
    JOIN
    Classes c
    ON
    s.class=c.class
    3.
    GROUP
    BY
    country
    4.
    ORDER
    BY
    country;
    country
    ships_list
    Gt.Britain
    Renown,Repulse,Resolution,Ramillies,Revenge,Royal
    Oak,Royal Sovereign
    Japan
    Musashi,Yamato,Haruna,Hiei,Kirishima,Kongo
    USA
    North Carolina,South
    Dakota,Washington,Iowa,Missouri,New
    Jersey,Wisconsin,California,Tennessee
    Из представленного результата видно, что корабли в списке не отсортированы. Сортировка в стиле GROUP_CONCAT здесь не работает.
    Чтобы задать порядок сортировки, используется необязательное предложение WITHIN GROUP:
    1.
    SELECT
    country, STRING_AGG
    (
    name,
    ','
    )
    WITHIN
    GROUP
    (
    ORDER
    BY
    name
    )
    ships_list
    2.
    FROM
    Ships s
    JOIN
    Classes c
    ON
    s.class=c.class
    3.
    GROUP
    BY
    country
    4.
    ORDER
    BY
    country;
    country
    ships_list
    Gt.Britain
    Ramillies,Renown,Repulse,Resolution,Revenge,Royal
    Oak,Royal Sovereign
    Japan
    Haruna,Hiei,Kirishima,Kongo,Musashi,Yamato

    USA
    California,Iowa,Missouri,New Jersey,North
    Carolina,South
    Dakota,Tennessee,Washington,Wisconsin
    При использовании функции STRING_AGGсоединяемые значения преобразуются к типу данных VARCHAR (NVARCHAR).
    Типом результата будет VARCHAR(8000) или NVARCHAR(4000), если среди соединяемых значений не будет значений типа VARCHAR(MAX) или
    NVARCHAR(MAX). В последнем случае результат будет иметь тип
    VARCHAR(MAX) (или NVARCHAR(MAX) соответственно).
    В нижеследующем примере будет получена ошибка, поскольку длина результата - 8011 - превышает значение 8000:

    8000 (значение 'a' будет дополнено пробелами, т.к. используется тип постоянной длины CHAR),

    1 символ на запятую-разделитель,

    10 (длина значения 'bbbbbbbbbb').

    1. declare @a char
    (
    8000
    )
    , @b varchar
    (
    10
    )
    ;
    2.
    SELECT
    @a=
    'a'
    ,@b=replicate
    (
    'b'
    ,
    10
    )
    ;
    3.
    SELECT
    string_agg
    (
    x,
    ','
    )
    res
    4.
    FROM
    (
    VALUES
    (
    @b
    )
    ,
    (
    @a
    ))
    X
    (
    x
    )
    ;
    Результат агрегирования STRING_AGG превышает предел в 8000 байтов.
    Используйте типы LOB, чтобы избежать усечения результатов.
    Однако если, скажем, для переменной @b мы используем тип
    VARCHAR(MAX), то код выполнится без ошибки:
    1. declare @a char
    (
    8000
    )
    , @b varchar
    (
    MAX
    )
    ;
    2.
    SELECT
    @a=
    'a'
    ,@b=replicate
    (
    'b'
    ,
    10
    )
    ;
    3.
    SELECT
    string_agg
    (
    x,
    ','
    )
    res
    4.
    FROM
    (
    VALUES
    (
    @b
    )
    ,
    (
    @a
    ))
    X
    (
    x
    )
    ;
    res
    bbbbbbbbbb,a ...(еще 7999 пробелов)

    Функция
    STRING_SPLIT
    Функция STRING_SPLIT выполняет операцию, обратную
    STRING_AGG
    . Она принимает на входе символьную строку и разбивает её на подстроки по заданному вторым параметром разделителю. Эти подстроки формируют значения унарной таблицы. К единственному столбцу этой таблицы можно обратиться по имени value.
    1.
    SELECT
    *
    FROM
    STRING_SPLIT
    (
    '0 1 2 3 4 5 6 7 8 9'
    ,
    ' '
    )
    ;
    value
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9

    Фактически функция
    STRING_SPLIT играет роль нестандартного конструктора одностолбцовой таблицы.
    Стандартная альтернатива выглядит так:
    1.
    SELECT
    *
    FROM
    (
    VALUES
    (
    0
    )
    ,
    (
    1
    )
    ,
    (
    2
    )
    ,
    (
    3
    )
    ,
    (
    4
    )
    ,
    (
    5
    )
    ,
    (
    6
    )
    ,
    (
    7
    )
    ,
    (
    8
    )
    ,
    (
    9
    ))
    X
    (
    value
    )
    ;
    Больший интерес представляет случай, когда на вход функции передается значение столбца некоторой таблицы.
    Пусть, например, нам требуется разбить многословные названия кораблей в таблице Ships на отдельные слова. Тогда можно выполнить следующий запрос:
    1.
    SELECT
    name, value
    FROM
    Ships
    CROSS
    APPLY
    STRING_SPLIT
    (
    name,
    ' '
    )
    2.
    WHERE
    name
    LIKE
    '% %'
    ;
    name
    value
    New Jersey
    New
    New Jersey
    Jersey
    North Carolina
    North
    North Carolina
    Carolina
    Royal Oak
    Royal
    Royal Oak
    Oak
    Royal Sovereign Royal
    Royal Sovereign Sovereign

    South Dakota
    South
    South Dakota
    Dakota
    Функция
    CHOOSE
    Нестандартная функция CHOOSE появилась в SQL Server версии 2012.
    Функция CHOOSE используется для выбора одного из нескольких вариантов. Выбор осуществляется на основании индекса (номера варианта), который является первым параметром функции. Остальные параметры представляют собой варианты выбора. Будет выбран тот вариант, номер которого в списке параметров, совпадает с индексом.
    Например, запрос
    1.
    SELECT
    CHOOSE
    (
    2
    ,
    'PC'
    ,
    'Laptop'
    ,
    'Printer'
    )
    ; вернет Laptop, т.к. это второй элемент списка.
    Больший интерес представляет случай, когда индексом является выражение, включающее столбцы таблиц.
    Пусть нам требуется вместо номера пункта (в базе данных "
    Вторсырье ") выводить его название.
    Поскольку в базе данных названия пунктов не хранятся, будем формировать их "налету", используя функцию CHOOSE:
    1.
    SELECT
    DISTINCT
    point,
    2. CHOOSE
    (
    point,
    'point A'
    ,
    'point B'
    ,
    'point C'
    )
    point_name
    3.
    FROM
    outcome;

    point
    point_name
    1
    point A
    2
    point B
    3
    point C
    Очевидно, что функция CHOOSE является еще одним частным вариантом выражения
    CASE
    . Наш пример с использовании функции CASE можно переписать в виде:
    1.
    SELECT
    DISTINCT
    point,
    2.
    CASE
    point
    3.
    WHEN
    1
    THEN
    'point A'
    4.
    WHEN
    2
    THEN
    'point B'
    5.
    WHEN
    3
    THEN
    'point C'
    6.
    END
    point_name
    7.
    FROM
    outcome;
    Если указанному индексу не будет найдено соответствия, то результатом функции CHOOSE будет NULL:
    1.
    SELECT
    CHOOSE
    (
    4
    ,
    'PC'
    ,
    'Laptop'
    ,
    'Printer'
    )
    ;
    Для обработки этого случая в выражении CASE предусмотрено предложение
    ELSE
    . С помощью этого предложения мы можем вместо NULL
    (по умолчанию) вывести, например, UNKNOWN:
    1.
    SELECT
    CASE
    4 2.
    WHEN
    1
    THEN
    'PC'
    3.
    WHEN
    2
    THEN
    'Laptop'
    4.
    WHEN
    3
    THEN
    'Printer'
    5.
    ELSE
    'UNKNOWN'

    6.
    END
    ;
    Мы легко можем преодолеть этот "недостаток" функции CHOOSE, обернув её функцией COALESCE:
    1.
    SELECT
    COALESCE
    (
    CHOOSE
    (
    4
    ,
    'PC'
    ,
    'Laptop'
    ,
    'Printer'
    )
    ,
    'UNKNOWN'
    )
    ; но это уже как бы "масло масляное" - case от case.
    Можно сказать, что функция CHOOSE имеет специфическое применение, но в этих ограниченных случаях она позволяет упростить запись.
    В заключение приведем еще один пример.
    Для каждой модели указать, является её номер четным (even) или
    нечетным (odd).
    1.
    SELECT
    model, CHOOSE
    (
    model%
    2+1
    ,
    'EVEN'
    ,
    'ODD'
    )
    2.
    FROM
    product
    3.
    WHERE
    ISNUMERIC
    (
    model
    )
    =
    1
    ;
    Предикат в предложении WHERE используется для того, чтобы ограничиться моделями, номера которых представлены числом. Обратите внимание на тип данных столбца model
    ! Надеюсь, что те, кто решает упражнения на сайте sql-ex.ru уже в курсе. :-) Они также должны быть в курсе, что использование функции ISNUMERIC в данном контексте не является радикальным решением.
    Заметки
    о типах
    данных

    В этом разделе я предполагаю писать о том, что мне показалось странным или необычным в отношении типов данных и их преобразовании, о том, что противоречит стандарту и называется «особенностями реализации».
    CHAR и
    VARCHAR
    Недавно мне довелось искать ошибку в решении, которое содержало такое преобразование:
    1.
    CAST
    (
    model
    AS
    VARCHAR
    )
    Те, кто изучил схему «
    Компьютеры
    », подумают о бессмысленности преобразования типа в тот же самый тип (столбец model определен как
    VARCHAR(50)). Однако именно это преобразование и делало запрос неверным.
    Дело в том, что, если размер типа при преобразовании не указан, то в
    SQL
    Server принимается значение по умолчанию, которое для VARCHAR равно 30. При этом если преобразуемая строка имеет больший размер, то отсекаются все символы кроме первых 30-ти. Разумеется, никакой ошибки при этом не возникает. Как раз на «длинных» номерах моделей предложенное решение и давало неверный результат. Как говорится в таких случаях, читайте документацию. Однако интересно, что по этому поводу говорит Стандарт?
    Согласно стандарту, если для типов CHAR и VARCHAR размер не указан, то подразумевается CHAR(1) и VARCHAR(1) соответственно. Давайте проверим, как следуют стандарту доступные мне СУБД: SQL Server, MySQL,
    PostgreSQL.
    Тут имеется два аспекта:
    1. Преобразование типа
    2. Использование типов CHAR/VARCHAR при описании схемы (DDL).
    Начнем с преобразования типа.

    SQL Server 2008
    1.
    SELECT
    CAST
    (
    '11111111112222222222333333333344444444445555555555
    '
    AS
    CHAR
    )
    chr,
    2.
    CAST
    (
    '11111111112222222222333333333344444444445555555555
    '
    AS
    VARCHAR
    )
    vchr;
    В результате получим
    chr
    vchr
    11111111112222222222333333
    3333
    11111111112222222222333333 3333
    То есть оба символьных типа усекаются до значения по умолчанию, которое равно 30. Никаких сообщений об ошибках не возникает, что, собственно, соответствует стандарту.
    PostgreSQL 8.3
    1.
    SELECT
    CAST
    (
    '11111111112222222222333333333344444444445555555555
    '
    AS
    CHAR
    )
    AS
    chr,
    2.
    CAST
    (
    '11111111112222222222333333333344444444445555555555
    '
    AS
    VARCHAR
    )
    AS
    vchr;
    chr
    vchr
    1
    11111111112222222222333333333344444444445555555555
    Налицо половинчатое следование стандарту, т.е. соответствие ему в отношении типа CHAR. Что касается типа VARCHAR, то согласно документации, если длина строки не указана, принимается строка любого
    размера, т.е. усечения не происходит. (If character varying is used without length
    specifier, the type accepts strings of any size.)
    MySQL 5.0
    Как говорится, чем дальше, тем «страньше». Оказывается, в MySQL преобразование к типу VARCHAR вообще не поддерживается. Нам остается проверить только преобразование к CHAR:
    1.
    SELECT
    CAST
    (
    '11111111112222222222333333333344444444445555555555
    '
    AS
    CHAR
    )
    chr;
    chr
    11111111112222222222333333333344444444445555555555
    Т.е. строка не усекается; при этом в документации читаю: «Если при использовании функций CAST и CONVERT размер не указан, то длина по умолчанию – 30.» (When n is not specified when using the CAST and CONVERT
    functions, the default length is 30).
    Посмотрим теперь, как обстоят дела с определением данных. Ниже приведен тестовый скрипт.
    1.
    CREATE
    TABLE
    Test_char
    (
    chr CHAR, vchr VARCHAR
    )
    ;
    1.
    DELETE
    FROM
    Test_char;
    1.
    INSERT
    INTO
    Test_char
    2.
    VALUES
    (
    '1'
    ,
    '11111111112222222222333333333344444444445555555555
    '
    )
    ;
    3.
    4.
    INSERT
    INTO
    Test_char

    5.
    VALUES
    (
    '11111111112222222222333333333344444444445555555555'
    ,
    '1'
    )
    ;
    6.
    7.
    INSERT
    INTO
    Test_char
    8.
    VALUES
    (
    '2'
    ,
    CAST
    (
    '111111111122222222223333333333'
    AS
    VARCHAR
    ))
    ;
    9.
    10.
    INSERT
    INTO
    Test_char
    11.
    VALUES
    (
    CAST
    (
    '111111111122222222223333333333'
    AS
    CHAR
    )
    ,
    '2'
    )
    ;
    12.
    13.
    INSERT
    INTO
    Test_char
    14.
    VALUES
    (
    '3'
    ,
    '3'
    )
    ;
    1.
    SELECT
    *
    FROM
    Test_char;
    1   ...   33   34   35   36   37   38   39   40   ...   47


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