relation does not exist означает, что PostgreSQL не нашёл объект с таким именем там, где искал. Слово «relation» покрывает таблицы, представления и последовательности разом. Причин у сообщения шесть, и опечатка среди них только первая. Всё дальше воспроизведено на PostgreSQL 16.11.
Данные те же, что в остальных статьях справочника: таблицы users и orders.
Синтаксис разобран, имя таблицы не найдено
Как читать сообщение
SELECT * FROM zakazy;
ERROR: relation "zakazy" does not exist
LINE 1: SELECT * FROM zakazy;
^
Три строки, и каждая полезна. В первой стоит имя, которое база искала, в том виде, в каком она его поняла. Во второй сам запрос, в третьей стрелка на место. Начинать разбор надо с кавычек в первой строке: если имя там выглядит не так, как ты писал, причина уже найдена.
Для колонок сообщение другое и часто идёт с подсказкой:
SELECT nam FROM users;
ERROR: column "nam" does not exist
LINE 1: SELECT nam FROM users;
^
HINT: Perhaps you meant to reference the column "users.name".
Строку HINT пропускают чаще всего, а она обычно и содержит ответ. PostgreSQL ищет похожие имена сам.
Когда колонка указана с префиксом таблицы, сообщение меняет форму и подсказки не будет:
SELECT u.amount FROM users u;
ERROR: column u.amount does not exist
LINE 1: SELECT u.amount FROM users u;
^
Имя без кавычек и без HINT означает, что база поняла запрос буквально: у таблицы за псевдонимом u такой колонки нет. Сумма лежит в orders, соединение не написано.
Таблица лежит в другой схеме
Схему в запросе обычно не пишут, и база ищет по search_path. Если таблица создана в другой схеме, её не видно:
SELECT * FROM orders_2025;
ERROR: relation "orders_2025" does not exist
LINE 1: SELECT * FROM orders_2025;
^
С явным указанием схемы тот же запрос отрабатывает:
SELECT count(*) FROM archive.orders_2025;
| count |
|---|
| 0 |
Проверить, где база вообще ищет, можно одной командой:
SHOW search_path;
| search_path |
|---|
| relart |
Отсюда типовой сюжет: коллега создал таблицу под своей ролью, она попала в его схему, а у тебя в search_path её нет. Лечится либо полным именем схема.таблица, либо правкой search_path.
Кавычки и регистр: самая дорогая причина
PostgreSQL приводит имена без кавычек к нижнему регистру. Имя в двойных кавычках сохраняется как есть. Отсюда следует, что таблица, созданная как "Orders", и запрос FROM Orders — это разные объекты.
Если таблицы в нижнем регистре нет, ошибка честно об этом скажет:
ERROR: relation "orders" does not exist
LINE 1: SELECT * FROM Orders;
^
Обрати внимание: в кавычках стоит orders строчными, хотя в запросе написано Orders. Это и есть отпечаток сворачивания регистра, главный признак того, что таблицу надо искать в кавычках.
Опаснее случай, когда таблица в нижнем регистре всё-таки есть. Тогда ошибки не будет вовсе:
SELECT id, amount FROM Orders ORDER BY id LIMIT 3;
| id | amount |
|---|---|
| 10 | 900 |
| 11 | 2400 |
| 12 | 1200 |
Запрос прочитал таблицу orders, а не "Orders", и ни одна строка об этом не сообщила. В кавычках он читает другую таблицу с другими колонками. Именно поэтому имена в кавычках считают плохой практикой: они создают два разных объекта, различимых только кавычками, и цена ошибки — молча неверный отчёт.
Из того же правила растёт и другая ошибка. Двойные кавычки в SQL — это имя, а не текст. Строку пишут одинарными:
SELECT * FROM users WHERE name = "Аня";
ERROR: column "Аня" does not exist
LINE 1: SELECT * FROM users WHERE name = "Аня";
^
HINT: Perhaps you meant to reference the column "users.id".
База искала колонку с именем «Аня». Подсказка тут бесполезна, зато само сообщение «column» вместо «relation» указывает на причину: в кавычках оказалось значение.
Псевдоним закрывает исходное имя
Как только у таблицы появился псевдоним, обращаться к ней по имени уже нельзя:
SELECT orders.amount FROM orders o;
ERROR: invalid reference to FROM-clause entry for table "orders"
LINE 1: SELECT orders.amount FROM orders o;
^
HINT: Perhaps you meant to reference the table alias "o".
Формулировка другая, а причина того же класса: имя не найдено там, где база его ищет. Подсказка называет решение прямо.
Алиас колонки не виден в WHERE
Имя, придуманное в SELECT, в WHERE ещё не существует:
SELECT amount * 2 AS doubled FROM orders WHERE doubled > 1000;
ERROR: column "doubled" does not exist
LINE 1: SELECT amount * 2 AS doubled FROM orders WHERE doubled > 100...
^
Причина в порядке выполнения: WHERE отрабатывает до SELECT, поэтому алиаса на тот момент нет. А вот ORDER BY идёт после и алиас видит:
SELECT amount * 2 AS doubled FROM orders ORDER BY doubled DESC LIMIT 2;
| doubled |
|---|
| 4800 |
| 2400 |
Лечится повтором выражения в WHERE или обёрткой запроса в подзапрос. Полный порядок выполнения предложений разобран в статье про ORDER BY, а асимметрия с HAVING — в статье про GROUP BY.
CTE живёт только внутри своего запроса
Временное имя из WITH существует до точки с запятой и ни секундой дольше:
WITH paid AS (SELECT * FROM orders WHERE status = 'paid')
SELECT count(*) FROM paid;
| count |
|---|
| 5 |
Следующий запрос уже его не видит:
SELECT count(*) FROM paid;
ERROR: relation "paid" does not exist
LINE 1: SELECT count(*) FROM paid;
^
Ошибка выглядит как пропавшая таблица, хотя таблицы никогда и не было. Подробно про область видимости и цепочки WITH — в статье про CTE.
Как найти, где лежит таблица
Когда причина неочевидна, спрашивают у самой базы. Системный каталог покажет все схемы, в которых встречается имя:
SELECT table_schema, table_name FROM information_schema.tables
WHERE table_name = 'orders_2025'
ORDER BY table_schema;
| table_schema | table_name |
|---|---|
| archive | orders_2025 |
Здесь же ловится и регистр: имя в каталоге хранится ровно так, как записано при создании, поэтому строка Orders в выдаче сразу объясняет, почему запрос без кавычек её не находит. В psql то же самое делают команды \dt *.* для таблиц и \dn для схем.
Порядок разбора
Шесть проверок по убыванию частоты:
- Прочитай имя в кавычках из текста ошибки. Оно отличается от написанного, значит дело в регистре или кавычках.
- Посмотри строку
HINT, если она есть. Для опечаток в колонках она обычно содержит готовый ответ. - Проверь
SHOW search_pathи поищи объект вinformation_schema.tables. - Убедись, что таблица не за псевдонимом, а колонка не за чужим префиксом.
- Проверь, не алиас ли это из
SELECT, использованный вWHERE. - Проверь, не CTE ли это из предыдущего запроса.
Частые вопросы
Почему запрос работал вчера, а сегодня нет
Чаще всего сменилось подключение: другая база, другая роль, другой search_path. Сравни SHOW search_path и SELECT current_database() в обоих сеансах.
Как проверить существование таблицы без ошибки
SELECT to_regclass('public.orders') возвращает NULL, если объекта нет, и не падает. Для условного удаления есть DROP TABLE IF EXISTS, а проверку наличия строк делает EXISTS, и это совсем другая задача.
Чем relation отличается от table в сообщении
Ничем важным для разбора. PostgreSQL называет relation всё табличное: таблицы, представления, материализованные представления, последовательности. Если имя занято представлением, а ты ждёшь таблицу, сообщения об ошибке не будет вовсе.
Почему PostgreSQL приводит имена к нижнему регистру
Так требует стандарт SQL, только он предписывает верхний регистр, а PostgreSQL исторически выбрал нижний. Практический вывод один: не создавай объекты в кавычках, тогда регистр перестанет иметь значение.
Что делать с ошибкой синтаксиса вместо этой
Это другой класс сообщений, и разбирается он иначе. Как читать near ... at line N и почему опечатку надо искать перед указанным местом, разобрано в статье про синтаксическую ошибку.
Где потренироваться
В пути «SQL для аналитиков» на Koddo запросы пишутся в браузере с автопроверкой, и все эти ошибки встречаются на живых данных: соединения без псевдонимов, алиасы в фильтрах, WITH из нескольких шагов. Проверить себя можно на задаче про повторные отклики.