Search code examples
postgresqlsequencesmultiple-columns

Incrementing multi-column sequence in PostgreSQL


Is there any built-in way (I mean, without need of triggers and/or functions) to have incrementing indexes per multiple columns?

So after performing:

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')

My table would end up like this (note the "task" column):

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

Solution

  • You can add simlpe SERIAL column to your table (it will give you the order for things) and then use something like:

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

    This will give you the results you want.

    If you do not need to order the rows, you can try:

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

    Details here : row_number() OVER(...)

    UPD How it works:

    A column with type SERIAL is essentially an "auto increment" field. It automatically get a value from a sequence. When you insert rows to the table they will look like this:

    | 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 |
    

    The key thing - every next added row has value of SERIAL_COLUMN greater than all previous rows.

    Next. The row_number() OVER (PARTITION BY month ORDER BY serial_column) does:

    1) Partition all the rows into groups with equal month (PARTITION BY month)

    2) Orders them by value of serial_column (ORDER BY serial_column)

    3) In every group assigns a row number using the ordering from step 2 (`row_number() OVER)

    The output is:

    | 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 |
    

    To change the output of the row_number() you need to change the values in SERIAL_COLUMN. Fro example, to place Second and last of second month before First of second month a will change the values of SERIAL_COLUMN like that:

    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';
    

    It will change the output of the 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 |
    

    The exact values in SERIAL_COLUMN do not matter. They only set an order on the tasks in a month.

    My SQLFiddle example is here.