An Alternate to Auto Increment

No Auto-Increment, Generate Unique Id by own.

Auto-Increment: A column in a database table can be specified as auto-increment, which means the value for that column is auto incremented everytime a new record is inserted. It may be useful for some applications and at the same time pose some issues in some applications.
For eg., Consider a simple schema with tables Person, PersonAddress. Schema as given below
 PERSON
  ID INTEGER(5) AUTO-INCREMENT,
  NAME VARCHAR (50)
 
 PERSON_ADDRESS
  ID  INTEGER (5) AUTO-INCREMENT,
  USER_ID  INTEGER(5) REFERENCES PERSON (ID),
  ADDRESS1 VARCHAR (50),
  CITY  VARCHAR (20),
  COUNTRY  VARCHAR (20)

Consider a use-case where a user is submitting the details, & the code is storing those details into these 2 tables in database.
Storing these details is two step process, i.e., First, create a record in PERSON table, and then create a record in PERSON_ADDRESS table, the issue rises when we are to insert record to PERSON_ADDRESS, how do we get the USER_ID that we just created. Since the ID field in PERSON table is auto-incremented, so in the program we dont have the USER_ID right away. So, How do we get this newly created USER_ID to use for PERSON_ADDRESS table? One way is to make a SQL call to database & get the USER_ID for recently created record, but, this is not efficient way of doing it.
The solution I prefer is to NOT use auto-increment feature of the database. Generate the Unique id's for the ID fields by own. In this article I'll show how this can be done in a simple & generic way for the whole application.

1) Table to store recently Used ID's for each table:
CREATE TABLE SEQUENCE ( 
 TABLE_NAME STRING (40) PRIMARY KEY,
 CURRENT_VALUE INTEGER(10) DEFAULT 0
);
SEQUENCE table stores recently used ID for the TABLE_NAME. Everytime the value is fetched, CURRENT_VALUE is incremented by 1. This is done by the utility method. (Shown below)

2) A Utility Method to generate ID (In sequence)

public class DBUtil {
 
 public int generateNextId (String tableName) {
  /*
   Create connection object & statement objects
   Connection conn = .... ;
   Statement stmt = ... ;
  */
  int nextId = -1;

  String sql = "SELECT CURRENT_VALUE FROM SEQUENCE WHERE TABLE_NAME = '" + tableName + "'";

  ResultSet rs = stmt.executeQuery(sql);
  while(rs.next()) {
   int temp = rs.getInt (CURRENT_VALUE);
   nextId = temp + 1;
  }
  if(nextId == -1) return nextId;

  // INCREMENT the ID & store back in table
  String updateSql = "UPDATE FROM SEQUENCE SET CURRENT_VALUE = ? WHERE TABLE_NAME = '" + tableName + "'";
  PreparedStatement pstatement = connection.prepareStatement(updateSql);

  pstatement.setString(1, nextId);
  int status = pstatement.executeUpdate();

  return nextId;
 }
}
3)Example: Using Utility method while inserting data

 String personId = DBUtil.generateNextId ("PERSON");

 String insertPersonSQL = "INSERT INTO PERSON (ID, NAME) VALUES (?,?)";
 pstatement1 = connection.prepareStatement(insertPersonSQL);
 pstatement1.setString(1, personId);
 pstatement1.setString(2, name);

 // Now we still have person id handle, can use the same for Person Address record
 String addressId = DBUtil.generateNextId ("PERSON_ADDRESS");

 String insertAddressSQL = "INSERT INTO PERSON_ADDRESS (ID, PERSON_ID, ADDRESS, CITY) VALUES (?,?,?,?)";
 pstatement2 = connection.prepareStatement(insertPersonSQL);
 pstatement2.setString(1, addressId);
 pstatement2.setString(2, personId);
 .
 .
Here, in this example, when inserting new records, we generate ID for person table using the utility as DBUtil.generateNextId ("PERSON");, and we have handle for this new Id, which we use for further insertions when required. This wouldn't be possible with default auto-increment feature that database provides.

Hope this makes sense & it's useful. Any suggestions/query, feel free to contact me.

2 comments:

  1. I had a PHP application that did exactly the solution you recommended whereby a new ID is generated manually from a table as a SELECT then UPDATE.

    You'd be surprised how often it would be possible for two concurrent accesses to the database to call SELECT to read the next available ID, then delay calling the UPDATE meaning the other thread also called SELECT. The result is that both threads ended up with the same ID.

    The only reasonable solution I came up with without more complex locking was to use AUTO_INCREMENT to guarantee that the same ID couldn't be the same between two concurrent accesses by relying on the database to handle the concurrency.

    ReplyDelete
  2. Thanks Martin,

    Yes, using Auto_Icrement is another option.
    This solution is not the best, but it does give some idea on different ways of working with auto-increment functionalities

    ReplyDelete