I'm creating an Android project and I have a "DBFunc" class that has multiple methods to handles queries called by the activities.
DBFunc.java
public int getTotalNumberOfQuestions (String table, String category) {
String selectQuery = "SELECT COUNT(*) FROM " + table + " WHERE category='" + category + "'";
// example
// SELECT COUNT(*) FROM questions WHERE category='History';
SQLiteDatabase database = this.getReadableDatabase();
Cursor c = database.rawQuery(selectQuery, null);
int ans = -1; // returns -1 if query unsuccessful
if (c.moveToFirst()) {
ans = c.getInt(0);
}
database.close();
c.close();
return ans;
}
I'm getting an error on the cursor, saying
android.database.sqlite.SQLiteException: no such column: category (code 1): , while compiling: SELECT COUNT(*) FROM questions WHERE category='Physics'
but I do have a category column in my questions table
When running this query through sqlite3 on the command prompt, it works and returns a number (e.g 1)
Here's what the schema looks like in "DB Browser for SQLite"
I really hope there's an easy solution, because I don't understand why it wouldn't work,
Thanks
EDIT 1:
#CL asked for the code that creates the database. The database is created in sqlite3 command line and passed into the program. But the query I used was
CREATE TABLE questions (questionId INTEGER PRIMARY KEY AUTOINCREMENT, question TEXT, option1 TEXT, option2 TEXT, option3 TEXT, option4 TEXT, category TEXT);
EDIT 2:
I did what #Uwe Partzsch sugested and used LIKE instead of ' '
String selectQuery = "SELECT * FROM " + table + " WHERE category LIKE '" + category + "'";
But now I'm getting a different error
no such table: questions
EDIT 3:
public class DBFunc extends SQLiteOpenHelper {
public static String DB_PATH ="/data/data/com.example.healyj36.quizapp/databases/";
public static String DB_NAME = "questions.db";
public static final int DB_VERSION = 1;
public static final String TB_NAME1 = "questions";
public static final String TB_NAME2 = "answers";
private SQLiteDatabase myDB;
private Context context;
public DBFunc(Context context) {
super(context, DB_NAME, null, DB_VERSION);
this.context = context;
}
//Copy database from source code assets to device
public void copyDataBase() throws IOException {
try {
InputStream myInput = context.getAssets().open(DB_NAME);
String outputFileName = DB_PATH + DB_NAME;
OutputStream myOutput = new FileOutputStream(outputFileName);
byte[] buffer = new byte[1024];
int length;
while ((length = myInput.read(buffer)) > 0) {
myOutput.write(buffer, 0, length);
}
myOutput.flush();
myOutput.close();
myInput.close();
} catch (Exception e) {
Log.e("tle99 - copyDatabase", e.getMessage());
}
}
public void createDatabase() throws IOException {
this.getReadableDatabase();
try {
copyDataBase();
} catch (IOException e) {
Log.e("tle99 - create", e.getMessage());
}
}
...
This is can exists 2 possiblities:
This problem can occurred from broken SQLite schema.
Your question is not about SQL Statement problem. but many developer can think SQL Statement problem about your question.
This case can check no demaged data in your database file. Although you can not use select fields and sql where clause by field name.
As a result, you can not use database file in your android code.
Exactly solution, I recommend recreate SQLite DB file, step by step.
You must be backup before use SQLite modification tool. (SQLite Manager, Browser, others db manage tools)
This problem occurred from your persistent data.
If you use the same file name for assets or raw data when run with modified data,
you can try uninstall previous installed app for refresh.
Probably you have added the category column latter and trying to reinstall after modifications.
Either do a clear data from the Settings>app>your_app and launc
or
Uninstall the app and then install again.
Delete your app data, also you can try LIKE instead of '='.
String selectQuery = "SELECT * FROM " + table + " WHERE category LIKE '" + category + "'";
Related
At this time, I have some case in developing android application, right now I have some class called DBHelper, this class is use to execute operation like create table and operation like create trigerrs and so on.. In this case I tried to execute sql statement for inserting data in my table, but the sql operation is not working, can anybody help me? I really need help here.
Here's my class..
public class DBHelper extends SQLiteOpenHelper
{
private Context mContext;
private static final String db_name ="schoolmap.db";
private static final int db_version=1;
//Constructor
public DBHelper(Context context) {
super(context, db_name, null, db_version);
this.mContext = context;
}
private static final String db_TABLE_foto = "create table "
+ "foto" + "("
+ id + " integer primary key autoincrement, "
+ caption_foto + " varchar(20), "
+ image + " BLOB);";
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(db_TABLE_foto);
//in this line i tried to add picture from the drawable folder
Bitmap b = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.a1);
int bytes = b.getByteCount();
ByteBuffer buffer = ByteBuffer.allocate(bytes); //Create a new buffer
b.copyPixelsToBuffer(buffer);
byte[] array = buffer.array();
//the question is right here, when I try this code, it's not working, means: the record is not
//inserted
ContentValues values = new ContentValues();
values.put(caption_foto, "Test");
values.put(image, array);
// Inserting Row
database.insert(foto, null, values);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(DBHelper.class.getName(),"Upgrading database from version " + oldVersion + "to"
+ newVersion + ",which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + db_TABLE_foto);
onCreate(db);
}
so I mean, when I running this apps first time, I tried to add picture in my application, but the sql operation is not working, can somebody help about this issue? Any Help is Needed
In common case it's not good idea to put images into database. You can fast and easy access to image from resources without using db.
Look answer here
The best way is to save image to file system and save uri of it to string field in database. If you need to link record in database to image in resources, you can save resource id (like R.drawable.a1) in int field.
Try This code:-
private ByteArrayOutputStream a_thumbnail = new ByteArrayOutputStream();
Bitmap b = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.a1);
b.compress(Bitmap.CompressFormat.PNG, 80, a_thumbnail);
ContentValues values = new ContentValues();
values.put("Test",caption_foto,);
values.put("Your image column name", a_thumbnail.toByteArray());
database.insert("foto", null, values);
My code is now as follows:
Main app java:
String TAG = "DATABASES";
try {
String destPath = "/data/data/" + getPackageName()
+ "/databases/data.db";
File f = new File(destPath);
if(!f.exists()){
Log.v(TAG,"Dest DB doesn't exist");
InputStream in = getAssets().open("airports.db");
OutputStream out = new FileOutputStream(destPath);
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
in.close();
out.close();
} else {
Log.v(TAG,"File exists: " + destPath);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
Log.v(TAG,"ioexeption");
e.printStackTrace();
}
DBManager dbManager = new DBManager(this);
Log.v(TAG,"Database is there with version: "+dbManager.getReadableDatabase().getVersion());
//String sql = "select * from airports where IATA='GRO' ";
String sql = "select count(*) from airports";
SQLiteDatabase db = dbManager.getReadableDatabase();
Cursor cursor = db.rawQuery(sql, null);
Log.v(TAG,"Query Result:"+cursor);
cursor.close();
db.close();
dbManager.close();
My DBManager.java:
package com.jammo.mywidget4;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class DBManager extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 1;
private static final String TAG = "DATABASES";
public DBManager(Context context) {
super(context, "data.db", null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
Log.v(TAG,"On create Called:"+db.getPath());
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
Now the execution runs ok in the main.java on initiation of "db", however fails on the next line where it tries to execute rawQuery()
FYI "select count(*) from airports" run on my Sqlite DB Manager GUI returns 1650 rows.
Error log is saying :
03-04 21:54:24.200: E/AndroidRuntime(11513): FATAL EXCEPTION: main
03-04 21:54:24.200: E/AndroidRuntime(11513): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.jammo.mywidget4/com.jammo.mywidget4.SectorInfo}: android.database.sqlite.SQLiteException: no such table: airports: , while compiling: select count(*) from airports
Local /assets/airports.db "seems" to be being detected and copied to /data/data/mypackage/databases/data.db as "file is found"
Many thanks
J
Try it without using your DBManager class.
Comment out this line...
DBManager dbManager = new DBManager(this);
...then open the database explicitly by replacing this line...
SQLiteDatabase db = dbManager.getReadableDatabase();
...with...
SQLiteDatabase db = SQLiteDatabase.openDatabase(destPath, null, SQLiteDatabase.OPEN_READONLY);
Then clear the app data so the database is re-copied from your assets directory.
EDIT: Even though my suggestion worked, it's actually just a workaround and the root of your problem was this...
String destPath = "/data/data/" + getPackageName() + "/databases/data.db";
You should avoid using hard-coded paths with Android - they may work fine for many devices but there is no guarantee they'll work with all devices or even with all versions of Android.
The problem was you were creating / copying your database to that path but the DBManager class was presumably creating an empty database located somewhere on a different path in the file-system.
To fix this for all devices and Android versions, before copying the database, I would use...
SQLiteDatabase.openOrCreateDatabase("data.db", null)
...to create a 'dummy' (empty) database. Then I'd call...
getDatabasePath("data.db")
...to get the absolute path to data.db. You can then overwrite that database during the copy operation and from then on you can use a class which extends SQLiteOpenHelper and it will find the correct database.
After a lot of work I managed to build my first app, but I stick with one question. For my app I am using a sql database.. Suppose I want to add 30 records to a certain table. How is it possible that when I put a new version in the android market with a new sql table to use this one for the future, but to keep the records of the previous database?
Does it has to do something with:
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
EDIT
my databasehelper code:
public class DataBaseHelper extends SQLiteOpenHelper {
private static String DB_PATH = "/data/data/com.test.com/databases/";
private static String DB_NAME = "quizDb";
private SQLiteDatabase myDataBase;
private final Context myContext;
private Cursor c;
static int numberOfLevels = 10;
private final static int DB_VERSION = 2; // = until level 10
/**
* Constructor Takes and keeps a reference of the passed context in order to
* access to the application assets and resources.
*
* #param context
*/
public DataBaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
this.myContext = context;
}
/**
* Creates a empty database on the system and rewrites it with your own
* database.
* */
public void createDataBase() throws IOException {
boolean dbExist = checkDataBase();
if (!dbExist) {
// By calling this method and empty database will be created into
// the default system path
// of your application so we are gonna be able to overwrite that
// database with our database.
this.getReadableDatabase();
try {
copyDataBase();
} catch (IOException e) {
throw new Error("Error copying database");
}
}
}
/**
* Check if the database already exist to avoid re-copying the file each
* time you open the application.
*
* #return true if it exists, false if it doesn't
*/
private boolean checkDataBase() {
File dbFile = new File(DB_PATH + DB_NAME);
return dbFile.exists();
}
/**
* Copies your database from your local assets-folder to the just created
* empty database in the system folder, from where it can be accessed and
* handled. This is done by transfering bytestream.
* */
private void copyDataBase() throws IOException {
// Open your local db as the input stream
InputStream myInput = myContext.getAssets().open(DB_NAME);
// Path to the just created empty db
String outFileName = DB_PATH + DB_NAME;
// Open the empty db as the output stream
OutputStream myOutput = new FileOutputStream(outFileName);
// transfer bytes from the inputfile to the outputfile
byte[] buffer = new byte[1024];
int length;
while ((length = myInput.read(buffer)) > 0) {
myOutput.write(buffer, 0, length);
}
// Close the streams
myOutput.flush();
myOutput.close();
myInput.close();
}
public void openDataBase() throws SQLException {
// Open the database
String myPath = DB_PATH + DB_NAME;
myDataBase = SQLiteDatabase.openDatabase(myPath, null,
SQLiteDatabase.OPEN_READONLY);
}
#Override
public synchronized void close() {
if (c != null)
c.close();
if (myDataBase != null)
myDataBase.close();
super.close();
}
#Override
public void onCreate(SQLiteDatabase db) {
}
public File getDatabasePath(String name) {
File file = myContext.getDatabasePath(name);
return file;
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("ATTACH DATABASE ? as AttachedDB",
new String[] { getDatabasePath("quizDbNew").getPath() });
db.execSQL("INSERT OR IGNORE INTO questions (_id, file, answer, level) SELECT _id, file, answer, level FROM AttachedDB.questions");
db.execSQL("DETACH AttachedDB");
}
The concept of using "DROP TABLE" in onUpgrade() is as primitive as database management gets, however more useful techniques require more SQL savvy. A smarter way to upgrade your databases by using "ALTER TABLE" to add new columns or otherwise finagle the old data into your new schema.
Addition
Below in the comments you stated (more or less):
I want to copy the content from my backup file of Db v1 into my current Db v2
So let's set up a couple hypothetical tables:
Database Version One (DBv1):
CREATE TABLE Foo(_id INTEGER PRIMARY KEY, bar TEXT, bar2 TEXT, bar3 TEXT);
Database Version Two (DBv2):
CREATE TABLE Foo(_id INTEGER PRIMARY KEY, bar2 TEXT, bar4 INTEGER);
First let's see a regular upgrade from DBv1 to DBv2. SQLite only supports ADD COLUMN and RENAME TO, not REMOVE COLUMN or anythings else. So we have to re-create the entire table:
#Override // DBv1 => DBv2
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("ALTER TABLE Foo RENAME TO OldFoo");
db.execSQL("CREATE TABLE Foo(_id INTEGER PRIMARY KEY, bar2 TEXT, bar4 INTEGER)");
db.execSQL("INSERT INTO Foo (_id, bar) SELECT _id, bar2 FROM OldFoo");
db.execSQL("DROP TABLE OldFoo");
}
Again this created a table with DBv2's schema and kept all of the valid, existing data from DBv1 by inserting the appropriate columns into DBv2. (Then it removed the old data by dropping the old table.)
You have wisely chosen to backup your database over time in a separate file, but now you want to bring the old data into the new table schema. To start make sure that your backup SQLite file is in the same directory as your current SQLite file (data/data/<reverse.package.name>/databases/). It will obviously need a unique name, let's call it DBBackup. Now let's attach DBBackup to your current database and perform a similar action from above:
// DBBackupv1 => DBv2
public void restore(SQLiteDatabase db) {
db.execSQL("ATTACH DATABASE ? as AttachedDB", new String[] {getDatabasePath("DBBackup").getPath()});
db.execSQL("INSERT OR IGNORE INTO Foo (_id, bar2) SELECT _id, bar2 FROM AttachedDB.Foo");
db.execSQL("DETACH AttachedDB");
}
I used INSERT OR IGNORE to restore any rows that were deleted but left the current existing rows untouched. You can use INSERT OR REPLACE to revert to the backed up version. There are many more options to suit your needs.
I have built a database helper class with an open() method and extended sqlite helper with onCreate() overridden. (shown below). Despite all of this, I am getting 'SQLiteException, no such table' error. I do not understand, why is the openHelper not helping?
public void open() {
try{
db = openHelper.getWritableDatabase();
} catch (SQLiteException e) {
db = openHelper.getReadableDatabase();
}
}
//other stuff
public static final String database_create = "create table " + database_table + " (" + primary_key + " integer primary key autoincrement, "
+ company_column + " text not null, " + product_column + " text not null);";
#Override
public void onCreate(SQLiteDatabase _db) {
_db.execSQL(database_create);
}
the following code is meant to insert an entry temporarily, because the database cannot be empty for other reasons. It seems to execute perfectly, yet the last bit of code, which comes after is what throws the error
CompanyAndProductDatabaseAdapter cpdAdapter = new CompanyAndProductDatabaseAdapter(this);
cpdAdapter.open();
errorguard = cpdAdapter.insertPair("Loading", "...");
cpdAdapter.close();
//other stuff
cpdAdapter.open();
Cursor cursor = cpdAdapter.getAllPairsCursor(); //error here
cursor.requery();
startManagingCursor(cursor);
I don't know why you implemented a open-method, also the database_create is not what it should be.
I assume the first code is part of CompanyAndProductDatabaseAdapter.
Take a look here:
Android - Sqlite database method undefined fot type
That's almost all you need to create/get a DB with inherted SQLiteOpenHelper.
Your problem is this function:
db = openHelper.getWritableDatabase();
db = openHelper.getReadableDatabase();
First: check your path/name of the database is correct. It can create a default database, an empty database ( no tables, no nothing) if the database is not found.
Second: try to open your database this way:
String myPath = DB_PATH + DB_NAME;
myDataBase = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READWRITE); // or OPEN_READONLY, depending on situation.
I am doing my first app with a database and I am having a little trouble understanding the onUpgrade function. My database has a table with an items and a favorite column so that the user can favorite an item. Most implementations I see simply drop the table and reconstruct it but I don't want to do this. I want to be able to add more items to the table.
When the app is upgraded through the android marketplace does the database know its version number? So could I increment the version number in the code and then export it to the marketplace and when the user boots up the upgraded version for the first time then onUpgrade will be called?
If this is the case my onUpgrade would simply pull from a file and add the database items in. Is this a standard way of doing things or is there a better way of handling this in Android. I am trying to stay as standard as possible.
Thanks
Ok, before you run into bigger problems you should know that SQLite is limited on the ALTER TABLE command, it allows add and rename only no remove/drop which is done with recreation of the table.
You should always have the new table creation query at hand, and use that for upgrade and transfer any existing data. Note: that the onUpgrade methods runs one for your sqlite helper object and you need to handle all the tables in it.
So what is recommended onUpgrade:
beginTransaction
run a table creation with if not exists (we are doing an upgrade, so the table might not exists yet, it will fail alter and drop)
put in a list the existing columns List<String> columns = DBUtils.GetColumns(db, TableName);
backup table (ALTER table " + TableName + " RENAME TO 'temp_" + TableName)
create new table (the newest table creation schema)
get the intersection with the new columns, this time columns taken from the upgraded table (columns.retainAll(DBUtils.GetColumns(db, TableName));)
restore data (String cols = StringUtils.join(columns, ",");
db.execSQL(String.format(
"INSERT INTO %s (%s) SELECT %s from temp_%s",
TableName, cols, cols, TableName));
)
remove backup table (DROP table 'temp_" + TableName)
setTransactionSuccessful
(This doesn't handle table downgrade, if you rename a column, you don't get the existing data transfered as the column names do not match).
.
public static List<String> GetColumns(SQLiteDatabase db, String tableName) {
List<String> ar = null;
Cursor c = null;
try {
c = db.rawQuery("select * from " + tableName + " limit 1", null);
if (c != null) {
ar = new ArrayList<String>(Arrays.asList(c.getColumnNames()));
}
} catch (Exception e) {
Log.v(tableName, e.getMessage(), e);
e.printStackTrace();
} finally {
if (c != null)
c.close();
}
return ar;
}
public static String join(List<String> list, String delim) {
StringBuilder buf = new StringBuilder();
int num = list.size();
for (int i = 0; i < num; i++) {
if (i != 0)
buf.append(delim);
buf.append((String) list.get(i));
}
return buf.toString();
}
Next to Pentium10's excellent answer, here are some good examples from living code:
Android AOSP: com.android.providers.calendar.CalendarDatabaseHelper.java
Android AOSP: com.android.browser.BrowserProvider.java
OpenIntents Notepad: org.openintents.notepad.NotePadProvider.java
Thank you for clarifying that onUpgrade() will not support Remove/Drop statements #Pentium 10
For those of you who would like to know the exact moment when onUpgrade() gets called, it is during a call to either getReadableDatabase() or getWriteableDatabase().
To those who are not clear how it ensure it gets triggered...the answer is: It is triggered when the database version provided to the constructor of SqLiteOpenHelper is updated. Here is a example
public class dbSchemaHelper extends SQLiteOpenHelper {
private String sql;
private final String D_TAG = "FundExpense";
//update this to get onUpgrade() method of sqliteopenhelper class called
static final int DB_VERSION = 2;
static final String DB_NAME = "fundExpenseManager";
public dbSchemaHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
// TODO Auto-generated constructor stub
}
now to...onUpgrade()
#Override
public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
sql = "ALTER TABLE " + fundExpenseSchema.Expense.TABLE_NAME + " ADD COLUMN " + fundExpenseSchema.Expense.FUNDID + " INTEGER";
arg0.execSQL(sql);
}
I've been using the solution proposed by #Pentium10 for a long time but today i had a problem, after doing alter table, getColumns from the original table still returns the same columns (in the new version of the db the table suffer mayor structure changes, some columns added some others), really i don't know why select statement does not reflect the structure changes, more over before creating my table again, select statement still returns the columns! When the table is not re-created yet!
So i manage solving this issue updating getColumns method using pragma table_info, like this:
/**
* Get a list of column base_dictionary for the selected table
*
* #param db
* Database that contains the table
* #param tableName
* Table name to be used
* #return A List of column name
*/
public static List<String> getColumns(SQLiteDatabase db, String tableName) {
List<String> ar = null;
Cursor c = null;
try {
c = db.rawQuery("pragma table_info(" + tableName + ")", null);
ar = new ArrayList<String>();
if (c != null && c.moveToFirst()) {
do {
ar.add(c.getString(c.getColumnIndexOrThrow("name")));
} while (c.moveToNext());
c.close();
}
} catch (Exception e) {
Log.v(tableName, e.getMessage(), e);
e.printStackTrace();
} finally {
if (c != null) c.close();
}
return ar;
}