+4

Como obter o valor da última chave primária inserida automaticamente em BD PostgreSQL?

criado por Rubens Takiguti Ribeiro em 04/12/2009 2:16pm
O BD PostgreSQL oferece uma sintaxe de INSERT que possibilita retornar o valor inserido. Basta colocar "RETURNING nome_da_pk" no final da SQL.

Por exemplo, se temos uma tabela de usuários com os campos id (chave primária auto incrementável), login e senha, basta executarmos a SQL:

INSERT INTO usuarios (login, senha) VALUES (..., ...) RETURNING id;

Com isso, a SQL fará a inserção e devolverá um resultado assim como um SELECT. Para obter este valor, depende da forma de conexão com o BD.

Usando funções nativas do PostgreSQL, seria algo assim:

$result = pg_query($sql);
if (is_resource($result)) {
$obj = pg_fetch_object($result);
$ultimo_id = $obj->id;
}

Usando PDO, seria algo semelhante a isso:
try {
$resultset = $pdo->query($sql);
$obj = $resultset->fetchObject();
$ultimo_id = $obj->id;
} catch (...)

A sintaxe permite, ainda, a inclusão de alias do nome retornado. Logo, se quiser renomear o campo retornado, basta fazer:

INSERT INTO usuarios (login, senha) VALUES (..., ...) RETURNING id AS pk;

E o resultado obtido será voltado na coluna chamada "pk".

Lista de Respostas:

0
04/12/2009 5:19pm
(~7 anos atrás)
Rubens, no php tem uma função que faz isso para o MYSQL mysql_insert_id(), eu não sei se tem para o POSTGRESQL, se tiver é uma boa, mais esta explicação esta de parabens.

Falou.

0
07/12/2009 3:20pm
(~7 anos atrás)
Ricardo Gama respondeu:
No postgre não tem uma função que nem o mysql_insert_id. A única forma de retornar a chave primária que é um sequence, é da forma que o Rubens explicou ou então fazendo o seguinte sql:

$sql = "select currval('public.nome_da_sequencia'::text) AS valor";
$result = pg_query($sql);
if (is_resource($result)) {
$obj = pg_fetch_object($result);
$ultimo_id = $obj->valor;
}
Bom, esse exemplo mostra já contando uma classe criada e interando com a ADODB.

Outro alerta é que o currval só funciona se tiver acontecido um insert antes.

Abcs, Rico

0
07/12/2009 6:23pm
(~7 anos atrás)
Ricardo, esta alternativa que mostrou é válida, mas não me parece 100% segura se não for utilizado o modo de transação adequado. Quando estamos tratando de um sistema com acesso concorrente ao BD (muitos usuários acessando ao mesmo tempo), pode acontecer de dois usuários inserirem dados na mesma tabela quase que no mesmo instante. Cada um deles precisa fazer um insert e um select logo em seguida. Mas nem sempre o select acontece imediatamente após o insert. Pode ser que, neste meio tempo, algum outro usuário já tenha feito um outro insert, o que atrapalha o select posterior.

A situação desejável seria essa:
Usuário 1: Realiza o insert do registro 45
Usuário 1: Realiza o select do último insert (45) valor correto
Usuário 2: Realiza o insert do registro 46
Usuário 2: Realiza o select do último insert (46) valor correto

Mas pode ocorrer isso:
Usuário 1: Realiza o insert do registro 45
Usuário 2: Realiza o insert do registro 46
Usuário 1: Realiza o select do último insert (46) valor errado
Usuário 2: Realiza o select do último insert (46) valor correto

Como cada requisição roda em "paralelo", mas o acesso ao BD é único, então a forma de contornar isso é abrindo uma transação com modo SERIALIZABLE, já que a transação envolve uma inserção e uma leitura.

O modo de transação SERIALIZABLE coloca a transação inteira em forma sequencial. Então ela ocorre por completo ou então não ocorre. Isso garante que sempre tenhamos a situação desejável.

No caso do PostgreSQL, esta adaptação da SQL de INSERT é extremamente útil, já que ela trata tudo que comentei. Logo, espera-se que o PostgreSQL sempre devolva o valor inserido, DE FATO, no momento da inserção.

0
08/12/2009 8:48am
(~7 anos atrás)
Marcos Regis respondeu:
O PHP tem uma função que trabalha com Postgre da mesma forma que o mysql_insert_id que é pg_last_oid.

O campo OID tornou-se opcional a partir do PostgreSQL 7.2. Quando um campo OID não é definido em uma tabela, o programador deve usar pg_result_status() para checar se o registro foi inserido com sucesso ou não.

Quanto as transações, se o controle dos indices ficar a cargo do BD não há inserção em concorrência e somente um código mal feito não traria o índice correto.

Por exemplo, se a tabela possui um campo Sequence e vc sempre usar a instrução NEXTVAL os índices nunca serão duplicados e visto que este valor se mantém dentro da sessão conectada o retorno nunca seria errôneo, mesmo em centenas de isntruções concorrentes.

O PDO possui métodos específicos para trabalhar com SEQUENCES
PDO::lastInsertId quando executado sem parâmetros em uma tabela que retorne um inteiro representando o índice irá retorná-lo de forma correta.
Para índices retornados por SEQUENCES basta passar o SEQUENCE como argumento.

Uma coisa interessante no uso de sequences é que é sempre aconselhado não utilizar triggers para controle de inserção quando se usa ORMs. Um DBA vai dizer que está errado mas ele, neste caso, é só um DBA.

0
08/12/2009 8:49am
(~7 anos atrás)
Marcos Regis respondeu:
Ahh.. vale lembrar que todos estes métodos não são do PHP realmente e sim do Bando de Dados. (mysql_insert_id, SEQUENCE.NEXTVAL, etc)

0
14/12/2009 10:35am
(~7 anos atrás)
Tomitas respondeu:
Rubens, bacana essa explicação, pois trabalho com Postgres tbm e sempre fiz o insert no banco e logo após o select max(id) from tabela. Sempre deu certo tbm, mas se for pensar, são duas querys que eu rodo, desta forma sua, apenas uma. Vivendo e aprendendo. Valew

0
12/02/2011 2:31pm
(~5 anos atrás)
Marcelo Santos respondeu:
ótimo artigo
preciso incrementar o meu site abaixo
http://www.familiahost.com
se vc tiver alguma dica me envie algo
no email marpapelaria@gmail.com

obrigado

-1
20/12/2009 5:09pm
(~7 anos atrás)
Diego respondeu:
Use: SELECT CURRVAL ('nome_da_seq')

Nova Resposta:

(Você pode usar tags como <b>, <i> ou <code>. URLs serão convertidas para links automaticamente.)