Sunday, 22 June 2014

SQLITE DATABASE- INSERT THOUSANDS OF DATA IN SECONDS (Tutorial 1)


Inserting a single data in database is easy, but when we have 1000's of data to be inserted it takes lot of time , because we are calling 1000's of sql queries. So in this tutorial I will show you how you can insert large data in seconds.



Download Source Code Download

In this tutorial we will make a Student database, contains table Record which is supposed to store students information , table columns are-



Project hierarchy is this-
  1. Project name DatabaseDemo.
  2. Project package name com.tutorialsface.databasedemo
  3. For database classes make a separate package which contains only database classes com.org.database
  4. For model classes make another separate package com.org.model

Project hierarchy
First of all make a model class Student.java in com.org.model
Student.java
package com.org.model;

public class Student {

      String name;
      String subject;
      int className;
      int marks;

      public String getName() {
            return name;
      }
      public void setName(String name) {
            this.name = name;
      }
      public String getSubject() {
            return subject;
      }
      public void setSubject(String subject) {
            this.subject = subject;
      }
      public int getClassName() {
            return className;
      }
      public void setClassName(int className) {
            this.className = className;
      }
      public int getMarks() {
            return marks;
      }
      public void setMarks(int marks) {
            this.marks = marks;
      }
}

1. Now make DatabaseConstant.java class in com.org.database package .In this class all the constants which are related to Database will be defined

DatabaseConstant.java
package com.org.database;

class DatabaseConstant {

      static final int DB_VERSION = 1;    // Database Version number
      static final String DB_NAME = "Student"// Database Name
     
      static final String KEY_ID = "_id";       // Autoincrement field
      static final String TABLE_RECORD = "Record";    // Table Name
                        //Table fields
      static final String RECORD_NAME = "name";      
      static final String RECORD_SUBJECT = "subject";
      static final String RECORD_CLASS = "class";
      static final String RECORD_MARKS = "marks";
     
      // Create Table "Record" Query
      static final String CREATE_TABLE_RECORD = "CREATE TABLE " + TABLE_RECORD + " ( " + KEY_ID + " integer primary key autoincrement, "RECORD_NAME + " text, " + RECORD_SUBJECT + " text, " + RECORD_CLASS + " integer," RECORD_MARKS + " integer)";
}

2. Make DatabaseHelper.java class in com.org.database and extends SQLiteOpenHelper.

DatabaseHelper.java
package com.org.database;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DatabaseHelper extends SQLiteOpenHelper{

      public DatabaseHelper(Context context) {
            super(context, DatabaseConstant.DB_NAME, null, DatabaseConstant.DB_VERSION);
      }

      @Override
      public void onCreate(SQLiteDatabase db) {
            // Executing Create Table "Record" Query
            db.execSQL(DatabaseConstant.CREATE_TABLE_RECORD); 
      }

      @Override
      public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            // TODO Auto-generated method stub
      }
}

3. Now make DatabaseAdapter.java class in com.org.database . In this class functions like opening and closing of database , query functions are written.

DatabaseAdapter.java
package com.org.database;

import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import com.org.model.Student;

public class DatabaseAdapter {

      private Context context;
      private SQLiteDatabase database;
      private DatabaseHelper dbHelper;
      //Constructor
      protected DatabaseAdapter(Context context){
            this.context = context;
      }
      //Open database
      protected DatabaseAdapter open() throws SQLException{
            dbHelper = new DatabaseHelper(context);
            database = dbHelper.getWritableDatabase();
            return this;
      }
      //Close Database
      protected void close() {
            dbHelper.close();
      }
      //Insert Data into Record Table
      protected void insertStudentRecord(List<Student> studentData){
            /*We have to make this query-
             * INSERT INTO Record (name, subject, class, marks) VALUES (name1,                 subject1, class1, marks1), (name2, subject2, class2, marks2).....
             */
            String values = buildQueryValueForRecordInsert(studentData);
            String query = "INSERT INTO " + DatabaseConstant.TABLE_RECORD + "(" 
DatabaseConstant.RECORD_NAME + ", " + DatabaseConstant.RECORD_SUBJECT + ", " +  DatabaseConstant.RECORD_CLASS + ", " + DatabaseConstant.RECORD_MARKS + ") VALUES " + values;
            database.execSQL(query);
      }
      //build values
      private String buildQueryValueForRecordInsert(List<Student> studentData){
            //String array of size equals to list size
            String[] valueArray = new String[studentData.size()]; 
           
            for(int i = 0; i < studentData.size(); i++){
                  // Fetching model from list one by one
                  Student data = studentData.get(i);  
                  // Fetching name from model
                  String name = data.getName();    
                  // Fetching subject from model   
                  String subject = data.getSubject(); 
                  // Fetching className from model
                  String className = "" + data.getClassName();
                  // Fetching marks from model    
                  String marks = "" + data.getMarks();     
                 // Make an String array of name, subject, class, marks     
                  String[] values = new String[]{name, subject, className, marks}; 
                 // Pass the string array in buildQueryInsertValue
                  valueArray[i] = buildQueryInsertValue(values); 
            }
            return TextUtils.join(",", valueArray);
      }
      /**Below function takes string array as input and return a string with "("  at the start & ")" at the end
       * with each array element separated by a comma(",")
       * So if the string array is new String[]{"name", "subject1", "10", "75"}
       * Output will be -> ('name', 'subject1', '10', '75')
       */
      private String buildQueryInsertValue(String[] values){
            for(int i = 0; i < values.length; i++){
                  if(values[i] == null){
                        values[i] = "";
                  }
                  values[i] = DatabaseUtils.sqlEscapeString(values[i]);
            }
            return "(" + TextUtils.join(",", values) + ")";
      }
      //get number of rows in Record table
      protected Cursor getRecordDataCount() {
            String query = "SELECT Count() as c FROM " + DatabaseConstant.TABLE_RECORD;
            return database.rawQuery(query, null);
      }
}

A. open() - This function opens the database.
B. close() - This function close the database.
C. getRecordDataCount() - returns the number of rows in Record table.
D. insertStudentRecord(List<Student> studentData) - This function takes List of Student data as parameter and insert data in Record table.

E. buildQueryInsertValue(String[] values) - This function takes array of String as parameter and return a string where each element of array is separated by comma (",") and adding "(" at the start and ")" at the end. Suppose array elements are-
values = new String[]{"name", "subject", "10", "75"}; 
i.   Iterate it with for loop.
ii.  Pass that value from DatabaseUtils.sqlEscapeString. This function handles the escape characters (as we are making the query without using ContentValues so we have to handle escape characters our-self).
iii. TextUtils.join(",", values) this function separates the String array element with comma(",") and returns a string.
iv. At last this function return string as - ('name', 'subject', '10', '75').

F. buildQueryValueForRecordInsert(List<Student> studentData) - This function takes List of Student data and return a String forming the VALUES part of the query.
i.  In this function we will pass the String array to the buildQueryInsertValue which will return String in the format of - ('name', 'subject', '10', '75'). which is stored in a array Like-
new Stirng[]{('name', 'subject', '10', '75'), ('name1', 'subject1', '12', '85'),('name3', 'subject3', '8', '70')};
ii. At last that array is passed into TextUtils.join(",", values) which separates the String array element with comma(",") and returns a string. So the output of this function will be.
('name', 'subject', '10', '75'), ('name1', 'subject1', '12', '85'),('name3', 'subject3', '8', '70')

4. Now create the last class of com.org.database package DatabaseController.java. This is the only class whose functions will be called from outside this package.
In this class a recursive function is written which is responsible for inserting 1000's of data in seconds. 

DatabaseController.java
package com.org.database;

import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import com.org.model.Student;

public class DatabaseController {

      public DatabaseController(){}
     
      private static DatabaseController instance = null;
     
      public static DatabaseController getInstance(){
            if(instance == null){
                  instance = new DatabaseController();
            }
            return instance;
      }
     
      public void insertRecord(Context context, ArrayList<Student> studentData){
            DatabaseAdapter dbAdapter = null;
            try{
                  dbAdapter = new DatabaseAdapter(context).open();      //Open database
                  insertRecordRecursion(studentData, context, dbAdapter);
                  dbAdapter.close();            //Close database
            }catch(Exception e){
                  e.printStackTrace();
            }
      }
     
      private void insertRecordRecursion(List<Student> studentData, Context context, DatabaseAdapter dbAdapter){
            int dataSize = studentData.size();
            if(dataSize == 0)
                  return;
            int countBeforeInsertion = getRecordDataCount(context);
            try{
                  dbAdapter.insertStudentRecord(studentData);
            }catch(Exception e){
                  e.printStackTrace();
            }
            int countAfterInsertion = getRecordDataCount(context);
            if(countAfterInsertion == countBeforeInsertion){
                  if(dataSize == 1)
                        return;
                  List<Student> firstHalfData = studentData.subList(0, dataSize/2);
                  List<Student> secondHalfData = studentData.subList(dataSize/2, dataSize);
                  insertRecordRecursion(firstHalfData, context, dbAdapter);
                  insertRecordRecursion(secondHalfData, context, dbAdapter);
            }
      }
     
      public int getRecordDataCount(Context context){
            DatabaseAdapter dbAdapter = null;
            Cursor c = null;
            int count = 0;
            try{
                  dbAdapter = new DatabaseAdapter(context).open();
                  c = dbAdapter.getRecordDataCount();
                  c.moveToFirst();
                  count = c.getInt(c.getColumnIndex("c"));
                  c.close();
                  dbAdapter.close();
            }catch(Exception e){
                  e.printStackTrace();
            }
            return count;
      }
}

A. insertRecord() - This function takes the list of student data.
i.   Suppose you want to insert 2,500 data of student then this list contains information of 2,500 student.
ii.  dbAdapter = new DatabaseAdapter(context).open() - will open the database.
iii. dbAdapter.close() - will close the database.

B. insertRecordRecursion() - This is how this function works-
i.  Check if listSize == 0 return; Means list is empty.
ii.  Get the number of entries in Record Table -> countBeforeInsertion.
iii. dbAdapter.insertStudentRecord() - Inserts data in the table. This should be written in try catch because if somehow data not entered in the table it will throw a exception.
iv. Again check the number of records present in Record table -> countAfterInsertion
v. If (countAfterInsertion == countBeforeInsertion) Means data is not entered in the table.
vi. Now check if listSize == 1. If it is true means there is some problem in that data, or you are using UNIQUE constraint in any of the column in your table. So this data will be skipped and will not enter in the table.
vii. If listSize != 1 then break the list into two halves and with each half call this function again.And this will continue until listSize becomes zero.

Recursion
If table column don't have UNIQUE or any other constraint then 2,500 data will be inserted in 14 attempts.
The maximum limit of sqlite to insert data is 500. This means you can only insert max 500 data at a time, So your query will be -
INSERT INTO TABLE_NAME (COLUMN1, COLUMN2) VALUES (VC11, VC21),(VC12, VC22),(VC13, VC23),(VC14, VC24)...................(VC1500, VC2500).
Also , if there is other limit then you don't have to worry because our recursion method handles it.

5. Now lets make a UI to check how it works, this will be very simple UI which will have a editText for the number of data to insert and a Button. Open activity_main.xml and write down the following code-

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <EditText
        android:id="@+id/textNumber"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:inputType="number" />

    <Button
        android:id="@+id/btnFetch"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Fetch And Store" />

    <TextView
        android:id="@+id/textViewLog"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="15dp" />

</LinearLayout>

6. Open MainActivity.java and write down the following code.

MainActivity.java
package com.tutorialsface.databasedemo;

import java.util.ArrayList;
import java.util.Random;
import com.org.database.DatabaseController;
import com.org.model.Student;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends Activity{

      private Button btnFetch;
      private EditText textNumber;
      private TextView textViewLog;
     
      @Override
      protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            init();
      }

      private void init() {
            btnFetch = (Button) findViewById(R.id.btnFetch);
            textNumber = (EditText) findViewById(R.id.textNumber);
            textViewLog = (TextView) findViewById(R.id.textViewLog);
            btnFetch.setOnClickListener(new OnClickListener() {
                 
                  @Override
                  public void onClick(View v) {
                        textViewLog.setText("");
                        try{
                              getData();
                        }catch(Exception e){}
                  }
            });
      }
     
      void getData(){
            String TAG = "getData";
            DatabaseController dbController = DatabaseController.getInstance();
            long t1 = System.currentTimeMillis();
            ArrayList<Student> studentData = makeData();
            long t2 = System.currentTimeMillis();
            textViewLog.setText("Number of Data to insert: " + studentData.size());
            Log.d(TAG,  TAG + "Time dataSize "  + studentData.size());
            Log.d(TAG,  TAG + "Time Fetching "  + ((float)(t2-t1)/1000) + " sec");
            textViewLog.setText(textViewLog.getText().toString() + "\nData Fetching Time: "  + ((float)(t2-t1)/1000) + " sec");
            textViewLog.setText(textViewLog.getText().toString() + "\nNumber of Records before Insertion:" + dbController.getRecordDataCount(getApplicationContext()));
            long t3 = System.currentTimeMillis();
            dbController.insertRecord(getApplicationContext(), studentData);
            long t4 = System.currentTimeMillis();
            Log.d(TAG,  TAG + "Time Insertion "  + ((float)(t4-t3)/1000) + " sec");
            textViewLog.setText(textViewLog.getText().toString() + "\nInsertion Time: "  + ((float)(t4-t3)/1000) + " sec");
            textViewLog.setText(textViewLog.getText().toString() + "\nNumber of Records after Insertion:" + dbController.getRecordDataCount(getApplicationContext()));
            Log.d(TAG,  TAG + "Number of Records" + dbController.getRecordDataCount(getApplicationContext()));
      }
     
      private ArrayList<Student> makeData() {
            Random random = new Random();
            String[] subjects = new String[]{"Physics", "Chemistry", "Maths", "Biology", "English"};
            int numberOfData = Integer.parseInt(textNumber.getText().toString());
            ArrayList<Student> studentData = new ArrayList<Student>();
            for(int i = 0; i < numberOfData; i++){
                  int randomNameMarks = random.nextInt(101);
                  int randomSubject = random.nextInt(subjects.length);
                  int randomClass = random.nextInt(13);
                  Student data = new Student();
                  data.setName("Student" + randomNameMarks);
                  data.setSubject(subjects[randomSubject]);
                  data.setClassName(randomClass);
                  data.setMarks(randomNameMarks);
                  studentData.add(data);
            }
            return studentData;
      }
}
  •  makeData() function prepares a List of the data on the basis of size provided in the editText. 
  • Now run the project and enter the number and press the button.
Download Source Code Download

Sometime You may see in your Logcat. This is because you are trying to insert more than 500 data at a time.






1 comment:

  1. Do you have car and bike parking places data base with images.....

    ReplyDelete