On devices with modified android versions i get this error. For example on Xiaomi devices.
String query = "select * from dialogues where userId = ? and topChat = 0 order by updatedAtByClient desc";
Cursor dialogRes = db.rawQuery(query, new String[]{userId});
Here i get exception:
android.database.sqlite.SQLiteException: no such column: topChat (code 1):,
while compiling: select * from dialogues where userId = ? and topChat = 0
order by updatedAtByClient desc
I have written the exception message by hand, because the user has sent me it in a screenshot, so there might be typos.
How can this be fixed, and why does this happen?
UPD1:
the create table statement looks similar to this:
"CREATE TABLE IF NOT EXISTS dialogues(fieldName VARCHAR, camelCaseFieldName VARCHAR,
topChat INTEGER, createdAt DATE);";
And i have a correctly implemented update method for when im changing the DB structure, but this particular table and field name did not change for a long time.
UPD2:
i have made an apk for the user with problems, that logs that table columns, and i did see the problematic column in the log, and user says that this version works ok.
So seems that this error does not happen 100% of times. Very strange. Maybe there is a way to check the database for integrity after creating it, and recreate tables with errors?
I don't believe this would be a xiaomi issue. it rather seems be the result of an unfortunate migration, where a new column had not been added and subsequently, the user might still work with the previous version of the table. and there is no other logical explanation for an absent column), simply because either the CREATE TABLE statement works - or it doesn't.
one can still work around it with ALTER TABLE. eg. when that SQLiteException occurs, addColumnIfNotExists("dialogues", "topChat", "INTEGER DEFAULT 0"); ...in order not to cause data-loss by dropping the table, only because it lacks some column.
public void addColumnIfNotExists(String tableName, String columnName, String dataType) {
Cursor cursor = null;
try {
cursor = db.rawQuery("SELECT * FROM " + tableName, null);
if(! Arrays.asList(cursor.getColumnNames()).contains(columnName)) {
db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s %s", tableName, columnName, dataType));
}
} finally {
if(cursor != null) {
cursor.close();
}
}
}
Related
I've been stuck on this problem with my sqlitedatabase for several days now.
My insert method works, I break after it has been run and check that the database now contain rows (before it was empty), by querying for the entire database, which works.
This is the insert method which returns the generated puzzleId.
public int savePuzzleToEdit(ContentValues puzzle, Integer puzzleId) {
Integer id = puzzle.getAsInteger(General.ID);
if (id == 0 && puzzleId == null) {
puzzle.remove(General.ID);
puzzleId = (int) ourDatabase.insert(PUZZLE_TABLE, null, puzzle);
}else {
....
}
return puzzleId
The query method however, does not return any results, aka the cursor is empty.
public ArrayList<ContentValues> getCreatedPuzzles(int puzzleId) {
Cursor c = ourDatabase.query(PUZZLE_TABLE, null, "_id = " + puzzleId, null, null, null, null, null);
while (c.moveToNext()) { // Generate return list ...}
I have tried breaking after the insert and before the query in order to see if there is indeed rows in the database, which there is. I am however not able to query by selecting by id. I can however, query by the other fields in the table and get the correct rows as a result, so it has to be the _id field which has an error.
I have also tried to use the rawQuery method which also works when querying for the entire database or by any of the other fields but id.
This is the create statement:
private static final String PUZZLE_TABLE_CREATE =
"CREATE TABLE puzzle ( _id INTEGER, name varchar(45), category " +
"varchar(25), publishdate DATE, rating INT, timesplayed INT, numberOfQuestions INT);";
As I understood it the id has to be INTEGER in order to auto_increment on insert, which I believe works since the insert method returns a id, which varies from one insert to another.
The query method does not return any errors or exceptions, and it just seems like there are no rows in the table with that id. I have assumed that the returned puzzleId is also being stored as the id in the table, but that might be wrong?!
Any and I mean any suggestions or insights on how to try to fix this problem is highly appreciated.
first thing first your create table statement might be wrong the _id will not autoincrement if you don't mention it in the statement. it should be like this
_id integer primary key autoincrement
and also check your create table statement for other errors
I have an SQLite table:
CREATE TABLE regions (_id INTEGER PRIMARY KEY, name TEXT, UNIQUE(name));
And some Android code:
Validate.notBlank(region);
ContentValues cv = new ContentValues();
cv.put(Columns.REGION_NAME, region);
long regionId =
db.insertWithOnConflict("regions", null, cv, SQLiteDatabase.CONFLICT_IGNORE);
Validate.isTrue(regionId > -1,
"INSERT ON CONFLICT IGNORE returned -1 for region name '%s'", region);
On duplicate rows insertWithOnConflict() is returning -1, indicating an error, and Validate then throws with:
INSERT ON CONFLICT IGNORE returned -1 for region name 'Overseas'
The SQLite ON CONFLICT documentation (emphasis mine) states:
When an applicable constraint violation occurs, the IGNORE resolution algorithm skips the one row that contains the constraint violation and continues processing subsequent rows of the SQL statement as if nothing went wrong. Other rows before and after the row that contained the constraint violation are inserted or updated normally. No error is returned when the IGNORE conflict resolution algorithm is used.
The Android insertWithOnConflict() documentation states:
Returns
the row ID of the newly inserted row OR the primary key of the existing row if the input param 'conflictAlgorithm' = CONFLICT_IGNORE OR -1 if any error
CONFLICT_REPLACE isn't an option, because replacing rows will change their primary key instead of just returning the existing key:
sqlite> INSERT INTO regions (name) VALUES ("Southern");
sqlite> INSERT INTO regions (name) VALUES ("Overseas");
sqlite> SELECT * FROM regions;
1|Southern
2|Overseas
sqlite> INSERT OR REPLACE INTO regions (name) VALUES ("Overseas");
sqlite> SELECT * FROM regions;
1|Southern
3|Overseas
sqlite> INSERT OR REPLACE INTO regions (name) VALUES ("Overseas");
sqlite> SELECT * FROM regions;
1|Southern
4|Overseas
I think that insertWithOnConflict() should, on duplicate rows, return me the primary key (_id column) of the duplicate row — so I should never receive an error for this insert. Why is insertWithOnConflict() throwing an error? What function do I need to call so that I always get a valid row ID back?
The answer to your question, unfortunately, is that the docs are simply wrong and there is no such functionality.
There is an open bug from 2010 that addresses precisely this issue and even though 80+ people have starred it, there is no official response from the Android team.
The issue is also discussed on SO here.
If your use case is conflict-heavy (i.e. most of the time you expect to find an existing record and want to return that ID) your suggested workaround seems the way to go. If, on the other hand, your use case is such that most of the time you expect for there to be no existing record, then the following workaround might be more appropriate:
try {
insertOrThrow(...)
} catch(SQLException e) {
// Select the required record and get primary key from it
}
Here is a self-contained implementation of this workaround:
public static long insertIgnoringConflict(SQLiteDatabase db,
String table,
String idColumn,
ContentValues values) {
try {
return db.insertOrThrow(table, null, values);
} catch (SQLException e) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT ");
sql.append(idColumn);
sql.append(" FROM ");
sql.append(table);
sql.append(" WHERE ");
Object[] bindArgs = new Object[values.size()];
int i = 0;
for (Map.Entry<String, Object> entry: values.valueSet()) {
sql.append((i > 0) ? " AND " : "");
sql.append(entry.getKey());
sql.append(" = ?");
bindArgs[i++] = entry.getValue();
}
SQLiteStatement stmt = db.compileStatement(sql.toString());
for (i = 0; i < bindArgs.length; i++) {
DatabaseUtils.bindObjectToProgram(stmt, i + 1, bindArgs[i]);
}
try {
return stmt.simpleQueryForLong();
} finally {
stmt.close();
}
}
}
While your expectations for the behavior of insertWithOnConflict seems entirely reasonable (you should get the pk for the colliding row), that's just not how it works. What actually happens is that you: attempt the insert, it fails to insert a row but signals no error, the framework counts the number of rows inserted, discovers that the number is 0, and, explicitly, returns -1.
Edited to add:
Btw, this answer is based on the code that, eventually, implements insertWithOnConflict:
int err = executeNonQuery(env, connection, statement);
return err == SQLITE_DONE && sqlite3_changes(connection->db) > 0
? sqlite3_last_insert_rowid(connection->db) : -1;
SQLITE_DONE is good status; sqlite3_changes is the number of inserts in the last call, and sqlite3_last_insert_rowid is the rowid for the newly inserted row, if any.
Edited to answer 2nd question:
After re-reading the question, I think that what you are looking for is a method that does this:
inserts a new row into the db, if that is possible
if it cannot insert the row, fails and returns the rowid for the existing row that conflicted (without changing that rowid)
The whole discussion of replace seems like red herring.
The answer to your 2nd question, then, is that there is no such function.
Problem has already been solved, but this may be an option which solved my issue. Just changing the last parameter to CONFLICT_REPLACE.
long regionId =
db.insertWithOnConflict("regions", null, cv, SQLiteDatabase.CONFLICT_REPLACE);
Hope it helps.
Don't immediately flag me for a duplicate question. My issue is different because I have a correctly formatted SQL query.
public static final String TABLE_NAME = "log";
public static final String COLUMN_ID = "_id";
public static final String LOG_TEXT = "logtext";
private static final String TABLE_CREATE = "CREATE TABLE " + TABLE_NAME + " (" +
COLUMN_ID + " integer primary key autoincrement, " +
LOG_TEXT + " TEXT not null);";
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(TABLE_CREATE);
}
and I query here
String[] columns = {LOG_TEXT,COLUMN_ID};
Cursor cursor = helper.getReadableDatabase().query(TABLE_NAME, columns, null, null, null, null, COLUMN_ID + " desc");
and I catch this the exception generated containing the sql query.
catch(Exception e){
Log.D("sql Exception",e.getMessage());}
and it returns
no such column: _id: , while compiling: SELECT logtext, _id FROM log ORDER BY _id desc
I'm familar with Oracle SQL and relational databases in general. Is it my ORDER BY clause? I was certain you can ALWAYS use order by. It doesn't have the same behavior as GROUP BY.
Any ideas on why the exception?
Incase anyone wants to see i'm updating with my ArrayAdaptor statements. I'm using the cursor in a listview
String[] data = query();
adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, android.R.id.text1, data);
listView.setAdapter(adapter);}
Rewrite
Whenever you change the schema in TABLE_CREATE you must inform you app of these changes, they will not happen automatically when you change TABLE_CREATE. The easiest way to do this is to increment your database_version in your extended SQLiteOpenHelper class. You discovered you can also uninstall / reinstall the app, for the same results. If you are savvy with SQL you could ALTER the table. But whatever the method you must make sure that you app makes the schema changes before trying to access the new columns...
Also for SQLite:
_id integer primary key
is synonymous with:
_id integer primary key autoincrement not null
And queries use descending as the default order, so ORDER BY _id is the same as ORDER BY _id DESC.
Had the same problem, meaning it should have worked but didn't (had some typos in the create command that I fixed but that still didn't help). A colleague then told me to try clearing the data (just at AppInfo and then "Clear Data") which solved my problem, apparently the old database (that didn't work) was still there and had to be cleared out first.
I just put this answer here in case anybody else like me (android beginner) stumbles across this problem, because I went through dozens of stackoverflow threads with this problem but not one offered this possibility/solution and it bothered me for quite some time.
Did you add the definition of the _id column to your create statement later on, i.e. after the code had already been run once? Databases are persisted files, so if you modify the table structure in your code you need to make sure you clear your application's data so the database file can ge re-created with the correct table/column data.
I was trying to solve the question on why I was getting this error yesterday with some code:
java.lang.IllegalArgumentException: column '_id' does not exist
I had a lot more code, especially that I did not need, so I stripped a lot of it out to make it easier to understand where I am going wrong. But essentially this is my schema:
database.execSQL("CREATE TABLE events (" +
"_id INTEGER PRIMARY KEY, event_name TEXT" +
")");
As one can tell, looks fine right.
Unless I forgot to read, it's most obviously there. But then I figured out where my error was coming from, or at least I am sure this is why. This code that retrieves a cursor:
public Cursor getEventsName() {
return database.rawQuery( "SELECT event_name FROM events", null);
}
According to android, this is the error. When I change it to this:
public Cursor getEventsName() {
return database.rawQuery( "SELECT * FROM events", null);
}
Everything is peachy. When the former, it crashes. Any reason as to why this is. I thought that in rawQuery() I could do that. So long as I am not including where clauses, which I am not. Any help much appreciated.
Let's call these, event cursor:
public Cursor getEventsName() {
return database.rawQuery( "SELECT event_name FROM events", null);
}
... and * cursor:
public Cursor getEventsName() {
return database.rawQuery( "SELECT * FROM events", null);
}
Most of the answers that you have received (even the ones here: In Android, does _id have to be present in any table created?) are guessing at the likely cause for your error. I figured I would answer your question as well:
Any reason as to why (the former crashes and the later is peachy?)
The difference between the * and event cursors is that * is selecting every column implicitly and event is only selecting event_name. In your events table, the * cursor is the equivalent of:
SELECT _id, event_name FROM events;
which is why the this cursor works just peachily. In other words you are not receiving this error:
java.lang.IllegalArgumentException: column '_id' does not exist
because you are implicitly selecting the _id column with *.
Of course the most probable reason for getting this error is when you bind your data with a ListView, Spinner, etc; they all tend to use a CursorAdapter of some form. This is from the CursorAdapter documentation:
Adapter that exposes data from a Cursor to a ListView widget. The Cursor must include a column named "_id" or this class will not work.
So the Solution is simple: you must select the _id column in your query as well as the other columns that you want. (The compiler isn't lying to you.)
That being said, if this still doesn't seem valid to your app or doesn't make sense please post the code where you use the Cursor and the error is thrown.
I suspect that whatever was handling the cursor was trying to get the _ID column but it wasn't specified in your select statement. Doing something like,
public Cursor getEventsName() {
return database.rawQuery( "SELECT _id, event_name FROM events", null);
}
Some Android components, such as the SimpleCursorAdapter require the _ID be available in the select statement since it uses internally when getItemId() is called.
java.lang.IllegalArgumentException: column '_id' does not exist
I had same problem, this exception is thrown because SimpleCursorAdapter need for SELECT column named _id so you can resolve it when for example if you created some table with column KEY_ID as PK so you can try it like this:
SELECT KEY_ID AS _id, column1, column2 FROM SomeTable.
public Cursor getEventsName() {
return database.rawQuery( "SELECT * FROM events", null);
Change it to
public Cursor getEventsName(){
final String[] columns = new String[]{"_id", "event_name "};
return database.query(events, columns, "" , null, null, null, null);
}
I am upgrading my database to add another column. What I am trying to do is (after the column is added in onUpgrade) this method is called from the main activity for each table (3 were upgraded). The method is supposed to replace all of the blanks in the new column with "1".
The code runs fine, stepping through, boolean test is true every time but when I open the table to view the data, the entire column is blank. The weird part is, my rowId numbers are incrementing every time. It starts out with 3 rows with rowIds of 1,2,3 respectively. After my code runs once, they now have rowIds of 4,5,6 respectively.
Can anyone help me out? KEY_ROWID is just my auto rowId number. KEY_MODE is just "mode" for column title. If I run through debugging it, the three rows I have show up in the code (it runs through the while loop 3 times).
public void checkBlanks(String table) {
Cursor cursor = mDb.query(table, new String[] {KEY_ROWID, KEY_MODE}, null, null, null, null, null);
while (cursor.moveToNext()) {
int modeCol = cursor.getColumnIndexOrThrow(KEY_MODE);
if (cursor.isNull(modeCol)) {
int rowId = cursor.getInt(cursor.getColumnIndexOrThrow(KEY_ROWID));
ContentValues args = new ContentValues();
args.put(KEY_MODE, 1); // replace the blank space with a "1"
boolean test = mDb.update(table, args, KEY_ROWID + "=" + rowId, null) > 0;
}
}
cursor.close();
}
Instead of manually looping through the rows, why don't you just leverage the power of SQL and update ALL of the rows in one call? E.g.
mDb.execSQL("UPDATE " + table + " SET " + KEY_MODE + " = 1;");
Since it's so simple, you can do this right in your onUpgrade() method.
You could have done that much easier:
1.) During onUpgrade(): "add column newcolumn default 1". This would add a new column with all newcolumns containing 1.
2.) onUpgrade() is already run: update table set newcolumn=1: Without a WHERE clause the whole table is affected.
There's not need to walk thru all rows.
What you want to do requires an SELECT...FOR UPDATE OF/UPDATE...WHERE CURRENT OF. I didn't do that with SQLite, so I don't know if this is supported.
In your situation (onUpgrade is already run) use 2.)