SELECT (обучающий этап) задачи по SQL запросам 120 штук, DML 10 шт. Дистанционное обучение языку баз данных SQL. Интерактивные упражнения и тестирование по операторам SELECT,INSERT,UPDATE,DELETE языка SQL. SQL remote education. SQL statements exercises. Подзапросы, Соединение таблиц, Функции SQL, Введение в SQL, Скачать книги по SQL. Команды SQL,CREATE SEQUENCE,CREATE SYNONYM,CREATE USER,CREATE VIEW,Create Table,DROP,GRANT,INSERT,REVOKE,SET ROLE,SET TRANSACTION,SQL ALTER TABLE,SQL команды.
четверг, 23 февраля 2017 г.
Функции работы со строками в MS SQL SERVER 2005
Функции работы со строками в MS SQL SERVER 2005
Вот полный перечень функций работы со строками, взятый из BOL:
ASCII
NCHAR
SOUNDEX
CHAR
PATINDEX
SPACE
CHARINDEX
REPLACE
STR
DIFFERENCE
QUOTENAME
STUFF
LEFT
REPLICATE
SUBSTRING
LEN
REVERSE
UNICODE
LOWER
RIGHT
UPPER
LTRIM
RTRIM
Начнем с двух взаимно обратных функций - ASCII и CHAR.
Функция ASCII возвращает ASCII-код крайнего левого символа строкового выражения, являющегося аргументом функции.
Вот, например, как можно определить, сколько имеется разных букв, с которых начинаются названия кораблей в таблице Ships:
SELECT COUNT(DISTINCT ASCII(name)) FROM Ships
Результат - 11. Чтобы выяснить, какие это буквы, мы можем применить функцию CHAR, которая возвращает символ по известному ASCII-коду (от 0 до 255):
SELECT DISTINCT CHAR(ASCII(name)) FROM Ships ORDER BY 1
Следует отметить, что аналогичный результат можно получить проще с помощью еще одной функции - LEFT, которая имеет следующий синтаксис: LEFT (<строковое выражение>, <целочисленное выражение> )
и вырезает заданное вторым аргументом число символов слева из строки, являющейся первым аргументом. Итак,
SELECT DISTINCT LEFT(name, 1) FROM Ships ORDER BY 1
А вот как, например, можно получить таблицу кодов всех алфавитных символов:
SELECT CHAR(ASCII('a')+ num-1) letter, ASCII('a')+ num - 1 [code]
FROM (SELECT 5*5*(a-1)+5*(b-1) + c AS num
FROM (SELECT 1 a UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5) x
CROSS JOIN
(SELECT 1 b UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5) y
CROSS JOIN
(SELECT 1 c UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5) z
) x
WHERE ASCII('a')+ num -1 BETWEEN ASCII('a') AND ASCII('z')
Тех, кто еще не в курсе генерации числовой последовательности, отсылаю к соответствующей статье.
Как известно, коды строчных и прописных букв отличаются. Поэтому чтобы получить полный набор без переписывания запроса, достаточно просто дописать к вышеприведенному коду аналогичный:
UNION
SELECT CHAR(ASCII('A')+ num-1) letter, ASCII('A')+ num - 1 [code]
FROM (SELECT 5*5*(a-1)+5*(b-1) + c AS num
FROM (SELECT 1 a UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5) x
CROSS JOIN
(SELECT 1 b UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5) y
CROSS JOIN
(SELECT 1 c UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5) z
) x
WHERE ASCII('A')+ num -1 BETWEEN ASCII('A') AND ASCII('Z')
Чтобы таблица выглядела более патриотично, достаточно заменить латинские буквы "a" и "A" на неотличимые на взгляд русские - "а" и "А", а "z" и "Z" на "я" и "Я". Вот только буквы "ё" вы не увидите в этой таблице, т.к. в кодовой таблице ASCII эти символы лежат отдельно, что легко проверить:
SELECT ASCII('ё') UNION ALL SELECT ASCII('Ё')
Я полагаю, что не будет сложным добавить эту букву в таблицу, если потребуется.
Рассмотрим теперь задачу определения нахождения искомой подстроки в строковом выражении. Для этого могут использоваться две функции - CHARINDEX и PATINDEX. Обе они возвращают начальную позицию (позицию первого символа подстроки) подстроки в строке. Функция CHARINDEX имеет синтаксис: CHARINDEX (искомое_выражение, строковое_выражение[, стартовая_позиция])
Здесь необязательный целочисленный параметр стартовая_позиция определяет позицию в строковом выражении, начиная с которой выполняется поиск искомого_выражения . Если этот параметр опущен, поиск выполняется от начала строкового_выражения. Например, запрос
SELECT name FROM Ships WHERE CHARINDEX('sh', name) > 0
будет выводить те корабли, в которых имеется сочетание символов "sh". Здесь используется тот факт, что если искомая строка не будет обнаружена, то функция CHARINDEX возвращает 0. Результат выполнения запроса будет содержать следующие корабли:
name
Kirishima
Musashi
Washington
Следует отметить, что если искомая подстрока либо строковое выражение есть NULL, то результатом функции тоже будет NULL.
Следующий пример определяет позиции первого и второго вхождения символа "a" в имени корабля "California"
SELECT CHARINDEX('a',name) first_a,
CHARINDEX('a', name, CHARINDEX('a', name)+1) second_a
FROM Ships WHERE name='California'
Обратите внимание, что при определении второго символа в функции используется стартовая позиция, которой является позиция следующего за первой буквой "a" символа - CHARINDEX('a', name)+1. Правильность результата - 2 и 10 - легко проверить :-).
Функция PATINDEX имеет синтаксис: PATINDEX ('%образец%' , строковое_выражение)
Главное отличие этой функции от CHARINDEX заключается в том, что поисковая строка может содержать подстановочные знаки - % и _. При этом концевые знаки "%" являются обязательными. Например, использование этой функции в первом примере будет иметь вид
SELECT name FROM Ships WHERE PATINDEX('%sh%', name) > 0
А вот, например, как можно найти имена кораблей, которые содержат последовательность из трех символов, первый и последний из которых есть "e":
SELECT name FROM Ships WHERE PATINDEX('%e_e%', name) >0
Результат выполнения этого запроса выглядит следующим образом:
name
Revenge
Royal Sovereign
Парная к LEFT функция RIGHT возвращает заданное число символов справа из строкового выражения: RIGHT(<строковое выражения>,<число символов>)
Вот, например, как можно определить имена кораблей, которые начинаются и заканчиваются на одну и ту же букву:
SELECT name FROM Ships WHERE LEFT(name, 1) = RIGHT(name, 1)
То, что в результате мы получим пустой результирующий набор, означает, что таких кораблей в базе данных нет. Давайте возьмем комбинацию значений - класс и имя корабля.
Соединение двух строковых значений в одно называется конкатенацией, и в SQL Server для этой операции используется знак "+" (в стандарте "||"). Итак,
SELECT * FROM (
SELECT class +' '+ name AS cn FROM Ships
) x
WHERE LEFT(cn, 1)=RIGHT(cn, 1)
Здесь мы разделяем пробелом имя класса и имя корабля. Кроме того, чтобы не повторять всю конструкцию в качестве аргумента функции, используем подзапрос. Результат будет иметь вид:
cn
Iowa Missouri
North Carolina Washington
А если строковое выражение будет содержать лишь одну букву? Запрос выведет ее. В этом легко убедиться, написав
SELECT * FROM (
SELECT class +' '+ name AS cn FROM Ships
UNION ALL
SELECT 'a' as nc
) x
WHERE LEFT(cn, 1)=RIGHT(cn, 1)
Чтобы исключить этот случай, можно воспользоваться еще одной полезной функцией LEN (<строковое выражение>), которая возвращает число символов в строке. Ограничимся случаем, когда число символов больше единицы:
SELECT * FROM (
SELECT class +' '+ name AS cn FROM Ships
UNION ALL
SELECT 'a' as nc
) x
WHERE LEFT(cn, 1)=RIGHT(cn, 1) AND LEN(cn)>1
Замечание. Реализация этой функции в MS SQL Server имеет одну особенность, а именно, при подсчете длины не учитываются концевые пробелы.
Действительно, выполним следующий код:
Функция REPLICATE дополняет константу 'abcde' пятью пробелами справа, которые не учитываются функцией LEN, - в обоих случаях получаем 5.
Функция DATALENGTH возвращает число байтов в представлении переменной и демонстрирует нам различие между типами CHAR и VARCHAR. DATALENGTH даст нам 12 для типа CHAR и 10 - для VARCHAR.
Как и следовало ожидать, DATALENGTH для переменной типа VARCHAR вернула фактическую длину переменной. Но почему для переменной типа CHAR результат оказался равным 12? Дело в том, что CHAR - это тип фиксированной длины. Если значение переменной оказывается меньше ее длины, а длину мы объявили как CHAR(12), то значение переменной будет "выровнено" до требуемой длины за счет добавления концевых пробелов.
На сайте имеются задачи, в которых требуется упорядочить (найти максимум и т.д.) в числовом порядке значения, представленные в текстовом формате. Например, номер места в самолете ("2d") или скорость CD ("24x"). Проблема заключается в том, что текст сортируется так (по возрастанию)
11a
1a
2a
Действительно,
SELECT '1a' AS place
UNION ALL SELECT '2a'
UNION ALL SELECT '11a'
ORDER BY 1
Если же требуется упорядочить места в порядке возрастания рядов, то порядок должен быть такой
1a
2a
11a
Чтобы добиться такого порядка, нужно выполнить сортировку по числовым значениям, присутствующим в тексте. Можно предложить такой алгоритм:
1. Извлечь число из строки.
2. Привести его к числовому формату.
3. Выполнить сортировку по приведенному значению.
Т.к. нам известно, что буква только одна, то для извлечения числа из строки можно воспользоваться следующей конструкцией, которая не зависит от числа цифр в номере места:
LEFT(place, LEN(place)-1)
Если только этим и ограничиться, то получим
place
1a
11a
2a
Приведение к числовому формату может быть следующим:
CAST (LEFT(place, LEN(place)-1) AS INT)
Осталось выполнить сортировку
SELECT * FROM (
SELECT '1a' AS place
UNION ALL SELECT '2a'
UNION ALL SELECT '11a'
) x
ORDER BY CAST(LEFT(place, LEN(place)-1) AS INT)
Что и требовалось доказать.
Ранее мы для извлечения числа из текстовой строки пользовались функцией LEFT, т.к. нам было известно априори, какое число символов нужно убрать справа (один). А если же нужно извлечь строку из подстроки не по известной позиции символа, а по самому символу? Например: извлечь все символы до первой буквы "х" (значение скорости CD).
В этом случае мы можем использовать также уже рассмотренную ранее функцию CHARINDEX, которая позволит определить неизвестную позицию символа:
SELECT model, LEFT(cd, CHARINDEX('x', cd) -1) FROM PC
Функция SUBSTRING
SUBSTRING (<выражение>, <начальная позиция>, <длина> )
Эта функция позволяет извлечь из выражения его часть заданной длины, начиная от заданной начальной позиции. Выражение может быть символьной или бинарной строкой, а также иметь тип text или image. Например, если нам потребуется получить 3 символа в названии корабля, начиная со 2-го символа, то сделать без помощи функции SUBSTRING будет не так просто. А так мы пишем:
SELECT name, SUBSTRING(name, 2, 3) FROM Ships
В случае, когда нужно извлечь все символы, начиная с некоторого, мы также можем использовать эту функцию. Например,
SELECT name, SUBSTRING(name, 2, LEN(name)) FROM Ships
даст нам все символы в названиях кораблей от второй буквы в имени. Обратите внимание на то, что для указания числа извлекаемых символов я использовал функцию LEN(name), которая возвращает число символов в имени. Понятно, что поскольку мне нужны символы, начиная со второго, то их число будет меньше общего количества символов в имени. Однако это не вызывает ошибки, поскольку если указанное число символов превышает возможное число, то будут извлечены все символы до конца строки. Поэтому я и беру их с запасом, не утруждая себя вычислениями.
Функция REVERSE
Эта функция переворачивает строку, как бы читая ее справа налево. Т.е. результатом запроса
SELECT REVERSE('abcdef')
будет 'fedcba'. Если бы в языке отсутствовала функция RIGHT, то запрос
SELECT RIGHT('abcdef',3)
можно было бы равносильно заменить запросом
SELECT REVERSE(LEFT(REVERSE('abcdef'),3))
Я вижу пользу этой функции в следующем. Пусть нам требуется определить позицию не первого, а последнего вхождения некоторого символа (или последовательности символов) в строке. Вспомним пример, в котором мы определяли позицию первого символа "а" в названии корабля "California":
SELECT CHARINDEX('a', name) first_a
FROM Ships WHERE name='California'
Определим теперь позицию последнего вхождения в это название символа "а". Функция
CHARINDEX('a', REVERSE(name))
позволит найти эту позицию, но справа. Для получения позиции этого же символа слева достаточно написать
SELECT LEN(name) + 1 - CHARINDEX('a', REVERSE(name)) first_a
FROM Ships WHERE name='California'
Функция REPLACE
REPLACE ( <строка1> , <строка2> , <строка3> )
Заменяет в строке1 все вхождения строки2 на строку3. Эта функция, безусловно, полезна в операторах обновления (UPDATE), если нужно изменить (исправить) содержимое столбца. Пусть, например, нужно заменить все пробелы дефисом в названиях кораблей. Тогда можно написать
UPDATE Ships
SET name = REPLACE(name, ' ', '-')
(Этот пример можно выполнить на странице с упражнениями DML, где разрешаются запросы на изменение данных)
Однако эта функция может найти применение и в более нетривиальных случаях. Давайте определим, сколько раз в названии корабля используется буква "a". Идея проста: заменим каждую искомую букву двумя любыми символами, после чего посчитаем разность длин полученной и искомой строки. Итак,
SELECT name, LEN(REPLACE(name, 'a', 'aa')) - LEN(name) FROM Ships
А если нам нужно определить число вхождений произвольной последовательности символов, скажем, передаваемой в качестве параметра в хранимую процедуру? Использованный выше алгоритм в этом случае следует дополнить делением на число символов в искомой последовательности:
DECLARE @str AS VARCHAR(100)
SET @str='ma'
SELECT name, (LEN(REPLACE(name, @str, @str + @str)) - LEN(name))/LEN(@str) FROM Ships
Для удвоения числа искомых символов здесь применялась конкатенация - @str + @str . Однако для этой цели можно использовать еще одну функцию - REPLICATE, которая повторяет первый аргумент такое число раз, которое задается вторым аргументом.
SELECT name, (LEN(REPLACE(name, @str, REPLICATE(@str, 2))) - LEN(name))/LEN(@str) FROM Ships
Т.е. мы повторяем дважды подстроку, хранящуюся в переменной @str .
Если же нужно заменить в строке не определенную последовательность символов, а заданное число символов, начиная с некоторой позиции, то проще использовать функцию STUFF: STUFF (<строка1> , <стартовая позиция> , <L> , <строка2>)
Эта функция заменяет подстроку длиной L, которая начинается со стартовой позиции в строке1, на строку2. Пример. Изменить имя корабля: оставив в его имени 5 первых символов, дописать "_" (нижнее подчеркивание) и год спуска на воду. Если в имени менее 5 символов, дополнить его пробелами.
Можно решать эту задачу с помощью разных функций. Мы же попытаемся это сделать с помощью функции STUFF. В первом приближении напишем (ограничимся запросом на выборку):
SELECT name, STUFF(name, 6, LEN(name), '_'+launched) FROM Ships
Третьим аргументом (количество символов для замены) я использую LEN(name), т.к. мне нужно заменить все символы до конца строки, поэтому я беру с запасом - исходное число символов в имени. И все же этот запрос вернет ошибку. Причем дело не в третьем аргументе, а в четвертом, где выполняется конкатенация строковой константы и числового столбца. Ошибка приведения типа. Для преобразования числа к его строковому представлению можно воспользоваться еще одной встроенной функцией - STR: STR ( <число с плавающей точкой> [ , <длина> [ , <число десятичных знаков> ] ] )
При этом преобразовании выполняется округление, а длина задает длину результирующей строки. Например,
STR(3.3456, 5, 1)
3.3
STR(3.3456, 5, 2)
3.35
STR(3.3456, 5, 3)
3.346
STR(3.3456, 5, 4)
3.346
Обратите внимание, что если полученное строковое представление числа меньше заданной длины, то добавляются лидирующие пробелы. Если же результат больше заданной длины, то усекается дробная часть (с округлением); в случае же целого числа получаем соответствующее число звездочек "*":
STR(12345,4,0)
****
Кстати, по умолчанию используется длина в 10 символов. Имея в виду, что год представлен четырьмя цифрами, напишем
SELECT name, STUFF(name, 6, LEN(name), '_'+STR(launched, 4)) FROM Ships
Уже почти все правильно. Осталось учесть случай, когда число символов в имени менее 6, т.к. в этом случае функция STUFF дает NULL. Ну что ж вытерпим до конца мучения, связанные с использованием этой функции в данном примере, попутно применив еще одну строковую функцию. Добавим конечные пробелы, чтобы длина имени была заведомо больше 6. Для этого имеется специальная функция SPACE SPACE(<число пробелов>):
SELECT name, STUFF(name + SPACE(6), 6, LEN(name), '_'+STR(launched,4)) FROM Ships
Функции LTRIM и RTRIM
LTRIM (<строковое выражение>) RTRIM (<строковое выражение>)
отсекают соответственно лидирующие и конечные пробелы строкового выражения, которое неявно приводится к типу VARCHAR.
Пусть требуется построить такую строку: имя пассажира_идентификатор пассажира для каждой записи из таблицы Passenger. Если мы напишем
SELECT name + '_' + CAST(id_psg AS VARCHAR) FROM Passenger,
то в результате получим что-то типа:
A _1
Это связано с тем, что столбец name имеет тип CHAR(30). Для этого типа короткая строка дополняется пробелами до заданного размера (у нас 30 символов). Здесь нам как раз и поможет функция RTRIM:
SELECT RTRIM(name) + '_' + CAST(id_psg AS VARCHAR) FROM Passenger
Функции LOWER и UPPER
LOWER(<строковое выражение>) UPPER(<строковое выражение>)
преобразуют все символы аргумента соответственно к нижнему и верхнему регистру. Эти функции оказываются полезными при сравнении регистрозависимых строк.
Пара интересных функций SOUNDEX и DIFFERENCE: SOUNDEX(<строковое выражение>) DIFFERENCE (<строковое выражение_1>, <строковое выражение_2>)
Позволяют определить близость звучания слов. При этом SOUNDEX возвращает четырехсимвольный код, используемый для сравнения, а DIFFERENCE собственно и оценивает близость звучания двух сравниваемых строковых выражений. Поскольку эти функции не поддерживают кириллицы, отсылаю интересующихся к BOL за примерами их использования.
В заключение приведем функции и несколько примеров использования юникода.
Функция UNICODE
UNICODE (<строковое выражение>)
возвращает юникод первого символа строкового выражения.
Функция NCHAR
NCHAR (<целое>)
возвращает символ по его юникоду. Несколько примеров.
SELECT ASCII('а'), UNICODE('а')
возвращает код ASCII и юникод русской буквы "а": 224 и 1072.
SELECT CHAR(ASCII('а')), CHAR(UNICODE('а'))
Пытаемся восстановить символ по его коду. Получаем "а" и NULL. NULL-значение возвращается потому, что кода 1072 нет в обычной кодовой таблице.
SELECT CHAR(ASCII('а')), NCHAR(UNICODE('а'))
Теперь все нормально, в обоих случаях "а". Наконец,
SELECT NCHAR(ASCII('а'))
даст "a", т.к. юникод 224 соответствует именно этой букве.
Приведенные здесь примеры можно выполнить непосредственно на сайте, установив флажок "Без проверки" на странице с упражнениями на SELECT.