Почему 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().

    Часто задаваемые вопросы