Строковые функции
Строковые функции вырезают куски текста, убирают лишнее и склеивают значения. Они нужны, когда данные хранятся в неудобном виде. Например, дата в виде текста или две координаты в одной колонке.
В уроке работаем с таблицей tutorial.sf_crime_incidents_2014_01 — инциденты, о которых сообщила полиция Сан-Франциско в январе 2014 года. Одна строка — один инцидент.
Важные колонки:
date— текст вида01/01/2014 08:00:00 AM +0000;time— текстHH:MM;location— текст вида(37.78, -122.42);latиlon— те же координаты, но числами.
Начните с просмотра:
SELECT *FROM tutorial.sf_crime_incidents_2014_01LIMIT 10;LEFT, RIGHT и LENGTH
Заголовок раздела «LEFT, RIGHT и LENGTH»LEFT(текст, n) берёт n символов слева. RIGHT(текст, n) — справа. LENGTH(текст) считает длину.
В колонке date первые 10 символов — это дата:
SELECT incidnt_num, date, LEFT(date, 10) AS cleaned_dateFROM tutorial.sf_crime_incidents_2014_01LIMIT 5;Справа — время:
SELECT incidnt_num, date, LEFT(date, 10) AS cleaned_date, RIGHT(date, 17) AS cleaned_timeFROM tutorial.sf_crime_incidents_2014_01LIMIT 5;RIGHT(date, 17) работает, потому что длина везде одинаковая (28 символов). Если длина разная, считайте через LENGTH:
SELECT incidnt_num, date, LEFT(date, 10) AS cleaned_date, RIGHT(date, LENGTH(date) - 11) AS cleaned_timeFROM tutorial.sf_crime_incidents_2014_01LIMIT 5;Функции можно вкладывать. Сначала работает самая внутренняя.
TRIM убирает символы с начала или конца текста.
SELECT location, TRIM(BOTH '()' FROM location) AS no_bracketsFROM tutorial.sf_crime_incidents_2014_01LIMIT 5;Три части:
BOTH,LEADINGилиTRAILING— откуда убирать (с обеих сторон, с начала, с конца);'()'— какие символы убирать (любой из них);FROM location— из какого текста.
Без параметров TRIM(text) убирает пробелы по краям.
POSITION и STRPOS
Заголовок раздела «POSITION и STRPOS»Они возвращают номер символа, с которого подстрока начинается. Если подстроки нет, вернётся 0. Регистр важен.
SELECT incidnt_num, descript, POSITION('A' IN descript) AS a_positionFROM tutorial.sf_crime_incidents_2014_01LIMIT 5;Тот же результат даёт STRPOS. Порядок аргументов другой:
SELECT incidnt_num, descript, STRPOS(descript, 'A') AS a_positionFROM tutorial.sf_crime_incidents_2014_01LIMIT 5;Чтобы искать без учёта регистра, приведите текст к одному регистру через UPPER или LOWER.
SUBSTR(текст, старт, длина) вырезает кусок из середины. Номера символов начинаются с 1.
SELECT incidnt_num, date, SUBSTR(date, 4, 2) AS dayFROM tutorial.sf_crime_incidents_2014_01LIMIT 5;CONCAT и ||
Заголовок раздела «CONCAT и ||»Склеить текст можно двумя способами:
SELECT incidnt_num, day_of_week, CONCAT(day_of_week, ' - ', category) AS day_and_category, day_of_week || ' - ' || category AS same_with_pipesFROM tutorial.sf_crime_incidents_2014_01LIMIT 5;Числа склеиваются с текстом без CAST.
UPPER и LOWER
Заголовок раздела «UPPER и LOWER»SELECT category, UPPER(category) AS upper_category, LOWER(category) AS lower_categoryFROM tutorial.sf_crime_incidents_2014_01LIMIT 5;Текст в дату
Заголовок раздела «Текст в дату»Дата в нашей таблице в американском формате. Соберём из неё ГГГГ-ММ-ДД:
SELECT incidnt_num, date, SUBSTR(date, 7, 4) || '-' || LEFT(date, 2) || '-' || SUBSTR(date, 4, 2) AS cleaned_dateFROM tutorial.sf_crime_incidents_2014_01LIMIT 5;Текст ГГГГ-ММ-ДД легко превратить в дату через ::DATE. Для timestamp добавьте время HH:MM:SS.
Дата в более удобную дату
Заголовок раздела «Дата в более удобную дату»DATE_TRUNC обрезает timestamp до недели, месяца и других периодов. Для этого нужна колонка типа timestamp. В таблице tutorial.sf_crime_incidents_cleandate уже есть такая колонка — cleaned_date:
SELECT cleaned_date, DATE_TRUNC('week', cleaned_date) AS week_start, DATE_TRUNC('month', cleaned_date) AS month_startFROM tutorial.sf_crime_incidents_cleandateLIMIT 5;COALESCE
Заголовок раздела «COALESCE»COALESCE(a, b, c) возвращает первое значение, которое не NULL. Так заменяют пропуски:
SELECT incidnt_num, descript, COALESCE(resolution, 'unknown') AS resolutionFROM tutorial.sf_crime_incidents_2014_01LIMIT 5;Практика
Заголовок раздела «Практика»Решайте сами, без AI. Сначала напишите запрос, потом сверьтесь с проверкой, и только потом открывайте решение.
Задание 1
Заголовок раздела «Задание 1»Разделите колонку location на широту и долготу. Сравните результат с колонками lat и lon.
Проверка: 11969 строк. В первой строке широта 37.7884521578132, долгота -122.420326993863. Значения совпадают с lat и lon. Следите за пробелом после запятой.
Решение
SELECT location, TRIM(LEADING '(' FROM LEFT(location, POSITION(',' IN location) - 1)) AS latitude, TRIM(TRAILING ')' FROM RIGHT(location, LENGTH(location) - POSITION(',' IN location) - 1)) AS longitude, lat, lonFROM tutorial.sf_crime_incidents_2014_01;Задание 2
Заголовок раздела «Задание 2»Склейте lat и lon так, чтобы получилась колонка, равная location. Используйте CONCAT.
Проверка: 11969 строк. Склеенное значение совпадает с location во всех строках.
Решение
SELECT CONCAT('(', lat, ', ', lon, ')') AS concat_location, locationFROM tutorial.sf_crime_incidents_2014_01;Задание 3
Заголовок раздела «Задание 3»Сделайте ту же колонку, но через || вместо CONCAT.
Проверка: 11969 строк. Результат такой же, как в задании 2.
Решение
SELECT '(' || lat || ', ' || lon || ')' AS concat_location, locationFROM tutorial.sf_crime_incidents_2014_01;Задание 4
Заголовок раздела «Задание 4»Сделайте колонку с датой в формате ГГГГ-ММ-ДД.
Проверка: 11969 строк. В первой строке 2014-01-01. Все даты между 2014-01-01 и 2014-01-31.
Решение
SELECT incidnt_num, date, SUBSTR(date, 7, 4) || '-' || LEFT(date, 2) || '-' || SUBSTR(date, 4, 2) AS cleaned_dateFROM tutorial.sf_crime_incidents_2014_01;Задание 5
Заголовок раздела «Задание 5»Верните category так, чтобы первая буква была заглавной, а остальные строчными. Например LARCENY/THEFT станет Larceny/theft.
Проверка: 11969 строк. В первой строке Larceny/theft. Всего 32 разных значения.
Решение
SELECT incidnt_num, category, UPPER(LEFT(category, 1)) || LOWER(RIGHT(category, LENGTH(category) - 1)) AS category_cleanedFROM tutorial.sf_crime_incidents_2014_01;Задание 6
Заголовок раздела «Задание 6»Соберите настоящий timestamp из колонок date и time. Добавьте колонку ровно на 1 неделю позже.
Проверка: 11969 строк. В первой строке 2014-01-01 00:01:00 и 2014-01-08 00:01:00.
Решение
SELECT incidnt_num, (SUBSTR(date, 7, 4) || '-' || LEFT(date, 2) || '-' || SUBSTR(date, 4, 2) || ' ' || time || ':00')::TIMESTAMP AS ts, (SUBSTR(date, 7, 4) || '-' || LEFT(date, 2) || '-' || SUBSTR(date, 4, 2) || ' ' || time || ':00')::TIMESTAMP + INTERVAL '1 week' AS ts_plus_weekFROM tutorial.sf_crime_incidents_2014_01;Задание 7
Заголовок раздела «Задание 7»Посчитайте инциденты по неделям. Таблица: tutorial.sf_crime_incidents_cleandate. Приведите неделю к DATE, чтобы убрать часы и минуты.
Проверка: 5 строк. Первая: 2013-12-30 — 1861. Последняя: 2014-01-27 — 1799.
Решение
SELECT DATE_TRUNC('week', cleaned_date)::DATE AS week_beginning, COUNT(*) AS incidentsFROM tutorial.sf_crime_incidents_cleandateGROUP BY 1ORDER BY 1;Задание 8
Заголовок раздела «Задание 8»Покажите, сколько времени прошло с каждого инцидента до сейчас. Считайте, что данные в тихоокеанском времени (UTC-8).
Проверка: 11969 строк. Колонка time_ago — интервал. Результат зависит от даты запуска. Для инцидента 2014-01-01 это больше 4000 дней.
Решение
SELECT incidnt_num, cleaned_date, now() AT TIME ZONE 'PST' AS now_pst, now() AT TIME ZONE 'PST' - cleaned_date AS time_agoFROM tutorial.sf_crime_incidents_cleandate;