2012-12-05 21 views
7

C'è un modo integrato (cioè, senza bisogno di trigger e/o funzioni) di avere indici incrementali per più colonne?Incremento della sequenza multi-colonna in PostgreSQL

Così, dopo l'esecuzione di:

INSERT INTO "table" 
    ("month", "desc") 
    VALUES 
    (1, 'One thing') 
, (1, 'Another thing') 
, (1, 'Last task of the month') 
, (2, 'Last task of the month') 
, (2, 'Last task of the month') 
, (3, 'First of third month') 

Il mio tavolo finirebbe come questo (notare la colonna "compito"):

month task desc 
1  1  One thing 
1  2  Another thing 
1  3  Last task of the month 
2  1  First of second month 
2  2  Second and last of second month 
3  1  First of third month 

risposta

12

È possibile aggiungere simlpe SERIAL colonna alla tabella (lo farà dare l'ordine per le cose) e quindi utilizzare qualcosa del tipo:

SELECT *, row_number() OVER (PARTITION BY month ORDER BY serial_column) 
FROM table 

Questo vi darà e risultati che vuoi

Se non avete bisogno di ordinare le righe, si può provare:

SELECT *, row_number() OVER (PARTITION BY month) 
FROM table 

dettagli qui: row_number() OVER(...)

UPD Come funziona:

Una colonna di tipo SERIAL è essenzialmente un campo "auto increment". Ottiene automaticamente un valore da una sequenza. Quando si inserisce righe alla tabella che sarà simile a questo:

| MONTH | SERIAL_COLUMN |      DESCRIPTION | 
----------------------------------------------------------- 
|  1 |    1 |      One thing | 
|  1 |    2 |     Another thing | 
|  1 |    3 |   Last task of the month | 
|  2 |    4 |   First of second month | 
|  2 |    5 | Second and last of second month | 
|  3 |    6 |   First of third month | 

La cosa fondamentale - ogni riga aggiunta accanto ha un valore di SERIAL_COLUMNmaggiore di tutte le righe precedenti.

Avanti. Il row_number() OVER (PARTITION BY month ORDER BY serial_column) fa:

1) divisorie tutte le righe in gruppi con uguale month (PARTITION BY month)

2) ordina loro per valore di serial_column (ORDER BY serial_column)

3) In ogni gruppo assegna un numero di riga utilizzando l'ordinamento dal passo 2 (`row_number() OVER)

il risultato è:

| MONTH | SERIAL_COLUMN |      DESCRIPTION | ROW_NUMBER | 
------------------------------------------------------------------------ 
|  1 |    1 |      One thing |   1 | 
|  1 |    2 |     Another thing |   2 | 
|  1 |    3 |   Last task of the month |   3 | 
|  2 |    4 |   First of second month |   1 | 
|  2 |    5 | Second and last of second month |   2 | 
|  3 |    6 |   First of third month |   1 | 

Per modificare l'output di row_number() è necessario modificare i valori in SERIAL_COLUMN. Fro esempio, di inserire il Second and last of second month prima First of second month un cambierà i valori di SERIAL_COLUMN così:

UPDATE Table1 
SET serial_column = 5 
WHERE description = 'First of second month'; 

UPDATE Table1 
SET serial_column = 4 
WHERE description = 'Second and last of second month'; 

Cambierà l'output della query:

| MONTH | SERIAL_COLUMN |      DESCRIPTION | ROW_NUMBER | 
------------------------------------------------------------------------ 
|  1 |    1 |      One thing |   1 | 
|  1 |    2 |     Another thing |   2 | 
|  1 |    3 |   Last task of the month |   3 | 
|  2 |    4 | Second and last of second month |   1 | 
|  2 |    5 |   First of second month |   2 | 
|  3 |    6 |   First of third month |   1 | 

I valori esatti in SERIAL_COLUMN non contano. Impostano solo un ordine sulle attività in un mese.

Il mio esempio SQLFiddle è here.

+0

Questo è il modo per farlo. In un ambiente multiutente è * molto difficile * far rispettare la numerazione senza spazio senza creare ** condizioni di gara **. Dovresti usare i blocchi del tavolo pesantemente, il che è un vero fattore negativo per le prestazioni. Ti suggerisco di usare 'row_number()' invece di 'rank()'. Il risultato è lo stesso in assenza di peer (come in questo caso), ma 'row_number()' è la funzione più economica e più appropriata. –

+0

@ErwinBrandstetter Grazie per 'row_number()'. Ho modificato le query. –

+0

Posso in seguito cambiare l'ordine delle attività in un mese? – Mario

4

Se si desidera rompere le istruzioni INSERT in una riga di dati per inserimento, è possibile utilizzare PostgreSQL rules. L'esempio che segue è un po 'complicato in quanto le regole non sembrano permetterti di reindirizzare le scritture a una relazione stessa. Di solito è fatto con trigger. Ma stiamo vedendo se questo è possibile, senza trigger, quindi ecco qui:

--drop table table_data cascade; 
CREATE TABLE table_data (
    month integer not null, 
    task integer not null, 
    "desc" text 
); 
ALTER TABLE table_data add primary key (month, task); 

CREATE VIEW "table" as 
select month, task, "desc" from table_data; 

CREATE OR REPLACE RULE calculate_task AS ON INSERT TO "table" 
    DO INSTEAD 
    INSERT into table_data (month, task, "desc") 
    VALUES (
    NEW.month, 
    (select coalesce(max(task),0) + 1 from table_data where month = NEW.month), 
    NEW."desc"); 

BEGIN; 
INSERT INTO "table" ("month", "desc") VALUES (1, 'One thing'); 
INSERT INTO "table" ("month", "desc") VALUES (1, 'Another thing'); 
INSERT INTO "table" ("month", "desc") VALUES (1, 'Last task of the month'); 
INSERT INTO "table" ("month", "desc") VALUES (2, 'Last task of the month'); 
INSERT INTO "table" ("month", "desc") VALUES (2, 'Last task of the month'); 
INSERT INTO "table" ("month", "desc") VALUES (3, 'First of third month'); 
COMMIT; 

select * from "table"; 

Note

  • Se è necessario supportare DELETE/UPDATE sul "tavolo", allora si potrebbe aggiungere regole anche per ciascuna di queste azioni.
  • Il blocco BEGIN e COMMIT viene utilizzato per dimostrare che anche all'interno della stessa transazione, questo metodo funzionerà finché ogni riga sarà interrotta nel proprio INSERT.
  • Si utilizzano alcuni reserved words come table e desc. Assicurati di raddoppiarli come hai fatto e non avrai problemi.

Here è il codice di cui sopra in sqlfiddle

Problemi correlati