sql >> Databasteknik >  >> RDS >> PostgreSQL

En översikt över den seriella pseudodatatypen för PostgreSQL

Introduktion

PostgreSQL tillhandahåller naturligt en rik mångfald av datatyper som stöder många praktiska användningsfall. Den här artikeln introducerar den speciella implementeringen av seriella datatyper som vanligtvis används för att skapa syntetiska primärnycklar.

Unika nycklar

En grundläggande princip för databasdesignteorin är att varje tuppel (dvs. rad) i en relation (dvs. tabell) måste identifieras unikt från andra tuplar. Attributen, eller kolumnerna, som tillsammans tydligt identifierar en tupel från alla andra kallas en "nyckel". Vissa purister hävdar att alla modellerade objekt eller koncept i sig har ett attribut eller en uppsättning attribut som kan fungera som en nyckel och att det är viktigt att identifiera denna uppsättning nyckelattribut och använda dem för det unika urvalet av tuplar.

Men rent praktiskt kan det vara opraktiskt att identifiera en tillräckligt stor uppsättning attribut som garanterar unikhet för ett modellerat objekt, och därför vänder sig utvecklare ofta till syntetiska nycklar som ett surrogat för implementeringar i verkliga världen. Det vill säga, snarare än att förlita sig på någon kombination av faktiska attribut, definieras ett värde internt i databasen, typiskt ökade heltalsvärden och i övrigt utan fysisk betydelse som en nyckel. Förutom enkelheten med en enkel kolumnnyckel betyder det faktum att det inte finns något verkligt beroende att externa faktorer aldrig kan tvinga fram ett behov av att ändra värdet, som till exempel kan vara fallet om en persons namn används som en nyckel ... och sedan gifte sig personen eller gick in i ett federalt vittnesskyddsprogram och bytte namn. Till och med vissa värden som vanligtvis av lekmän anses vara unika och oföränderliga, såsom det amerikanska personnummer, är ingetdera:en person kan få ett nytt SSN, och SSN:er återanvänds ibland.

Deklarera en seriell datatyp

PostgreSQL tillhandahåller en speciell datatypdeklaration för att tillfredsställa detta behov av syntetiska nycklar. Att deklarera en databastabellkolumn som typ SERIAL uppfyller kravet på syntetiska nycklar genom att tillhandahålla unika heltal vid insättningar av nya tupler. Denna pseudodatatyp implementerar en heltalsdatatypkolumn med ett tillhörande standardvärde som härletts via ett funktionsanrop som tillhandahåller inkrementerade heltalsvärden. Kör följande kod för att skapa en enkel tabell med en id-kolumn av typen seriell:

CREATE TABLE person (id serial, full_name text);
actually executes the following DDL
CREATE TABLE person (
    id integer NOT NULL,
    full_name text
);

CREATE SEQUENCE person_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;
ALTER SEQUENCE person_id_seq OWNED BY person.id;
ALTER TABLE ONLY person
    ALTER COLUMN id
    SET DEFAULT nextval('person_id_seq'::regclass);

Det vill säga, nyckelordet "seriell" som en datatypsspecifikation innebär exekvering av DDL-satser som skapar en heltalstypskolumn med en NOT NULL-begränsning, en SEQUENCE, och sedan ändras kolumnstandarden för att anropa en inbyggd funktion som får åtkomst till den SEQUENCE.

Den inbyggda funktionen nextval utför en autoinkrementtjänst:varje gång nextval kallas ökar den den angivna sekvensräknaren och returnerar det nyinkrementerade värdet.

Du kan se resultatet av denna effekt genom att undersöka tabelldefinitionen:

postgres=# \d person
                   Table "public.person"
  Column   |  Type   |         Modifiers
-----------+---------+-----------------------------------------
 Id        | integer | not null default nextval('person_id_seq'::regclass)
 full_name | text    |

Infoga serievärden

För att använda den automatiska ökningsfunktionen infogar vi helt enkelt rader, beroende på standardvärdet för seriekolumnen:

INSERT INTO person (full_name) VALUES ('Alice');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
(1 row)

Vi ser att ett värde för id-kolumnen som motsvarar den nya "Alice"-raden har genererats automatiskt. Alternativt kan man använda nyckelordet DEFAULT om man uttryckligen vill lista alla kolumnnamn:

INSERT INTO person (id, full_name) VALUES (DEFAULT, 'Bob');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
(2 rows)

där vi ser den automatiska ökningsfunktionen mer uppenbart, och tilldelar värdet seriellt nästa till den nya raden för den andra infogningen av "Bob".

Att infoga flera rader fungerar till och med:

INSERT INTO person (full_name) VALUES ('Cathy'), ('David');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  3 | Cathy
  4 | David
(4 rows)
Ladda ner Whitepaper Today PostgreSQL Management &Automation med ClusterControlLäs om vad du behöver veta för att distribuera, övervaka, hantera och skala PostgreSQLDladda Whitepaper

Serialvärden saknas

Den inbyggda nextval-funktionen är optimerad för icke-blockerande applikationer med hög samtidighet och respekterar därför inte återställning. Följaktligen betyder detta att det kan saknas värden i sekvensen. Nedan återställer vi en infogning, men ser sedan att en efterföljande infogning får ett nytt värde som hoppar över värdet som skulle ha associerats med den avbrutna transaktionen:

BEGIN TRANSACTION;
INSERT INTO person (full_name) VALUES ('Eve');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  3 | Cathy
  4 | David
  5 | Eve
(5 rows)
ROLLBACK;
INSERT INTO person (full_name) VALUES ('Fred');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  3 | Cathy
  4 | David
  6 | Fred
(5 rows)

Fördelen med att inte respektera återställningar är att andra sessioner som försöker samtidigt infoga inte blockeras av andra infogningssessioner.

Ett annat sätt att sluta med saknade värden är om rader raderas:

DELETE FROM person WHERE full_name = 'Cathy';
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  6 | Fred
(4 rows)

Observera att även efter radering av den senast infogade raden som motsvarar det största auto-increment id-kolumnvärdet, återgår inte sekvensräknaren, dvs. även om efter radering av raden som motsvarar 'Fred', för efterföljande infogning, kvarstår sekvensräknaren fortfarande det tidigare kända största värdet och inkrement därifrån:

DELETE FROM person WHERE full_name = 'Fred';
INSERT INTO person (full_name) VALUES ('Gina');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  7 | Gina
(4 rows)

Luckor eller saknade värden som visas ovan anses enligt uppgift vara ett problem av vissa applikationsutvecklare eftersom det på PostgreSQL General e-postlistan finns en långsam men stadig upprepning av frågan hur man undviker sekvensluckor när man använder den seriella pseudodatatypen. Ibland finns det inga faktiska underliggande affärskrav, det handlar bara om personlig motvilja mot saknade värderingar. Men det finns omständigheter när det är ett verkligt behov att förhindra saknade nummer, och det är ämnet för en efterföljande artikel.

NEJ DU KAN INTE - JA DU KAN!

NOT NULL-begränsningen som tillskrivs av den seriella pseudodatatypen skyddar mot infogning av NULL för id-kolumnen genom att avvisa sådana infogningsförsök:

INSERT INTO person (id, full_name) VALUES (NULL, 'Henry');
ERROR:  null value in column "id" violates not-null constraint
DETAIL:  Failing row contains (null, Henry).

Således är vi säkra på att ha ett värde för det attributet.

Ett problem som vissa människor stöter på är att, som deklarerats ovan, ingenting hindrar explicit infogning av värden, och kringgår standardvärdet för autoinkrement som härleds via anrop av nästaval-funktionen:

INSERT INTO person (id, full_name) VALUES (9, 'Ingrid');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  7 | Gina
  9 | Ingrid
(5 rows)

Men sedan två insättningar senare med standarden producerar ett duplikatvärde för id-kolumnen om det inte finns någon kontroll av kolumnvärden mot sekvensvärdet:

INSERT INTO person (full_name) VALUES ('James');
INSERT INTO person (full_name) VALUES ('Karen');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  7 | Gina
  9 | Ingrid
  8 | James
  9 | Karen
(7 rows)

Om vi ​​i själva verket använde kolumnen seriell id som en nyckel, skulle vi ha deklarerat den som en PRIMÄR KEY eller åtminstone skapat ett UNIKT INDEX. Hade vi gjort det, skulle 'Karen'-inlägget ovan ha misslyckats med ett dubblettnyckelfel. Den senaste versionen av PostgreSQL innehåller en ny syntax för begränsningsdeklaration "genererad som standard som identitet" som undviker denna fallgrop och några andra äldre problem relaterade till den seriella pseudodatatypen.

Sekvensmanipuleringsfunktioner

Förutom nästaval-funktionen som vi redan nämnt som flyttar fram sekvensen och returnerar det nya värdet, finns det några andra funktioner för att fråga och ställa in sekvensvärdena:currval-funktionen returnerar det värde som senast erhållits med nextval för specificerad sekvens, lastval-funktionen returnerar det senast erhållna värdet med nextval för valfri sekvens, och setval-funktionen ställer in en sekvenss aktuella värde. Dessa funktioner anropas med enkla frågor, till exempel

SELECT currval('person_id_seq');
 currval
---------
       9
(1 row)

Och observera att om ett anrop görs till nästaval-funktionen oberoende av att en infogning faktiskt utförs, ökar den sekvensen, och det kommer att återspeglas i efterföljande infogning:

SELECT nextval('person_id_seq');
 nextval
---------
      10
(1 row)
INSERT INTO person (full_name) VALUES ('Larry');
SELECT * FROM person;
 id | full_name
----+-----------
  1 | Alice
  2 | Bob
  4 | David
  7 | Gina
  9 | Ingrid
  8 | James
  9 | Karen
 11 | Larry
(8 rows)

Slutsats

Vi har introducerat en grundläggande förståelse för PostgreSQL SERIAL pseudo-datatypen för automatiskt inkrementerade syntetiska nycklar. Som illustration i den här artikeln använde vi SERIAL-typdeklarationen, som skapar en 4-byte heltalskolumn. PostgreSQL tillgodoser olika intervallbehov med pseudodatatyperna SMALLSERIAL och BIGSERIAL för 2-byte respektive 8-byte kolumnstorlekar. Leta efter en framtida artikel om ett sätt att ta itu med behovet av sekvenser utan att sakna värden.


  1. Liquibase lås - anledningar?

  2. Hur man väljer den första raden i varje GRUPP FÖR Grupp

  3. Hur får jag den primära nyckeln/nycklarna till en tabell från Postgres via plpgsql?

  4. PostgreSQL-strömning vs logisk replikering – jämförelse