Wie verwende ich currval () in PostgreSQL, um die zuletzt eingefügte ID zu erhalten?

64

Ich habe einen Tisch:

CREATE TABLE names (id serial, name varchar(20))

Ich möchte die "zuletzt eingefügte ID" aus dieser Tabelle, ohne RETURNING idbeim Einfügen zu verwenden. Es scheint eine Funktion zu geben CURRVAL(), aber ich verstehe nicht, wie man sie benutzt.

Ich habe versucht mit:

SELECT CURRVAL() AS id FROM names_id_seq
SELECT CURRVAL('names_id_seq')
SELECT CURRVAL('names_id_seq'::regclass)

aber keiner von ihnen funktioniert. Wie kann ich currval()die zuletzt eingegebene ID ermitteln?

Jonas
quelle
2
Leser dieses Problems / dieser Lösung sollten sich bewusst sein, dass von der Verwendung von currval () generell abgeraten wird, da die RETURNING-Klausel den Bezeichner ohne den Overhead der zusätzlichen Abfrage und ohne die Möglichkeit der Rückgabe des WRONG VALUE (was currval in tun wird) bereitstellt Einige Anwendungsfälle.)
Chander
1
@chander: Hast du eine Referenz für diese Behauptung? Die Verwendung von currval() wird auf keinen Fall entmutigt.
a_horse_with_no_name
Vielleicht ist es eine Frage der Meinung, ob die Verwendung von currval nicht empfohlen wird, aber in bestimmten Fällen sollten Benutzer sich darüber im Klaren sein, dass dies einen Wert liefert, der nicht Ihren Erwartungen entspricht Haben Sie Tabelle A, die Sequenz a_seq verwendet, und Tabelle B, die auch a_seq verwendet (Aufruf von nextval ('a_seq') für die PK-Spalte.) Angenommen, Sie haben auch einen Trigger (a_trg), der in Tabelle B ON INSERT in Tabelle A einfügt In diesem Fall gibt die Funktion currval () (nach dem Einfügen in Tabelle A) die Nummer zurück, die für das Einfügen in Tabelle B und nicht in Tabelle A
generiert wurde

Antworten:

51

Wenn Sie eine Spalte als serialPostgreSQL erstellen, wird automatisch eine Sequenz dafür erstellt.

Der Name der Sequenz wird automatisch generiert und lautet immer tablename_columnname_seq. In Ihrem Fall handelt es sich bei der Sequenz um Namen names_id_seq.

Nach dem Einfügen in die Tabelle können Sie currval()mit diesem Sequenznamen aufrufen :

postgres=> CREATE TABLE names in schema_name (id serial, name varchar(20));
CREATE TABLE
postgres=> insert into names (name) values ('Arthur Dent');
INSERT 0 1
postgres=> select currval('names_id_seq');
 currval
---------
       1
(1 row)
postgres=>

Anstatt den Sequenznamen fest zu codieren, können Sie auch Folgendes verwenden pg_get_serial_sequence():

select currval(pg_get_serial_sequence('names', 'id'));

Auf diese Weise müssen Sie sich nicht auf die von Postgres verwendete Namensstrategie verlassen.

Oder wenn Sie den Sequenznamen überhaupt nicht verwenden möchten, verwenden Sie lastval()

ein Pferd ohne Name
quelle
Ich denke, es ist nicht gut, currval()in einem Mehrbenutzer-Setup zu verwenden. ZB auf einem Webserver.
Jonas
9
Nein, Sie irren sich. currval()ist "lokal" für Ihre aktuelle Verbindung. Daher ist die Verwendung in einer Mehrbenutzerumgebung kein Problem. Das ist der ganze Zweck einer Sequenz.
a_horse_with_no_name
1
Dies könnte in dem (recht seltenen) Fall, dass Ihre Einfügung mehr Einfügungen in derselben Tabelle auslöst, scheitern.
Leonbloy
1
@a_horse_with_no_name: Ich dachte nicht an mehrere Einfügungen (das ist leicht zu erkennen), sondern an (möglicherweise unbekannte) Trigger, die in der Tabelle definiert sind. Versuchen Sie zum Beispiel Folgendes
leonbloy
1
Es ist nicht IMMER der Name der Tabelle und der Spalte. Wenn bereits eine Sequenz mit diesem Namen vorhanden ist, wird durch Verketten oder Erhöhen einer Zahl am Ende eine neue Sequenz generiert. Der Name muss möglicherweise gekürzt werden, wenn er die Obergrenze überschreitet (die 62 Zeichen zu betragen scheint).
PhilHibbs
47

Dies geht direkt vom Stapelüberlauf aus

Wie von @a_horse_with_no_name und @Jack Douglas hervorgehoben, funktioniert currval nur mit der aktuellen Sitzung. Wenn Sie also damit einverstanden sind, dass das Ergebnis durch eine nicht festgeschriebene Transaktion einer anderen Sitzung beeinflusst wird, und Sie dennoch etwas möchten, das über mehrere Sitzungen hinweg funktioniert, können Sie Folgendes verwenden:

SELECT last_value FROM your_sequence_name;

Verwenden Sie den Link zu SO für weitere Informationen.

Aus der Dokumentation von Postgres geht jedoch eindeutig hervor, dass

Es ist ein Fehler, lastval aufzurufen, wenn nextval in der aktuellen Sitzung noch nicht aufgerufen wurde.

Eigentlich müssten Sie also so etwas tun, um currval oder last_value für eine sitzungsübergreifende Sequenz richtig zu verwenden?

SELECT setval('serial_id_seq',nextval('serial_id_seq')-1);

Vorausgesetzt natürlich, Sie haben in der aktuellen Sitzung keine Einfügung oder andere Möglichkeit, das serielle Feld zu verwenden.

Slak
quelle
3
Ich kann mir keine Situation vorstellen, in der dies nützlich wäre.
ypercubeᵀᴹ
Ich habe mich nur gefragt, ob dies eine Möglichkeit ist, currval abzurufen, wenn nextval in der aktuellen Sitzung nicht aufgerufen wurde. Irgendwelche Vorschläge dann?
Slak
Ich musste dies tun, als ich Primärschlüssel für generierte Fixture-Daten fest codierte, wobei ich wollte, dass die PK-Werte, die normalerweise inkrementell generiert werden, im Voraus bestimmt werden, um das Testen des Clients zu vereinfachen. Um Einfügungen in einer Spalte zu unterstützen, für die normalerweise der Standardwert von a gilt, müssen nextval()Sie die Reihenfolge manuell so einstellen, dass sie mit der Anzahl der Fixture Records übereinstimmt, die Sie mit den fest codierten IDs eingefügt haben. Um das Problem zu lösen, dass currval () / lastval () nicht vor nextval verfügbar ist, müssen Sie SELECTdie Sequenz direkt eingeben .
Peter M. Elias
@ypercubeᵀᴹ Im Gegenteil, mir fällt kein guter Grund ein, die gewählte "richtige" Antwort zu verwenden. Für diese Antwort muss kein Datensatz in die Tabelle eingefügt werden. Dies beantwortet auch die Frage. Auch hier kann ich mir keinen guten Grund vorstellen, diese Antwort NICHT über die gewählte Antwort zu stellen.
Henley Chiu
1
Bei dieser Antwort wird die Tabelle überhaupt nicht geändert. Der Überprüfte tut es. Idealerweise möchten Sie nur die letzte ID kennen, ohne irgendwelche Änderungen vorzunehmen. Was ist, wenn dies eine Produktionsdatenbank ist? Sie können nicht einfach eine zufällige Zeile ohne mögliche Auswirkungen einfügen. Somit ist diese Antwort sowohl sicherer als auch korrekt.
Henley Chiu
14

Sie müssen nextvaldiese Sequenz in dieser Sitzung aufrufen, bevor Siecurrval :

create sequence serial;
select nextval('serial');
 nextval
---------
       1
(1 row)

select currval('serial');
 currval
---------
       1
(1 row)

Daher können Sie die 'zuletzt eingefügte ID' in der Sequenz nicht finden, es sei denn, insertdies erfolgt in derselben Sitzung (eine Transaktion wird möglicherweise zurückgesetzt, die Sequenz jedoch nicht).

Wie in der Antwort von a_horse ausgeführt, wird create tablemit einer Spalte vom Typ serialautomatisch eine Sequenz erstellt und verwendet, um den Standardwert für die Spalte zu generieren, sodass insertnormalerweise nextvalimplizit auf Folgendes zugegriffen wird :

create table my_table(id serial);
NOTICE:  CREATE TABLE will create implicit sequence "my_table_id_seq" for 
         serial column "my_table.id"

\d my_table
                          Table "stack.my_table"
 Column |  Type   |                       Modifiers
--------+---------+-------------------------------------------------------
 id     | integer | not null default nextval('my_table_id_seq'::regclass)

insert into my_table default values;
select currval('my_table_id_seq');
 currval
---------
       1
(1 row)
Jack Douglas
quelle
3

Es gibt also einige Probleme mit diesen verschiedenen Methoden:

Currval ruft nur den letzten in der aktuellen Sitzung generierten Wert ab. Dies ist hilfreich, wenn Sie keine anderen Werte generieren, aber in Fällen, in denen Sie möglicherweise einen Trigger aufrufen und / oder die Sequenz in der aktuellen Transaktion mehrmals vorverlegt haben, ist dies der Fall Es wird nicht der richtige Wert zurückgegeben. Das ist kein Problem für 99% der Leute da draußen - aber es ist etwas, das man berücksichtigen sollte.

Der beste Weg, den eindeutigen Bezeichner nach einer Einfügeoperation zu erhalten, ist die Verwendung der RETURNING-Klausel. Im folgenden Beispiel wird davon ausgegangen, dass die an die Sequenz gebundene Spalte "id" heißt:

insert into table A (cola,colb,colc) values ('val1','val2','val3') returning id;

Beachten Sie, dass die Nützlichkeit der RETURNING-Klausel weit über das Abrufen der Sequenz hinausgeht, da sie auch Folgendes bewirkt:

  • Rückgabewerte, die für die "endgültige Einfügung" verwendet wurden (nachdem beispielsweise ein BEFORE-Trigger die einzufügenden Daten möglicherweise geändert hat).
  • Liefert die gelöschten Werte:

    aus Tabelle A löschen, wobei ID> 100 zurückgibt *

  • Gibt die geänderten Zeilen nach einem UPDATE zurück:

    Tabelle aktualisieren A setze X = 'y' wobei blah = 'blech' zurückkehrt *

  • Verwenden Sie das Ergebnis eines Löschvorgangs für ein Update:

    WITH A as (lösche * aus Tabelle A als Rückgabe-ID) Update B set deleted = true wobei ID in (wähle ID aus A);

Chander
quelle
Natürlich sagte das OP ausdrücklich, dass sie keine RETURNINGKlausel verwenden wollten - aber es ist nichts Falsches daran, die Vorteile der Verwendung für andere klarer zu machen.
RDFozz
Ich habe wirklich nur versucht, die Fallstricke der verschiedenen anderen Methoden aufzuzeigen (insofern, als wenn man nicht aufpasst, dass ein anderer Wert als der erwartete zurückgegeben wird) - und die beste Vorgehensweise zu klären.
Chander
1

Ich musste eine Abfrage ausführen, obwohl ich SQLALchemy verwendet habe, da ich currval nicht erfolgreich eingesetzt habe.

nextId = db.session.execute("select last_value from <table>_seq").fetchone()[0] + 1

Dies war ein Python-Kolben + Postgresql-Projekt.

Jalal
quelle
1

Wenn Sie den Wert der Sequenzen erhalten möchten, ohne aufgerufen zu nextval()haben und ohne die Sequenz zu ändern, habe ich eine PL / pgSQL-Funktion zusammengestellt, um damit umzugehen : Ermitteln Sie den Wert aller Datenbanksequenzen

Christophe
quelle
1

Sie müssen das GRANTSchema folgendermaßen verwenden:

GRANT USAGE ON SCHEMA schema_name to user;

und

GRANT ALL PRIVILEGES ON schema_name.sequence_name TO user;
AMML
quelle
1
Willkommen bei dba.se! Prost auf deinen ersten Beitrag! Es sieht nicht so aus, als ob der ursprüngliche Beitrag speziell die Berechtigungen betrifft. Erwägen Sie möglicherweise, Ihre Antwort so zu erweitern, dass sie einige Details zu Berechtigungsfehlern enthält, wenn Sie die currval()Funktion aufrufen , um sie für diesen Thread relevanter zu machen?
Peter Vandivier
0

In PostgreSQL 11.2 können Sie die Sequenz wie eine Tabelle behandeln:

Beispiel, wenn Sie eine Sequenz mit dem Namen "names_id_seq" haben

select * from names_id_seq;
 last_value | log_cnt | is_called
------------+---------+-----------
          4 |      32 | t
(1 row)

Das sollte Ihnen die letzte eingegebene ID geben (in diesem Fall 4), was bedeutet, dass der aktuelle Wert (oder der Wert, der für die nächste ID verwendet werden soll) 5 sein sollte.

Scifisamurai
quelle
-2

Verschiedene Versionen von PostgreSQL verfügen möglicherweise über verschiedene Funktionen, um die aktuelle oder nächste Sequenz-ID abzurufen.

Zunächst müssen Sie die Version Ihres Postgres kennen. Verwenden der ausgewählten Version (); um die version zu bekommen.

In PostgreSQL 8.2.15 erhalten Sie die aktuelle Sequenz-ID mit select last_value from schemaName.sequence_name.

Wenn die obige Anweisung nicht funktioniert, können Sie verwenden select currval('schemaName.sequence_name');

Robin
quelle
2
Gibt es Beweise für verschiedene Versionen, die das anders machen?
Dezso