Showing posts with label database. Show all posts
Showing posts with label database. Show all posts

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.

A Generic database schema design for UserContact details

Most of the business application that deals with a User entity, will have contact details for the user.
Contact details may be Email, Phone, Address (Home, office), Fax etc., There are various types of contacts.
Rather having to design/create the schema for user/user contacts for every application we design from scratch. It helps to have a generic model which we can just refer & use in our applications. In this article i'll show the simple & efficient schema for the same.
Source: This schema idea is taken from OfBiz framework (Database schema design of Ofbiz is considered one of the best schema design).

Basic User schema
1) USER Entity
 USER_ID  Integer(10) primary key,
 NAME  Varchar (30)
For simplicity sake, we just have 2 fields for User entity. You can add more columns when required.
2) CONTACT_MECH Entity
 CONTACT_MECH_ID  Integer(10) primary key,
 TYPE   Varchar (10),
 VALUE   Varchar (50)
'Type' field holds a constant string values like 'PHONE', 'EMAIL', 'ADDRESS', etc., 'Value' field will be used for holding values when the value is a single value like EMAIL, WEBSITE etc.,
3) USER_CONTACT_MECH
 USER_ID   Integer(10) Foreign key,
 CONTACT_MECH_ID  Integer(10) Foreign key,
 unique (USER_ID, CONTACT_MECH_ID)
This entity is the link between User & ContactMech tables.
4) ADDRESS
 CONTACT_MECH_ID  Integer(10) Foreign key,
 ADDRESS_LINE1  Varchar(40),
 ADDRESS_LINE2  Varchar(40),
 CITY   Varchar(20),
 STATE   Varchar(20),
 COUNTRY   Varchar(20),
 ZIP_CODE  Varchar(10)
If contact mech type is ADDRESS (HOME_ADDRESS, SHIPPING_ADDRESS etc.,), this table is looked up to get the details.

In this example, ADDRESS entity is kept separate, but Phone, email is kept as part of CONTACT_MECH. If necessary even phone can be kept as separate entity.

Using The Schema
In the application, when storing the details, based on the contact mech type, the values is stored in ContactMech and Address entity. For eg., If a webapplication has a form for user to fill in the details like Name, Email, phone & address (address1, city,country etc.,) fields. When data is submitted, the backend code will add an entry in User entity, then create Contact mech records for each of the type, i.e., Email, phone & address and store the values of email & phone in ContactMech table, and create a entry in Address entity to store address details. Finally create entries in UserContactMech to map between User & ContactMech records.

Sample Java (jsp) code for Storing details would be something like this:

 String name = request.getParameter("name");
 String email = request.getParameter("email");
 String phone = request.getParameter("phone");
 String address1 = request.getParameter("address1");
 String city = request.getParameter("city");
 String country = request.getParameter("country");
 String zipcode = request.getParameter("zipcode");
 
 int status = 0;
 // Insert new record for User table
 String userId = DBUtil.getNextId ("USER");

 String insertUserSQL = "INSERT INTO USER (USER_ID, NAME) VALUES (?,?)";
 
 pstatement1 = connection.prepareStatement(insertUserSQL);
 pstatement1.setString(1, userId);
 pstatement1.setString(2, name);
 
 status = pstatement.executeUpdate();

 String nextContactMechId = "";

 // Create entry in Contact Mech for Email

 nextContactMechId = DBUtil.getNextId ("CONTACT_MECH");
 String insertEmailSQL = "INSERT INTO CONTACT_MECH (CONTACT_MECH_ID, TYPE, VALUE) VALUES (?, ?, ?)";
 pstatement2 = connection.prepareStatement(insertEmailSQL);
 pstatement2.setString(1, nextContactMechId);
 pstatement2.setString(2, "EMAIL");
 pstatement2.setString(1, email);
 status = pstatement2.executeUpdate();
 
 String insertUserContactMechSQL = "INSERT INTO USER_CONTACT_MECH (USER_ID, CONTACT_MECH_ID) VALUES (?, ?)";
 pstatement2a = connection.prepareStatement(insertUserContactMechSQL);
 pstatement2a.setString(1, userId);
 pstatement2a.setString(2, nextContactMechId);
 status = pstatement2a.executeUpdate();

 // Create entry in Contact Mech for Address
 nextContactMechId = DBUtil.getNextId ("CONTACT_MECH");
 String insertAddressSQL = "INSERT INTO ADDRESS (CONTACT_MECH_ID, ADDRESS_LINE1, CITY, COUNTRY, ZIPCODE) ";
 insertAddressSQL += " VALUES (?, ?, ?, ?, ?) ";
 pstatement3 = connection.prepareStatement(insertAddressSQL);
 pstatement3.setString(1, nextContactMechId);
 pstatement3.setString(2, address1);
 pstatement3.setString(3, city);
 pstatement3.setString(4, country);
 pstatement3.setString(5, zipcode);
 status = pstatement3.executeUpdate();
 
 String insertContactMechSQL = "INSERT INTO CONTACT_MECH (CONTACT_MECH_ID, TYPE, VALUE) VALUES (?, ?)";
 pstatement3a = connection.prepareStatement(insertContactMechSQL);
 pstatement3a.setString(1, nextContactMechId);
 pstatement3a.setString(2, "HOME_ADDRESS"); 
 status = pstatement3a.executeUpdate();
 // Value field is left empty

 pstatement3b = connection.prepareStatement(insertUserContactMechSQL);
 pstatement3b.setString(1, userId);
 pstatement3b.setString(2, nextContactMechId);
 status = pstatement3b.executeUpdate();
 
 // Create entry for Phone
 /* Is similar to creating for EMAIL as above */
 
DBUtil.getNextId() is a utility method, which gets the next id to use for the specified entity name. (See my other post on How to generate unique ID by own, rather using Auto-increment.)

Similary, when retrieving the details for displaying, based on the type, the data is fetched from appropriate table.
Example Java code for retrieving is as below: (Get details for userId passed as parameter)

 String userId = request.getParameter("user_id");
 
 // Values for these will be populated from database
 String name = null;
 String email = null;
 String phone = null;
 String address1 = null;
 String city = null, country = null, zipcode = null;

 String sql = "SELECT USER_ID, NAME FROM USER WHERE USER_ID = '" + userId + "'";
 stmt=con.createStatement();
 rs=stmt.executeQuery(sql);
 
 while(rs.next()) {
  name = rs.getString("NAME"); 
 } 
 
 // Get list of contact mech id's
 sql = "SELECT CONTACT_MECH_ID FROM USER_CONTACT_MECH WHERE USER_ID = '" + userId + "'";
 stmt1 = con.createStatement();
 rs1 = stmt.executeQuery(sql);
 while (rs1.next()) {
  String contactMechId = rs1.getString ("CONTACT_MECH_ID");

  // Get ContactMech for this contactMechId
  String sql_temp = "SELECT CONTACT_MECH_ID, TYPE, VALUE FROM CONTACT_MECH WHERE CONTACT_MECH_ID = '" + contactMechId + "'";
  stmt2 = con.createStatement();
  rs2 = stmt.executeQuery(sql_temp);
  
  while (rs2.next()) {
   String type = rs2.get("TYPE");
   if("EMAIL".equalsIgnoreCase (type)) {
    email = rs2.get("VALUE");
   } else if("PHONE". equalsIgnoreCase (type) ) {
    phone = rs2.get("VALUE");
   } else if ("HOME_ADDRESS".equalsIgnoreCase (type) || "SHIPPING_ADDRESS".equalsIgnoreCase (type) ) {
    sql_temp = "SELECT ADDRESS_LINE1, CITY, COUNTRY, ZIPCODE FROM ADDRESS WHERE  CONTACT_MECH_ID = '" + contactMechId + "'";
    stmt3 = con.createStatement();
    rs3 = stmt.executeQuery(sql_temp);
    while(rs3.next()) {
     address1 = rs3.getString ("ADDRESS_LINE1");
     city = rs3.getString ("CITY");
     country = rs3.getString ("COUNTRY");
     zipcode = rs3.getString ("ZIPCODE");
     
    }
   }
  } // End of rs2 loop
 }
 
 // Display the details.

 out.println ("Name: " + name );
 out.println ("Phone: " + phone );
 out.println ("Email: " + email );
 out.println ("Address: " + address1 + ", " +  city + ", " + country + "," + zipcode);


Note: If you use any ORM like Hibernate, the storage/retrieval process will be even easier. Only the mapping between bean & database tables should be done properly.