I have 2 tables : registereduser, listbands. listbands has foreign key referencing registereduser(id)
I managed to create both tables in the databases, however when i create a new user the listbands fields are empty! It should have the id of registereduser.
Here is my code :
public class DatabaseHelper extends SQLiteOpenHelper {
//User table
public static final String DATABASE_NAME ="bands.db";
public static final String TABLE_USER ="registereduser";
public static final String COL_USER_ID ="BID";
public static final String COL_USER_EMAIL ="email";
public static final String COL_USER_PASS ="password";
//bands table
public static final String TABLE_LIST ="listbands";
public static final String COL_LIST_ID ="LID";
public static final String COL_LIST_FK ="BID";
public static final String COL_LIST_NAME ="name";
public DatabaseHelper(#Nullable Context context ) {
super(context, DATABASE_NAME, null , 1);
}
#Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL("CREATE TABLE registereduser (BID INTEGER PRIMARY KEY AUTOINCREMENT, " +
"email TEXT , password TEXT )");
sqLiteDatabase.execSQL("CREATE TABLE listbands (LID INTEGER PRIMARY KEY AUTOINCREMENT ," +
"BID INTEGER NOT NULL, name TEXT, FOREIGN KEY(BID) REFERENCES registereduser(BID) )");
}
#Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + TABLE_USER);
sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + TABLE_LIST);
onCreate(sqLiteDatabase);
}
#Override
public void onOpen(SQLiteDatabase sqLiteDatabase) {
super.onOpen(sqLiteDatabase);
//enable foreign key constraints like ON UPDATE CASCADE, ON DELETE CASCADE
sqLiteDatabase.execSQL("PRAGMA foreign_keys=ON;");
}
public long addUser(String email, String password) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(" email ",email);
contentValues.put(" password ",password);
long res = db.insert("registereduser", null, contentValues);
db.close();
return res;
}
public long addList(String name) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues contentLists = new ContentValues();
contentLists.put(" name ",name);
long rest = db.insert("listbands", null, contentLists);
db.close();
return rest;
}
public boolean checkUser ( String email, String password){
String[] column = {COL_USER_ID};
SQLiteDatabase db = getReadableDatabase();
String choice = COL_USER_EMAIL + "=?" + " and " + COL_USER_PASS + "=?";
String[] choiceArgs = { email, password};
Cursor cursor = db.query(TABLE_USER, column, choice, choiceArgs, null, null, null);
int count = cursor.getCount();
cursor.close();
db.close();
if (count>0)
return true;
else
return false;
}
}
and this is the screenshoot that my listbands is empty:
this is screenshoot from sqlite manager
Defining a Foreign Key doesn't automatically insert data into the child table (listbands table) when a row is added to the parent table (registereduser table).
That is a Foreign Key defines a constraint (rule) saying that the value of the child column(s) must be in a row of the parent table's column(s) when inserting or updating rows in the child table.
You need to add the rows to the child table as and when required.
What the screenshot is showing is that there are now rows in the listBands table.
Currently your code has an addList method this will not work as it is as the result will be a NOT NULL constraint conflict. However, it won't fail as the exception is trapped by the insert method. However if you were to look at the value returned it would be -1 rather than 1 or greater (the id of the inserted row).
You would need to use something like :-
public long addListProper(String name, long id) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put(COL_LIST_FK,id);
cv.put(COL_LIST_NAME,name);
return db.insert(TABLE_LIST,null,cv);
}
to insert a listbands row.
Example
Consider the following which :-
Deletes all rows form the listbands table.
Deletes all rows from the registereduser table.
Note that as registereduser may include parents of listbands rows then listbands should be deleted first.
These two steps are just to make it so the tables are empty each time the demo is run.
Adds a user
Adds a list (that WILL NOT be added)
Adds a list using the alternative/proper insert that uses the ID of the user
Logs info regrading the id's of the inserts.
Extracts the rows from the tables into a cursor which is dumped to the log.
The code :-
mDBHlpr = new DatabaseHelper(this);
mDBHlpr.getWritableDatabase().delete(DatabaseHelper.TABLE_LIST,null,null);
mDBHlpr.getWritableDatabase().delete(DatabaseHelper.TABLE_USER,null,null);
long this_user = mDBHlpr.addUser("Fred","1234567890");
long this_list = mDBHlpr.addList("Blah"); //<<<<<<<<<< WILL NOT WORK
long alt_list = mDBHlpr.addListProper("blah blah",this_user); //<<<<<<<<< SHOULD WORK
Log.d("INSERTIFO","ID of user was " + this_user + " ID of list was " + this_list);
DatabaseUtils.dumpCursor(
mDBHlpr.getWritableDatabase().query(DatabaseHelper.TABLE_USER,null,null,null,null,null,null)
);
DatabaseUtils.dumpCursor(
mDBHlpr.getWritableDatabase().query(DatabaseHelper.TABLE_LIST,null,null,null,null,null,null)
);
Results
First in the log will be :-
2019-10-27 12:45:00.152 32338-32338/aso.so58575523foreignkeys E/SQLiteDatabase: Error inserting name =Blah
android.database.sqlite.SQLiteConstraintException: NOT NULL constraint failed: listbands.BID (code 1299 SQLITE_CONSTRAINT_NOTNULL)
at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:879)
at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:790)
at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:88)
at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1599)
at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java:1468)
at aso.so58575523foreignkeys.DatabaseHelper.addList(DatabaseHelper.java:76)
at aso.so58575523foreignkeys.MainActivity.onCreate(MainActivity.java:22)
at android.app.Activity.performCreate(Activity.java:7802)
at android.app.Activity.performCreate(Activity.java:7791)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
i.e. the addList has failed because BID column is null and thus the row is not inserted because of the NOT NULL constraint. Although the stack-trace is in the log the exception was trapped and thus processing continues.
The Log will then have :-
2019-10-27 12:45:00.165 D/INSERTIFO: ID of user was 3 ID of list was -1 ID of alternative list item was 2
see how the attempt to use addList resulted in an id of -1.
then :-
2019-10-27 12:45:00.165 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor#561c44
2019-10-27 12:45:00.166 I/System.out: 0 {
2019-10-27 12:45:00.166 I/System.out: BID=3
2019-10-27 12:45:00.166 I/System.out: email=Fred
2019-10-27 12:45:00.166 I/System.out: password=1234567890
2019-10-27 12:45:00.166 I/System.out: }
2019-10-27 12:45:00.166 I/System.out: <<<<<
2019-10-27 12:45:00.167 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor#1def12d
2019-10-27 12:45:00.167 I/System.out: 0 {
2019-10-27 12:45:00.167 I/System.out: LID=2
2019-10-27 12:45:00.167 I/System.out: BID=3
2019-10-27 12:45:00.167 I/System.out: name=blah blah
2019-10-27 12:45:00.167 I/System.out: }
2019-10-27 12:45:00.167 I/System.out: <<<<<
As can be seen the 2nd attempt (using addListProper) has added a row to listbands and there was no Foreign Key constraint conflict or the NOT NULL constraint conflict that originally stopped the row being added.
Extra
consider this line of code :-
long otheralter_list = mDBHlpr.addListProper("not blah",100); //<<<<<<< FK constraint as no user with an ID of 100.
A row is trying to be added that references a user with an id of 100 (no such user exists). The row will not be inserted (but again will not result in a failure) because of the Foreign Key constraint as no row in the registereduser table (the parent table of the Foreign Key) has 100 in the BID column (the parent column).
The result in the log would be :-
2019-10-27 13:12:39.272 32564-32564/? E/SQLiteDatabase: Error inserting BID=100 name=not blah
android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY)
at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:879)
at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:790)
at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:88)
at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1599)
at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java:1468)
at aso.so58575523foreignkeys.DatabaseHelper.addListProper(DatabaseHelper.java:86)
at aso.so58575523foreignkeys.MainActivity.onCreate(MainActivity.java:24)
at android.app.Activity.performCreate(Activity.java:7802)
at android.app.Activity.performCreate(Activity.java:7791)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Additional Re comment
So i must use CONSTRAINT in the listbands table to create a foreign key?
No you have correctly defined the Foreign Key. CONSTRAINT ???? FOREIGN KEY just gives the Constraint a name. It doesn't alter what the constraint does.
The listbands table also dont have ID even though its created as AUTOINCREMENT
No. The screen shots shows that there is no data at all in the listbands table (due to the NOT NULL constraint as explained above).
AUTOINCREMENT does very little INTEGER PRIMARY KEY is what allows an auto-generated value to be provided. Although this isn't actually correct as all that INTEGER PRIMARY KEY does is make the column an alias of the normally hidden rowid column, which is an automatically generated column for all tables except those defined using WITHOUT ROWID.
AUTOINCREMENT supplements the alias with an additional rule, that SQLite will adhere to if possible saying that the automatically generated value is greater then any previously allocated. Without AUTOINCREMENT and IF the largest possible value of the rowid (9223372036854775807) has been used then a lower value may be used. Without AUTOINCREMENT then SQLIte will instead issue an SQLITE_FULL exception.
AUTOINCREMENT uses an additional table, sqlite_sequence, to store the last assigned value of the rowid. As such inefficiencies are introduced in managing this extra table.
If AUTOINCREMENT WERE removed then there would be no noticeable difference.
Related
I'm working on a simple SQLite CRUD application and I want to add data to manually created database in SQLite. But when I'm adding data, the app stops and shows the below error message
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.sqlitecrudexample, PID: 14124
android.database.sqlite.SQLiteConstraintException: NOT NULL constraint failed: employees.id (code 1299 SQLITE_CONSTRAINT_NOTNULL)
at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)
at android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:890)
at android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:756)
at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:66)
at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:1920)
at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1897)
at com.example.sqlitecrudexample.MainActivity.addEmployee(MainActivity.kt:70)
at com.example.sqlitecrudexample.MainActivity.access$addEmployee(MainActivity.kt:13)
at com.example.sqlitecrudexample.MainActivity$onCreate$1.onClick(MainActivity.kt:30)
at android.view.View.performClick(View.java:7448)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992)
at android.view.View.performClickInternal(View.java:7425)
at android.view.View.access$3600(View.java:810)
at android.view.View$PerformClick.run(View.java:28305)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
I/Process: Sending signal. PID: 14124 SIG: 9
Here is my code for creating and adding data to the table
private fun addEmployee(){
var name:String = editTextName.text.toString().trim()
var salary:String = editTextSalary.text.toString().trim()
var dept = spinnerDepartment.selectedItem.toString()
var calendar = Calendar.getInstance()
var simpleDateFormat = SimpleDateFormat("yyyy-mm-dd hh:mm:ss")
var joiningDate = simpleDateFormat.format(calendar.time)
if(inputsAreCorrect(name,salary)){
val insertSQL = """
INSERT INTO employees
(name, department, joiningdate, salary)
VALUES
(?, ?, ?, ?);
""".trimIndent()
mDatabase.execSQL(insertSQL, arrayOf(name, dept, joiningDate, salary))
Toast.makeText(this,"Employee Added Successfully",Toast.LENGTH_SHORT).show()
}
}
private fun createEmployeeTable() {
mDatabase.execSQL(
"""CREATE TABLE IF NOT EXISTS employees (
id int PRIMARY KEY AUTOINCREMENT NOT NULL,
name varchar(200) NOT NULL,
department varchar(200) NOT NULL,
joiningdate datetime NOT NULL,
salary double NOT NULL
);"""
)
}
And this is my data class
data class Employee(
var id: Int,
var name: String,
var dept: String,
var joiningDate: String,
var salary: Double
)
Change SQL statement to 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL` for id row. You need to use primitive type
You are using incorrect syntax while defining id row, You have to use Integer as keyword AUTOINCREMENT can be used with INTEGER field only. Change your create table syntax as below and it will work
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
If you really tried to create the table with this statement:
CREATE TABLE IF NOT EXISTS employees (
id int PRIMARY KEY AUTOINCREMENT NOT NULL,
..........................
);
the result would be this error:
AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY
So, my guess is that the table was not created by that statement, but by a previous statement on which you made changes later. These changes though were not actually reflected to the database, because the table already existed.
What you have to do is either uninstall the app from the device so the db is deleted, or change the version of the db in your SQLiteOpenHelper class so the onUpgrade() method is called which will delete and recreate the table.
So change the CREATE statement to:
CREATE TABLE IF NOT EXISTS employees (
id INTEGER PRIMARY KEY AUTOINCREMENT,
..........................
);
Also, don't use execSQL() to insert rows in the table.
The recommended method is insert():
val cv = ContentValues()
cv.put("name", name)
cv.put("department", dept)
cv.put("joiningdate", joiningDate)
cv.put("salary", salary)
mDatabase.insert("employees", null, cv)
I have a table in room database with a field set as index and it's autoincrement. Sqlite will save some meta data in its master table to keep count of the last auto-generated value.
Based on my app logic I will clear database and keep the structure; suppose I have inserted 3 items to the database and the mentioned action takes place so I clear items, but when I insert a new item its auto-generated field will be 4 which will cause overflow and app crash in the long run. I worked it around by removing autoincrement and setting the field manually!
Now my question is how can I reset the auto-incremented field value to be set to 1 after each database clearance (I will prefer room only way)?
The way that autoincrement works is that 2 values are used when determining a new value :-
the first is equivalent to using max(the_autoincrement_column) (i.e the column that aliases the rowid column that has AUTOINCREMENT coded),
the second is obtained from the table sqlite_sequence from the seq column of the row that has the table name in the name column.
Note that the value(s) are not stored in THE master table, sqlite_master (the schema) but in the sqlite_sequence table.
The sqlite_sequence table will only exist if AUTOINCREMENT has been used.
1 is added to the greater value.
To reset, in theory, you should delete all rows from the table and delete the respective row from the sqlite_sequence table.
However, room protects system tables. So in short there appears to be no way of using room to do the latter and hence the issue. Here is answer is an example that does the above BUT it has to be run outside of (before) room and is thus limited.
Note in the answer there is additional code that is used to start numbering from 0 (the Trigger).
However in regards to overflow then it's basically highly unlikely as per :-
Maximum Number Of Rows In A Table
The theoretical maximum number of rows in a table is 2 to the power of 64
(18446744073709551616 or about 1.8e+19). This limit is unreachable
since the maximum database size of 140 terabytes will be reached
first. A 140 terabytes database can hold no more than approximately
1e+13 rows, and then only if there are no indices and if each row
contains very little data.
Limits In SQLite
With autoincrement it is 2 to power of 63 (9,223,372,036,854,775,808) (without autoincrement you can use negative values(java) so you can utilise the 64th bit hence the thoerectical maximum) as such the limitation would likely be disk capacity rather than the highest id being reached.
Additional
After some playing around, the following does reset the sequence whilst Room has the database.
That is the following builds the Room Database inserts two rows, resets the sequence (including deleting the recently added rows)
by opening the database as a standard SQLiteDatabase
Note the use of both OPENREADWRITE and ENABLEWRITEAHEADLOGGING
(if not the latter then a warning message saying that WAL can't be turned off as the db is open, so this just opens it in WAL mode)
deleting the existing rows in the table and
deleting the respective row from sqlite_sequence and finally
closing this other database.
:-
public class MainActivity extends AppCompatActivity {
public static final String DBNAME = "mydatabase";
public static final String MYTABLENAME = "mytable";
MyDatabase mydb,mydb2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mydb = Room.databaseBuilder(this,MyDatabase.class,DBNAME).allowMainThreadQueries().build();
MyTableDAO mytabledao = mydb.getMyTableDAO();
MyTable mt1 = new MyTable();
mt1.setName("Test001");
mytabledao.insert(mt1);
MyTable mt2 = new MyTable();
mt2.setName("Test002");
mytabledao.insert(mt2);
for (MyTable mt: mytabledao.getAllMyTables()) {
Log.d("MYTABLEROW","ID=" + String.valueOf(mt.getId()) + " Name=" + mt.getName());
}
/*
while (mydb.isOpen()) {
mydb.close();
}
Ouch if used :-
E/ROOM: Invalidation tracker is initialized twice :/. (ignored)
E/ROOM: Cannot run invalidation tracker. Is the db closed?
java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
*/
resetSequencedTable(MYTABLENAME);
//mydb2 = Room.databaseBuilder(this,MyDatabase.class,DBNAME).allowMainThreadQueries().build(); // No Good
/*
Works even though :-
05-12 12:31:40.112 28585-28585/? D/MYTABLEROW: ID=1 Name=Test001
05-12 12:31:40.112 28585-28585/? D/MYTABLEROW: ID=2 Name=Test002
05-12 12:31:40.114 28585-28585/? E/SQLiteLog: (5) statement aborts at 2: [PRAGMA journal_mode=PERSIST]
05-12 12:31:40.115 28585-28585/? W/SQLiteConnection: Could not change the database journal mode of '/data/user/0/soa.myapplication/databases/mydatabase' from 'wal' to 'PERSIST' because the database is locked. This usually means that there are other open connections to the database which prevents the database from enabling or disabling write-ahead logging mode. Proceeding without changing the journal mode.
05-12 12:31:40.126 28585-28585/? D/MYTABLEROW: ID=1 Name=Test003
05-12 12:31:40.126 28585-28585/? D/MYTABLEROW: ID=2 Name=Test004
*/
for (MyTable mt: mytabledao.getAllMyTables()) {
Log.d("MYTABLEROW","ID=" + String.valueOf(mt.getId()) + " Name=" + mt.getName());
}
MyTable mt3 = new MyTable();
mt3.setName("Test003");
mytabledao.insert(mt3);
MyTable mt4 = new MyTable();
mt4.setName("Test004");
mytabledao.insert(mt4);
for (MyTable mt: mytabledao.getAllMyTables()) {
Log.d("MYTABLEROW","ID=" + String.valueOf(mt.getId()) + " Name=" + mt.getName());
}
}
private void resetSequencedTable(String table) {
Log.d("RESETSEQ","Initiating sequence reset");
SQLiteDatabase db = SQLiteDatabase.openDatabase(this.getDatabasePath(DBNAME).toString(),null,SQLiteDatabase.OPEN_READWRITE | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING);
db.delete(table,null,null);
String whereclause = "name=?";
String[] whereargs = new String[]{table};
db.delete("sqlite_sequence",whereclause,whereargs);
db.close();
Log.d("RESETSEQ", "Terminating sequence reset");
}
}
The Entity for the table is :-
#Entity(tableName = MainActivity.MYTABLENAME)
public class MyTable {
#PrimaryKey(autoGenerate = true)
private long id;
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
I'm trying to insert rows in my Student Table which contains two rows : ID and Name
Here is the addHandler function which is implemented in MyDBHandler class :
public void addHandler(Student student) {
ContentValues values = new ContentValues();
values.put(COLUMN_ID, student.getID());
values.put(COLUMN_NAME, student.getStudentName());
SQLiteDatabase db = this.getWritableDatabase();
db.insert(TABLE_NAME, null, values);
db.close();
}
The onCreate method is :-
public void onCreate(SQLiteDatabase db) {
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + COLUMN_ID + " INTEGER PRIMARY KEY ," + COLUMN_NAME + " TEXT )";
db.execSQL(CREATE_TABLE);
}
The attributes of MyDBHandler class which extends SQLiteOpenHelper :
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_NAME = "studentDB.db";
public static final String TABLE_NAME = "Student";
public static final String COLUMN_ID = "StudentID";
public static final String COLUMN_NAME = "StudentName";
I have a ADD Button in my activity_main.xml file and here is the code behind :
public void add(View view){
MyDBHandler dbHandler = new MyDBHandler(this, null, null, 1);
int id = Integer.parseInt(studentIdText.getText().toString());
String name = studentNameText.getText().toString();
Student student = new Student(id, name);
dbHandler.addHandler(student);
studentIdText.setText("");
studentNameText.setText("");
}
The app is running perfectly but when i want to insert a row in the table , i get the following errors in Run Tab :
E/SQLiteLog: (1) table Student has no column named StudentID
E/SQLiteDatabase: Error inserting StudentName=yassine StudentID=10
android.database.sqlite.SQLiteException: table Student has no column named StudentID (code 1): , while compiling: INSERT INTO Student(StudentName,StudentID) VALUES (?,?)
#################################################################
Error Code : 1 (SQLITE_ERROR)
Caused By : SQL(query) error or missing database.
(table Student has no column named StudentID (code 1): , while compiling: INSERT INTO Student(StudentName,StudentID) VALUES (?,?))
#################################################################
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:1093)
at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:670)
at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:59)
at android.database.sqlite.SQLiteStatement.<init>(SQLiteStatement.java:31)
at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1607)
at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java:1479)
at com.example.test.MyDBHandler.addHandler(MyDBHandler.java:48)
at com.example.test.MainActivity.add(MainActivity.java:40)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:385)
at android.view.View.performClick(View.java:5246)
at android.widget.TextView.performClick(TextView.java:10566)
at android.view.View$PerformClick.run(View.java:21256)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6917)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
Any recommendations ?
Your issue is very likely a mis-conception in regard to the onCreate method. That is it onCreate doesn't run every time the App is run. onCreate will only run if the database doesn't actually exist. If the database has already been created by another previous run of the App then the onCreate method is not run.
As such any changes made to the structure of the database, as typically applied in the onCreate method will not be applied.
It would appear that you have added the defnition for the StudentId column, to the code in the onCreate method, after the App has been run.
As long as you have no data that needs to be preserved (which is very likely) then the simplest solution is to do 1 of the following :-
delete or clear the App's data (via settings/Apps)
uninstall the App
and then rerun the App, the database will then be created using the code as per the modified onCreate code.
I am trying to make the cart of a shopping app where I first query a cart element and from the id of a cart, list element find the corresponding meta-data related to the product by querying another table containing product information. I am able to successfully query the product list while showing the "menu" and am trying to apply the same code to the cart. Yet it tells me that the column "_id" doesn't exist. I have stuck on this for a while.
You can find the entire project on GitHub
Here are important parts of relevant files
YourCart.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_your_cart);
ContentValues values = new ContentValues();
values.put(CartContract.CartEntry._ID, 2);
values.put(CartContract.CartEntry.COLUMN_NAME_ORDERED_QUANTITY, 37);
getContentResolver().insert(CartContract.CartEntry.CONTENT_URI, values);
String[] projection = {
CartContract.CartEntry._ID,
CartContract.CartEntry.COLUMN_NAME_ORDERED_QUANTITY
};
//gets the entire cart
cart = getContentResolver().query(CartContract.CartEntry.CONTENT_URI, projection, null, null, null);
ListView cartList = findViewById(R.id.CartListView);
cartList.setAdapter(new cartAdapter(YourCart.this, cart));
}
// Following is part of cartAdapter
#Override
public void bindView(View view, Context context, Cursor cart) {
prodName = view.findViewById(R.id.cartListElementProductNameTextView);
prodPrice = view.findViewById(R.id.cartListElementProductPriceTextView);
//Projection is just the name of the columns we would like to receive
String[] projection = {
ProductListContract.ProductEntry.COLUMN_NAME_PRODUCT_THUMBNAIL,
ProductListContract.ProductEntry.COLUMN_NAME_PRODUCT_NAME,
ProductListContract.ProductEntry.COLUMN_NAME_PRODUCT_PRICE
};
Integer ui = cart.getInt(cart.getColumnIndexOrThrow(CartContract.CartEntry._ID));
String[] hoho = {ui.toString()};
Cursor productCursor = getContentResolver().query(ProductListContract.ProductEntry.CONTENT_URI, projection, ProductListContract.ProductEntry._ID, hoho, null);
prodName.setText(productCursor.getInt(productCursor.getColumnIndexOrThrow(ProductListContract.ProductEntry.COLUMN_NAME_PRODUCT_NAME)));
ui = productCursor.getInt(productCursor.getColumnIndexOrThrow(ProductListContract.ProductEntry.COLUMN_NAME_PRODUCT_PRICE));
prodPrice.setText(ui.toString());
productCursor.close();
}
I'm pretty sure the column gets created when the table is created as can be seen here from an excerpt from the Database Helper
public static final String SQL_CREATE_ENTRIES =
"CREATE TABLE " + TABLE_NAME + " ( " +
_ID + " INTEGER NON NULL, " +
COLUMN_NAME_ORDERED_QUANTITY + " INTEGER)";
Finally here is the log of the crash. The app crashes as soon as the YourCart activity is launched
03-16 09:50:30.987 11672-11672/com.example.tanmay.shoppingapp E/SQLiteLog: (1) table cart has no column named _id
03-16 09:50:30.991 11672-11672/com.example.tanmay.shoppingapp E/SQLiteDatabase: Error inserting quantity=37 _id=2
android.database.sqlite.SQLiteException: table cart has no column named _id (code 1): , while compiling: INSERT INTO cart(quantity,_id) VALUES (?,?)
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:889)
at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:500)
at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58)
at android.database.sqlite.SQLiteStatement.<init>(SQLiteStatement.java:31)
at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1472)
at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java:1343)
at com.example.tanmay.shoppingapp.DataSet.DataProvider.insertCart(DataProvider.java:169)
at com.example.tanmay.shoppingapp.DataSet.DataProvider.insert(DataProvider.java:155)
at android.content.ContentProvider$Transport.insert(ContentProvider.java:264)
at android.content.ContentResolver.insert(ContentResolver.java:1279)
at com.example.tanmay.shoppingapp.YourCart.onCreate(YourCart.java:34)
at android.app.Activity.performCreate(Activity.java:6684)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1119)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2652)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2766)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1507)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6236)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:891)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:781)
03-16 09:50:30.991 11672-11672/com.example.tanmay.shoppingapp E/com.whatever.tag: Failed to insert row for content://com.example.tanmay.shoppingapp/cart
03-16 09:50:30.992 11672-11672/com.example.tanmay.shoppingapp E/SQLiteLog: (1) no such column: _id
03-16 09:50:30.994 11672-11672/com.example.tanmay.shoppingapp D/AndroidRuntime: Shutting down VM
03-16 09:50:30.996 11672-11672/com.example.tanmay.shoppingapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.tanmay.shoppingapp, PID: 11672
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.tanmay.shoppingapp/com.example.tanmay.shoppingapp.YourCart}: android.database.sqlite.SQLiteException: no such column: _id (code 1): , while compiling: SELECT _id, quantity FROM cart
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2699)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2766)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1507)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6236)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:891)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:781)
Caused by: android.database.sqlite.SQLiteException: no such column: _id (code 1): , while compiling: SELECT _id, quantity FROM cart
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:889)
at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:500)
at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58)
at android.database.sqlite.SQLiteQuery.<init>(SQLiteQuery.java:37)
at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:44)
at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1318)
at android.database.sqlite.SQLiteDatabase.queryWithFactory(SQLiteDatabase.java:1165)
at android.database.sqlite.SQLiteDatabase.query(SQLiteDatabase.java:1036)
at android.database.sqlite.SQLiteDatabase.query(SQLiteDatabase.java:1204)
at com.example.tanmay.shoppingapp.DataSet.DataProvider.query(DataProvider.java:92)
at android.content.ContentProvider.query(ContentProvider.java:1020)
at android.content.ContentProvider$Transport.query(ContentProvider.java:239)
at android.content.ContentResolver.query(ContentResolver.java:534)
at android.content.ContentResolver.query(ContentResolver.java:475)
at com.example.tanmay.shoppingapp.YourCart.onCreate(YourCart.java:44)
at android.app.Activity.performCreate(Activity.java:6684)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1119)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2652)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2766)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1507)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6236)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:891)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:781)
For some reason the table does not contain the column named _id. However, it is not because of the SQL, even though the SQL likely does not do what you wish.
More specifically the use of INTEGER NON(instead of NOT)NULL will rather than add the NOT NULL constraint, it will give the column a column_type of INTEGER NON which will equate, as it contains INT, to a column-type (affinity) of INTEGER.
As an example (this utilises the logDatabaseInfo to be found here) which with the SQL as :-
CREATE TABLE cart (_ID INTEGER NON NULL, quantity INTEGER)
Shows :-
D/SQLITE_CSU: DatabaseList Row 1 Name=main File=/data/data/soupd.so49313202updatefailed/databases/mydb
D/SQLITE_CSU: Database Version = 1
D/SQLITE_CSU: Table Name = android_metadata Created Using = CREATE TABLE android_metadata (locale TEXT)
D/SQLITE_CSU: Table = android_metadata ColumnName = locale ColumnType = TEXT Default Value = null PRIMARY KEY SEQUENCE = 0
D/SQLITE_CSU: Table Name = cart Created Using = CREATE TABLE cart (_ID INTEGER NON NULL, quantity INTEGER)
D/SQLITE_CSU: Table = cart ColumnName = _ID ColumnType = INTEGER NON Default Value = null PRIMARY KEY SEQUENCE = 0
D/SQLITE_CSU: Table = cart ColumnName = quantity ColumnType = INTEGER Default Value = null PRIMARY KEY SEQUENCE = 0
Table = cart ColumnName = _ID ColumnType = INTEGER NON Default Value = null being the pertinent information.
The SQL should likely be :-
CREATE TABLE cart (_ID INTEGER NOT NULL, quantity INTEGER)
Note normally _id/_ID is used for a column that holds a unique identifier that is automatically generated by SQLite, and thus
typically you would have _ID INTEGER PRIMARY KEY for the column
definition. Coding this and not providing a value for the _id will
result in the unique identifier being generated by SQLite (typically
1,2,3,4.....).
Note I haven't shown this in the SQL below because you would need to look at inserting without the id.
The Likely Real Issue
The real issue is elsewhere but is very likely due to the DatabaseHelper's onCreate method not having been called. It has likely been called once as the database exists. So the most likely issue is that the table structure has been changed but the database hasn't been deleted.
More specifically the onCreate method is only invoked (automatically) once when the database is created.
The Likely Fix
The likely fix is that the database should be deleted and then the App rerun.
- You can delete the database by deleting the App's data
- or by uninstalling the App.
- I'd suggest changing the SQL as shown above.
try replacing your create SQL_CREATE_ENTRIES statement by this,
public static final String SQL_CREATE_ENTRIES =
"CREATE TABLE " + TABLE_NAME + " ( " +
_ID + " INTEGER NOT NULL, " +
COLUMN_NAME_ORDERED_QUANTITY + " INTEGER)";
your query has invalid key word which is "NON" in _ID column.
I'm working on a android program with SQLite. I'm trying to create a datebase with two tables related by a foreign key, and I want to automaticaly populate one entry of the mother table using the insert funcion. But this generate an SQLite error.
Here is the funcion to insert an entry into the mother class
private long new_event(SQLiteDatabase db)
{
ContentValues values = new ContentValues();
long id = db.insert("EVENT",null,values);
return id;
}
Here is the function to insert an entry into the child class
public long new_specific_event(SQLiteDatabase db)
{
long id_event = new_event(db);
ContentValues values = new ContentValues();
values.put("id_event", id_event);
values.put("whatsoever", "whatsoever");
long id = db.insert("SPECIFIC_EVENT",null,values);
return id;
}
Here is the mother table
CREATE TABLE EVENT (id INTEGER PRIMARY KEY AUTOINCREMENT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
and here is the child table
CREATE TABLE SPECIFIC_EVENT (id INTEGER PRIMARY KEY AUTOINCREMENT, id_event NUMBER,whatsoever TEXT,FOREIGN KEY(id_event) REFERENCES EVENT(id));
This result into the following error
android.database.sqlite.SQLiteException: near "null": syntax error (code 1): , while compiling: INSERT INTO EVENT(null) VALUES (NULL)
I could do it using this and the db.execSQL() funcion, but then I have no access to the id of the entry I just create.
So, how can I use the insert funcion to insert an entry with just default value?
With ContentValues you need to put at least one value. To get a default value, put a null for a value for a column:
ContentValues values = new ContentValues();
values.putNull("_id");
long id = db.insert("EVENT",null,values);
Inserting a completely empty row is not possible, so the insert() method has the parameter nullColumnHack to allow you to specify a column that gets a NULL value in this case:
ContentValues values = new ContentValues();
long id = db.insert("EVENT", "_id", values);