Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom SERIAL / autoincrement per group of values

I'm trying to make a blog system of sort and I ran into a slight problem.

Simply put, there's 3 columns in my article table:

id SERIAL,
category VARCHAR FK,
category_id INT

id column is obviously the PK and it is used as a global identifier for all articles.

category column is well .. category.

category_id is used as a UNIQUE ID within a category so currently there is a UNIQUE(category, category_id) constraint in place.

However, I also want for category_id to auto-increment.

I want it so that every time I execute a query like

INSERT INTO article(category) VALUES ('stackoverflow');

I want the category_id column to be automatically be filled according to the latest category_id of the 'stackoverflow' category.

Achieving this in my logic code is quite easy. I just select latest num and insert +1 of that but that involves two separate queries. I am looking for a SQL solution that can do all this in one query.

like image 316
yhware Avatar asked Jan 02 '16 21:01

yhware


People also ask

How do I set AutoIncrement value?

Syntax. In MySQL, the syntax to change the starting value for an AUTO_INCREMENT column using the ALTER TABLE statement is: ALTER TABLE table_name AUTO_INCREMENT = start_value; table_name.

How many auto increment column can a table have?

Each table can have only one AUTO_INCREMENT column. It must defined as a key (not necessarily the PRIMARY KEY or UNIQUE key).

Can you create an auto increment on a unique key?

AUTO INCREMENT FieldAuto-increment allows a unique number to be generated automatically when a new record is inserted into a table. Often this is the primary key field that we would like to be created automatically every time a new record is inserted.

Is there auto increment in PostgreSQL?

PostgreSQL has the data types smallserial, serial and bigserial; these are not true types, but merely a notational convenience for creating unique identifier columns. These are similar to AUTO_INCREMENT property supported by some other databases.


2 Answers

This has been asked many times and the general idea is bound to fail in a multi-user environment - and a blog system sounds like exactly such a case.

So the best answer is: Don't. Consider a different approach.

Drop the column category_id completely from your table - it does not store any information the other two columns (id, category) wouldn't store already.

Your id is a serial column and already auto-increments in a reliable fashion.

  • Auto increment SQL function

If you need some kind of category_id without gaps per category, generate it on the fly with row_number():

  • Serial numbers per group of rows for compound key
like image 57
Erwin Brandstetter Avatar answered Oct 26 '22 08:10

Erwin Brandstetter


Concept

There are at least several ways to approach this. First one that comes to my mind:

Assign a value for category_id column inside a trigger executed for each row, by overwriting the input value from INSERT statement.

Action

Here's the SQL Fiddle to see the code in action


For a simple test, I'm creating article table holding categories and their id's that should be unique for each category. I have omitted constraint creation - that's not relevant to present the point.

create table article ( id serial, category varchar, category_id int )

Inserting some values for two distinct categories using generate_series() function to have an auto-increment already in place.

insert into article(category, category_id)
  select 'stackoverflow', i from generate_series(1,1) i
  union all
  select 'stackexchange', i from generate_series(1,3) i

Creating a trigger function, that would select MAX(category_id) and increment its value by 1 for a category we're inserting a row with and then overwrite the value right before moving on with the actual INSERT to table (BEFORE INSERT trigger takes care of that).

CREATE OR REPLACE FUNCTION category_increment()
RETURNS trigger
LANGUAGE plpgsql
AS
$$
DECLARE
  v_category_inc int := 0;
BEGIN
  SELECT MAX(category_id) + 1 INTO v_category_inc FROM article WHERE category = NEW.category;
  IF v_category_inc is null THEN
    NEW.category_id := 1;
  ELSE
    NEW.category_id := v_category_inc;
  END IF;
RETURN NEW;
END;
$$ 

Using the function as a trigger.

CREATE TRIGGER trg_category_increment 
  BEFORE INSERT ON article 
  FOR EACH ROW EXECUTE PROCEDURE category_increment()

Inserting some more values (post trigger appliance) for already existing categories and non-existing ones.

INSERT INTO article(category) VALUES 
  ('stackoverflow'),
  ('stackexchange'),
  ('nonexisting');

Query used to select data:

select category, category_id From article order by 1,2

Result for initial inserts:

category    category_id
stackexchange   1
stackexchange   2
stackexchange   3
stackoverflow   1

Result after final inserts:

category    category_id
nonexisting     1
stackexchange   1
stackexchange   2
stackexchange   3
stackexchange   4
stackoverflow   1
stackoverflow   2
like image 27
Kamil Gosciminski Avatar answered Oct 26 '22 08:10

Kamil Gosciminski