Числа не всегда хранятся в базе данных как числа. Они могут находиться внутри текста. Посмотри на time_offset
таблицы timezone
:
SELECT *
FROM timezone
# | timezone_id | time_offset |
---|---|---|
1 | 1 | UTC+2 |
2 | 2 | UTC+3 |
3 | 3 | UTC+4 |
4 | 4 | UTC+5 |
5 | 5 | UTC+6 |
... | ... | ... |
Например, мы хотим получить все часовые зоны со смещением относительно UTC не более 5 часов.
Разобьем задачу на части:
- Получим сдвиг относительно UTC числом;
- Ограничим 5 часами.
Получим сдвиг относительно UTC числом
Мы знаем, что в поле time_offset
данные хранятся в формате UTC+СДВИГ_В_ЧАСАХ
. Возьмем подстроку, начиная с 5-го символа:
SELECT t.timezone_id,
t.time_offset,
right(t.time_offset, -4) AS hours
FROM timezone t
# | timezone_id | time_offset | hours |
---|---|---|---|
1 | 1 | UTC+2 | 2 |
2 | 2 | UTC+3 | 3 |
3 | 3 | UTC+4 | 4 |
4 | 4 | UTC+5 | 5 |
5 | 5 | UTC+6 | 6 |
6 | 6 | UTC+7 | 7 |
7 | 7 | UTC+8 | 8 |
8 | 8 | UTC+10 | 10 |
9 | 9 | UTC+1 | 1 |
10 | 10 | UTC+9 | 9 |
Ограничим 5 часами
Получив сдвиг в часах, мы сделали почти всю работу. Осталось лишь добавить WHERE right(t.time_offset, -4) <= 5
:
SELECT t.timezone_id,
t.time_offset,
right(t.time_offset, -4) AS hours
FROM timezone t
WHERE right(t.time_offset, -4) <= 5
error: operator does not exist: text <= integer
Упс... Что-то пошло не так...
Postgres говорит, что нельзя сравнивать текст с числом.
Для исправления ошибки нужно преобразовать подстроку, полученную right(t.time_offset, -4)
, к типу данных integer
(целые числа). Сделать это можно одним из способов:
- использовав конструкцию
::
- вызвав функцию
cast()
Конструкция ::
Чтобы преобразовать значение к другому типу данных достаточно после значения поставить ::
и написать требуемый тип данных. Например:
SELECT '123'::integer
# | int4 |
---|---|
1 | 123 |
Посмотрим на типы данных в нашем запросе:
SELECT pg_typeof (t.time_offset) AS "type time_offset",
pg_typeof (right(t.time_offset, -4)) AS "type number substring",
pg_typeof (right(t.time_offset, -4)::integer) AS "type with ::integer",
pg_typeof ('12'::integer) AS "type '12'::integer",
pg_typeof (current_timestamp) AS "type current_timestamp"
FROM timezone t
LIMIT 1
# | type time_offset | type number substring | type with ::integer | type '12'::integer | type current_timestamp |
---|---|---|---|---|---|
1 | text | text | integer | integer | timestamp with time zone |
Доделаем работу до конца
SELECT t.timezone_id,
t.time_offset,
right(t.time_offset, -4)::integer AS hours
FROM timezone t
WHERE right(t.time_offset, -4)::integer <= 5
ORDER BY hours
# | timezone_id | time_offset | hours |
---|---|---|---|
1 | 9 | UTC+1 | 1 |
2 | 1 | UTC+2 | 2 |
3 | 2 | UTC+3 | 3 |
4 | 3 | UTC+4 | 4 |
5 | 4 | UTC+5 | 5 |
Функция cast
Функция cast
делает ровно то же самое. Синтаксис
cast (значение AS тип данных)
Например:
SELECT cast('123' AS integer)
# | int4 |
---|---|
1 | 123 |
SELECT t.timezone_id,
t.time_offset,
cast(right(t.time_offset, -4) AS integer) AS hours
FROM timezone t
WHERE cast(right(t.time_offset, -4) AS integer) <= 5
ORDER BY hours
# | timezone_id | time_offset | hours |
---|---|---|---|
1 | 9 | UTC+1 | 1 |
2 | 1 | UTC+2 | 2 |
3 | 2 | UTC+3 | 3 |
4 | 3 | UTC+4 | 4 |
5 | 4 | UTC+5 | 5 |
P.S. Функция cast
включена в стандарт SQL и доступна в СУБД других производителей. Синтаксис через ::
является историческим для PostgreSQL. На практике чаще используют ::
, потому что так короче.