Почему PDO::lastInsertId() в PostgreSQL возвращает 0 и как это исправить
При переходе с MySQL на PostgreSQL многие разработчики сталкиваются с неожиданным поведением метода PDO::lastInsertId(). В MySQL этот метод после INSERT возвращает ID последней добавленной записи без дополнительных параметров. В PostgreSQL же вызов $db->lastInsertId() всегда возвращает 0, даже если передать имя последовательности. Разберём причины и способы корректного получения идентификатора.
Причина возврата 0 в PostgreSQL
PostgreSQL в отличие от MySQL не хранит автоматически последний сгенерированный ID в глобальном состоянии. Метод lastInsertId() в PDO требует явного указания имени последовательности (sequence), связанной с колонкой SERIAL или BIGSERIAL. Если последовательность не создана автоматически или передано неверное имя, метод возвращает 0.
Типичная ошибка: неправильное имя последовательности
При создании таблицы с автоинкрементом PostgreSQL создаёт последовательность по шаблону имя_таблицы_имя_колонки_seq. Например, для таблицы user с колонкой id последовательность будет называться user_id_seq. Если вы передаёте другое имя, lastInsertId() не сможет найти последовательность.
Правильные способы получения ID после INSERT
Способ 1: Использование RETURNING (рекомендуется)
Наиболее надёжный и переносимый способ - добавить в запрос INSERT конструкцию RETURNING id. Тогда вы получите ID сразу в результате выполнения запроса:
$stmt = $db->prepare('INSERT INTO user (name) VALUES (?) RETURNING id');
$stmt->execute([$name]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$id = $row['id'];Этот подход не зависит от последовательностей и работает в PostgreSQL, Oracle, SQLite и других СУБД, поддерживающих RETURNING.
Способ 2: Корректный вызов lastInsertId() с именем последовательности
Если вы всё же хотите использовать lastInsertId(), убедитесь, что передаёте правильное имя последовательности. Для таблицы user с колонкой id типа SERIAL:
$db->prepare('INSERT INTO user (name) VALUES (?)')->execute([$name]);
$id = $db->lastInsertId('user_id_seq');Важно: последовательность user_id_seq должна существовать. Если вы создали таблицу без автоинкремента (например, просто INTEGER), последовательности не будет, и метод вернёт 0.
Способ 3: Получение текущего значения последовательности
Как альтернатива, можно явно запросить значение последовательности через SQL-запрос:
$db->prepare('INSERT INTO user (name) VALUES (?)')->execute([$name]);
$stmt = $db->query("SELECT currval('user_id_seq')");
$id = $stmt->fetchColumn();Но учтите: currval() возвращает последнее значение, сгенерированное в текущей сессии. Если вы вставили несколько записей, оно вернёт последнее.
Проверка существования последовательности
Если вы не уверены в имени последовательности, выполните запрос к системному каталогу PostgreSQL:
SELECT sequence_name FROM information_schema.sequences WHERE sequence_schema = 'public';Или для конкретной таблицы:
SELECT pg_get_serial_sequence('user', 'id');Этот запрос вернёт полное имя последовательности, например public.user_id_seq.
Заключение
Главная причина, по которой PDO::lastInsertId() возвращает 0 в PostgreSQL, - неверное или отсутствующее имя последовательности. Самый надёжный способ избежать этой проблемы - использовать конструкцию RETURNING id в запросе INSERT. Это упрощает код и делает его независимым от особенностей конкретной СУБД. Если по каким-то причинам вы не можете изменить запрос, всегда проверяйте имя последовательности через pg_get_serial_sequence().