How to set primary key in SQLite after table have been already created?
I am creating a project using SQLite and I have forgetton to provide the primary key to user id,now how can I set it?
Below is my ContactContract class
public final class ContactContract {
private ContactContract(){}
public static class ContactEntry
{
public static final String TABLE_NAME="contact_info";
public static final String KEY_P = "KEY";
public static final String CONTACT_ID = "contact_id";
public static final String NAME="name";
public static final String EMAIL="email";
}
}
Below is my ContactDbHelper class
public class ContactDbHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "contact_db";
public static final int DATABASE_VERSION = 1;
public static final String CREATE_TABLE="create table "+ContactContract.ContactEntry.TABLE_NAME+
"(" + ContactContract.ContactEntry.KEY_P +"INTEGER PRIMARY KEY," +ContactContract.ContactEntry.CONTACT_ID+" number,"+ContactContract.ContactEntry.NAME+" text,"+
ContactContract.ContactEntry.EMAIL+" text);";
Using :-
public static final String CREATE_TABLE="create table "+ContactContract.ContactEntry.TABLE_NAME+
"(" + ContactContract.ContactEntry.KEY_P +"INTEGER PRIMARY KEY," +ContactContract.ContactEntry.CONTACT_ID+" number,"+ContactContract.ContactEntry.NAME+" text,"+
ContactContract.ContactEntry.EMAIL+" text);";
Will not work as expected because of :-
You have no space after the column name and INTEGER PRIMARY KEY,
,
You cant have a column named KEY (as would be the case when adding the space before INTEGER) as it's a keyword. Perhaps use public static final String KEY_P = "KEYP";
to overcome this.
As such use :-
public static final String KEY_P = "KEYP";
along with :-
public static final String CREATE_TABLE="create table "+ContactContract.ContactEntry.TABLE_NAME+
"(" + ContactContract.ContactEntry.KEY_P +" INTEGER PRIMARY KEY," +ContactContract.ContactEntry.CONTACT_ID+" number,"+ContactContract.ContactEntry.NAME+" text,"+
ContactContract.ContactEntry.EMAIL+" text);";
With 1 and 2 corrected. Then if you can easily regenerate the data then you could re-install the App to apply the schema changes.
However, as you state that the database has been populated and if you are unable to easily regenerate the data then it can be done.
Assuming the database currently has data (e.g. for testing the following data is part) :-
The the following method could be used to change the schema to add the PRIMARY KEY column :-
private void addPrimaryKey() {
String TAG = "ADDPRMRYKEY";
Log.d(TAG,"Initiated adding the primary key.");
SQLiteDatabase db = this.getWritableDatabase();
Cursor csr = db.query(
"sqlite_master",
null,
"name =? AND instr(sql,'PRIMARY KEY') > 0",
new String[]{ContactContract.ContactEntry.TABLE_NAME},
null,null,null
);
if (csr.getCount() < 1) {
Log.d(TAG," PRIMARY KEY clause not found for table " + ContactContract.ContactEntry.TABLE_NAME);
if (CREATE_TABLE.indexOf("PRIMARY KEY") > 0) {
Log.d(TAG,"PRIMARY KEY clause located in CREATE TABLE SQL so !!!!ALTERING!!!! table " + ContactContract.ContactEntry.TABLE_NAME);
db.execSQL("ALTER TABLE " + ContactContract.ContactEntry.TABLE_NAME + " RENAME TO OLD" + ContactContract.ContactEntry.TABLE_NAME);
Log.d(TAG,"RENAMED TABLE " + ContactContract.ContactEntry.TABLE_NAME + " to OLD" + ContactContract.ContactEntry.TABLE_NAME);
db.execSQL(CREATE_TABLE);
Log.d(TAG,"CREATED new version of table " + ContactContract.ContactEntry.TABLE_NAME + " !!!!INSERTING DATA EXTRACTED!!!! from old version");
db.execSQL("INSERT INTO " + ContactContract.ContactEntry.TABLE_NAME + " SELECT null,* FROM OLD" + ContactContract.ContactEntry.TABLE_NAME);
} else {
Log.d(TAG,"PRIMARY KEY clause not found in the CREATE TABLE SQL so doing nothing.");
}
} else {
Log.d(TAG,"PRIMARY KEY clause found for table " + ContactContract.ContactEntry.TABLE_NAME + " - Nothing to do!!!!");
}
csr.close();
}
The above works by
interrogating the sqlite_master table to extract the SQL used to create the table, IF it includes the PRIMARY KEY clause.
If it does the method returns without doing anything as the PRIMARY KEY has already been defined.
The potentially new/replacement table create SQL is checked to see if it contains the PRIMARY KEY clause.
If it does not then nothing will be done.
The current table is RENAMED.
A new table with the original name is created according to the create table SQL.
The data is copied from the original table to the new table.
Rather than increase the version number the addPrimaryKey method has been incorporated into the Database Helper's constructor as per :-
public ContactDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
addPrimaryKey(); //<<<<<<<<<< ADDED to Convert
}
db.execSQL("INSERT INTO " + ContactContract.ContactEntry.TABLE_NAME + " SELECT null,* FROM OLD" + ContactContract.ContactEntry.TABLE_NAME);
should be changed accordingly.The App was run using (KEY_P commented out as is assumed the current status):-
public static final String CREATE_TABLE = "create table " + ContactContract.ContactEntry.TABLE_NAME +
"(" +
//ContactContract.ContactEntry.KEY_P + " INTEGER PRIMARY KEY," + //<<<<<<<<<< comment out this line for first run to generate data
ContactContract.ContactEntry.CONTACT_ID + " number," +
ContactContract.ContactEntry.NAME + " text," +
ContactContract.ContactEntry.EMAIL + " text" +
");";
The invoking activity used throughout was :-
public class MainActivity extends AppCompatActivity {
ContactDbHelper mDBHlpr;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDBHlpr = new ContactDbHelper(this);
addSomeData();
Cursor csr = mDBHlpr.getWritableDatabase().query(ContactContract.ContactEntry.TABLE_NAME,null,null,null,null,null,null);
DatabaseUtils.dumpCursor(csr);
csr.close();
/*
DataBaseHelper mDBHlpr = DataBaseHelper.getInstance(this);
Cursor csr = mDBHlpr.getWritableDatabase().query("sqlite_master",null,null,null,null,null,null);
DatabaseUtils.dumpCursor(csr);
*/
}
/**
* Add some data
*/
private void addSomeData() {
if (DatabaseUtils.queryNumEntries(mDBHlpr.getWritableDatabase(), ContactContract.ContactEntry.TABLE_NAME) > 0 ) return;
Random rnd = new Random();
for (int i=0;i < 100; i++) {
mDBHlpr.insertContact(rnd.nextInt(),"Aname" + String.valueOf(i),"Aname" + String.valueOf(i) + "@email.com");
}
}
}
This :-
:-
2019-07-04 15:52:03.813 7758-7758/aso.so56873021recopydb D/ADDPRMRYKEY: Initiated adding the primary key.
2019-07-04 15:52:03.830 7758-7758/aso.so56873021recopydb D/ADDPRMRYKEY: PRIMARY KEY clause not found for table contact_info
2019-07-04 15:52:03.830 7758-7758/aso.so56873021recopydb D/ADDPRMRYKEY: PRIMARY KEY clause not found in the CREATE TABLE SQL so doing nothing.
2019-07-04 15:52:03.888 7758-7758/aso.so56873021recopydb I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@791f7af
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: 0 {
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: contact_id=-1179778271
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: name=Aname0
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: email=Aname0@email.com
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: }
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: 1 {
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: contact_id=1334348157
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: name=Aname1
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: email=Aname1@email.com
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: }
2019-07-04 15:52:03.890 7758-7758/aso.so56873021recopydb I/System.out: 2 {
2019-07-04 15:52:03.891 7758-7758/aso.so56873021recopydb I/System.out: contact_id=1123604651
2019-07-04 15:52:03.891 7758-7758/aso.so56873021recopydb I/System.out: name=Aname2
2019-07-04 15:52:03.891 7758-7758/aso.so56873021recopydb I/System.out: email=Aname2@email.com
As can be seen from the output above the addPrimaryKey method has been called but it did nothing because of D/ADDPRMRYKEY: PRIMARY KEY clause not found in the CREATE TABLE SQL so doing nothing.
As such it's just a matter of changing the CREATE SQL to include the new column to be :-
public static final String CREATE_TABLE = "create table " + ContactContract.ContactEntry.TABLE_NAME +
"(" +
ContactContract.ContactEntry.KEY_P + " INTEGER PRIMARY KEY," + //<<<<<<<<<< comment out this line for first run to generate data
ContactContract.ContactEntry.CONTACT_ID + " number," +
ContactContract.ContactEntry.NAME + " text," +
ContactContract.ContactEntry.EMAIL + " text" +
");";
And the result is as per :-
2019-07-04 15:56:47.170 7979-7979/aso.so56873021recopydb D/ADDPRMRYKEY: Initiated adding the primary key.
2019-07-04 15:56:47.175 7979-7979/aso.so56873021recopydb D/ADDPRMRYKEY: PRIMARY KEY clause not found for table contact_info
2019-07-04 15:56:47.176 7979-7979/aso.so56873021recopydb D/ADDPRMRYKEY: PRIMARY KEY clause located in CREATE TABLE SQL so !!!!ALTERING!!!! table contact_info
2019-07-04 15:56:47.176 7979-7979/aso.so56873021recopydb D/ADDPRMRYKEY: RENAMED TABLE contact_info to OLDcontact_info
2019-07-04 15:56:47.177 7979-7979/aso.so56873021recopydb D/ADDPRMRYKEY: CREATED new version of table contact_info !!!!INSERTING DATA EXTRACTED!!!! from old version
2019-07-04 15:56:47.179 7979-7979/aso.so56873021recopydb I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@791f7af
2019-07-04 15:56:47.179 7979-7979/aso.so56873021recopydb I/System.out: 0 {
2019-07-04 15:56:47.179 7979-7979/aso.so56873021recopydb I/System.out: KEYP=1
2019-07-04 15:56:47.179 7979-7979/aso.so56873021recopydb I/System.out: contact_id=-1179778271
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: name=Aname0
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: email=Aname0@email.com
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: }
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: 1 {
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: KEYP=2
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: contact_id=1334348157
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: name=Aname1
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: email=Aname1@email.com
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: }
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: 2 {
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: KEYP=3
2019-07-04 15:56:47.180 7979-7979/aso.so56873021recopydb I/System.out: contact_id=1123604651
2019-07-04 15:56:47.181 7979-7979/aso.so56873021recopydb I/System.out: name=Aname2
2019-07-04 15:56:47.181 7979-7979/aso.so56873021recopydb I/System.out: email=Aname2@email.com
i.e. the data has been retained but the new column has been introduced and values assigned.