Come si fa a creare un numero di revisione incremento automatico unica di una chiave in PGSQL?

StackOverflow https://stackoverflow.com/questions/912057

Domanda

Supponendo che ho le seguenti tabelle.

PARENT: PARENT_ID serial, DESCRIPTION character varying(50)

CHILD: PARENT_ID integer, CHILD_ID integer, DESCRIPTION character varying(50)

Quello che vorrei vedere è ogni riga BAMBINO avere un CHILD_ID che inizia a 1 e con incrementi di 1, unico per ogni PARENT_ID. Sarebbe simile ad un numero di revisione. Per esempio ..

PARENT_ID 1, CHILD_ID 1
PARENT_ID 1, CHILD_ID 2
PARENT_ID 1, CHILD_ID 3
PARENT_ID 2, CHILD_ID 1
PARENT_ID 3, CHILD_ID 1
PARENT_ID 3, CHILD_ID 2

C'è un modo per avere il valore CHILD_ID assegnato automaticamente, come ad esempio una sequenza o un vincolo, solo con la possibilità di riutilizzare un CHILD_ID che è stato eliminato? L'unico modo che riesco a capire è qualcosa per l'effetto di questo SQL.

INSERT INTO child SELECT parent_id, MAX(child_id)+1, 'description' FROM child WHERE parent_id = :PARENT_ID GROUP BY parent_id

E 'un po' di hack però. Mi rendo conto che la normalizzazione del database suggerisce che non dovrebbe avere una chiave legati ad un altro, ma non hanno questa opzione per altri motivi. Tutte le idee?

Modifica del brutto titolo. Se qualcuno di voi gente alto punteggio può pensare a uno che è più accurato, non esitate a cambiarlo.

È stato utile?

Soluzione

Io suggerirei utilizzando:

CHILD: PARENT_ID integer, CHILD_ID serial, DESCRIPTION character varying(50)

Quando è necessario per ottenere il risultato desiderato:

  • È possibile contare righe su lato client.

  • Quando si seleziona le righe in cui PARENT_ID =? è possibile utilizzare la sequenza temporanea.

  • In prossima uscita PostgreSQL 8.4 è possibile utilizzare funzioni di windowing in questo modo:

    $ create table child (parent_id integer, child_id serial);
    NOTICE:  CREATE TABLE will create implicit sequence "child_child_id_seq" for serial column "child.child_id"
    CREATE TABLE
    
    $ insert into child (parent_id) values (1), (1), (1), (2), (3), (3);
    
    $ select * from child;
     parent_id | child_id 
    -----------+----------
             1 |        1
             1 |        2
             1 |        3
             2 |        4
             3 |        5
             3 |        6
    (6 rows)
    
    $ select parent_id, row_number() over (partition by parent_id order by child_id) from child;
     parent_id | row_number 
    -----------+------
             1 |          1
             1 |          2
             1 |          3
             2 |          1
             3 |          1
             3 |          2
    (6 rows)
    

E 'molto veloce, facile da implementare e scalerà molto bene come non ci saranno problemi di concorrenza di cui preoccuparsi.

Altri suggerimenti

Questo inserto non è tutta la storia però. Avrete anche bisogno di gestire le eliminazioni di colmare il divario che è stato creato se si vuole veramente i numeri per essere contigui.

Il mio suggerimento sarebbe quello di ricavare questo valore come avete bisogno. Che cosa determina l'ordine del numero? Se è la data inserita nel sistema quindi aggiungere tale data al vostro tavolo e mettere il vostro PK sul parent_id e quella data, allora si può abbastanza facilmente venire con il numero tramite SQL o nel front-end come avete bisogno.

È possibile usare un numero di versione incrementale sulla tabella padre e impostare l'id bambino a quel valore e incrementarlo. Probabilmente avrete bisogno di aggiornare la riga padre e inserire la riga figlio in una singola transazione.

BEGIN
-- Get and hold onto parent_id and version values.
SELECT PARENT_ID, VERSION FROM PARENT WHERE PARENT_ID = :PARENT_ID;
-- Use the values to insert into the child table
INSERT INTO CHILD (PARENT_ID, CHILD_ID) VALUES (:PARENT_ID, :VERSION);
-- Update the version using an optimistic lock.
UPDATE PARENT SET VERSION = VERSION + 1 WHERE PARENT_ID = :PARENT_ID AND 
                                              VERSION = :VERSION_ID
-- If no rows are updated rollback the transaction and try again.
END

Questo assicurerà gli ID bambino sono strettamente ascendendo, ma non riutilizzare i valori ID dopo una cancellazione. Se si può evitare il vincolo di riutilizzare vecchi ids, sarà semplificare la soluzione (e la soluzione sarà più efficiente). Se deve ids riutilizzo allora si hanno 2 opzioni, in primo luogo la soluzione specificato sopra, ma su eliminazione rinumerazione di tutti i valori che si verificano dopo quello eliminato. L'altra opzione è quella di avere un qualche tipo di funzione che analizza gli ID dei bambini in ordine e li confronta con una serie di numeri sequenziali e restituisce il valore quando il primo non è stato trovato. Entrambe queste soluzioni sono più complessi e sarà lento come avrete bisogno di prendere un blocco di riga per impedire aggiornamenti simultanei e sia inserzioni o entrambe le inserzioni e delezioni comportano un O (n) penalità.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top