Android SQLite Join Query - android

I'm creating an app as a learning tool and am having difficulty with join queries.
I have a database with two tables- horses and covers- declared as follows;
private static final String HORSES_CREATE = "create table horses (_id integer primary key autoincrement, "
+ "name text not null, type integer not null, birthDate text not null, vaccineDate text not null, "
+ "inFoal integer not null, notes text not null);";
The 'type' field refers to stallion, mare, gelding etc and is selected from a spinner (populated from an XML String array).
private static final String COVERS_CREATE = "create table covers (_id integer primary key autoincrement, "
+ "stallionName integer not null, mareName integer not null, firstCoverDate text not null, lastCoverDate text not null, "
+ "scan14Date text not null, scan28Date text not null, foalingDate text not null, inFoal integer not null, notes text not null);";
stallionName is actually stored as the _id field of the horse from the horse table. It is selected from a spinner that only displays horses whose type defined as 'Stallion' in the horses table. (The same applies for Mare).
I have a class 'DatabaseHelper' to create and upgrade the tables, and each table has its own adapter class 'horsesDbAdapter' and 'coversDbAdapter' that contains the methods to add, edit and delete entries, and relevant queries. (fetchAllHorses(), fetchHorse(long rowId) )
eg:
public Cursor fetchAllHorses() {
return mDb.query(DATABASE_TABLE,
new String[] { KEY_ROWID, KEY_NAME, KEY_TYPE, KEY_BIRTHDATE,
KEY_VACCINEDATE, KEY_INFOAL, KEY_NOTES }, null, null,
null, null, null);
}
(It's all adapted from the Android notepad example)
I have the contents of the covers table displayed in a listview (just showing the stallionName and mareName). But as those fields just contain the unique reference to the horses table all that is displayed is the fairly uninformative _id field.
My question is; how can I get the relevant name for the horses to display in the listView? I've read up on join queries etc but get lost when I try implement them. I assume I have to join on horses._id and covers.stallionName (then make an almost-identical one for MareName) but I can't find a concrete example of how to do this.
Please let me know if any additional information/ code is needed.
Any help would be greatly appreciated, thankyou in advance.
EDIT:
I have made stallionName and mareName foreign keys referencing (_id) in the horses table, but am still unsure how and where to implement the join query; should it be in the horsesDbAdapter, coversDbAdapter or the coversList class? (coversList is the class that creates and populates the listView)
The covers table declaration now reads;
private static final String COVERS_CREATE = "create table covers (_id integer primary key autoincrement, "
+ "stallionName integer not null, mareName integer not null, firstCoverDate text not null, lastCoverDate text not null, "
+ "scan14Date text not null, scan28Date text not null, foalingDate text not null, inFoal integer not null, notes text not null," +
"FOREIGN KEY (stallionName) REFERENCES horses (_id), FOREIGN KEY (mareName) REFERENCES horses (_id));";

I'm a newbie to Android and SQLLiteHelper and was wondering about the same thing.
After reading this post, I found that the method to define joins between tables is done with the SQLLiteQueryBuilder.setTables method. See the reference here
I got this from this blog
Hope this helps pointing readers in the right direction.

I've managed to get it working. For anyone else who may have a similar problem I'll try detail what I did.
As #deceiver stated I should have made stallionName and mareName foreign keys referencing horses (see Edit).
In the coversList class (the class that implements the listView) I just needed to get an instance of the database and use a rawQuery to implement the SQL code directly (It may be possible to do it with Query but I'm not sure how)
The added code is as follows;
private Cursor coversCursor;
private void fillData() {
db = (new DatabaseHelper(this).getReadableDatabase());
coversCursor = db.rawQuery("SELECT horses1.name AS stallionNameText, covers.*, horses2.name AS mareNameText FROM horses horses1 JOIN covers ON horses1._id = covers.stallionName JOIN horses horses2 ON horses2._id = covers.MareName",
null);
startManagingCursor(coversCursor);
// Create an array to specify the fields we want to display in the list
// (the renamed fields from the above query)
String[] from = new String[] { "stallionNameText", "mareNameText" };
// and an array of the fields we want to bind those fields to (in this
// case just text1)
int[] to = new int[] { R.id.rowStallionName, R.id.rowMareName };
// Now create a simple cursor adapter and set it to display
SimpleCursorAdapter covers = new SimpleCursorAdapter(this,
R.layout.covers_list_row, coversCursor, from, to);
setListAdapter(covers);
}
FillData() is then called in the onCreate() method. Then just close the db in the onDestroy method.
(Selecting all columns from the covers table seems wasteful here but I will eventually show these columns in the listView aswell. I just wanted to answer this before continuing coding).
A tutorial I found helpful was http://coenraets.org/blog/android-samples/androidtutorial/
I had issues with the SQL query as there are 2 foreign keys referencing the same table and I wanted the listView to display both the stallion name and mare name, so had to join two horses table to a covers table. I just needed to rename the tables in the FROM section of the SQL query. Hopefully the above code is clear. If not, I found the following useful; http://www.bryantwebconsulting.com/blog/index.cfm/2005/3/11/join_a_table_to_itself_in_sql
Sorry if this explanation is too specific to my (unusual) example.
Thanks for reading.

Related

Delete specific record in sqlite table based on two criteria: _id and column

I have created a sqlite table for my android app, this table has 5 columns and multiple rows, the columns being: _id, column1, column2, column3, column4.
I want to delete a specific record, for instance the record stored in column3 corresponding to _id (in a different class are the getters and setters, for this I've named the class "TableHandler")
I guess that I'm a bit confused, following is what I was planning, but for column3 I'm not sure what should be the argument, I just want to delete whatever is in that column position corresponding to _id
public void deleteValueColumn3(TableHandler value){
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_NAME, KEY_ID + " = ? AND " + KEY_COLUMN3 + " = ?",
new String[] {String.valueOf(value.getID()), ?????????);
db.close();
}
The ???????? is that I'm stuck there, maybe the whole method needs to be rewritten, I would appreciate your input.
Thanks
If you want to delete the whole record, just use the _id of the record in delete method, because that is the primary key for your table and therefore is unique. If you'd rather keep the record, you con always use the SQLiteDatabase.update method, specifying null as the new value that will replace column3 value; check out that column3 declaration has no NOT NULL tag, otherwise that could easily throw exception at you.
SQLite does not allow you to delete columns for a specific row.
You can only delete ROWS of data (delete the row that has the column _ID = 1).
Here's a quick tutorial on SQL.
How about updating that column with a null value, rather than using delete()?
ContentValues cv = new ContentValues();
cv.putNull(KEY_COLUMN3);
db.getWritableDatabase().update(
TABLE_NAME,
cv,
KEY_ID + "=?",
new String[]{String.valueOf(keyIdValue)});

get dat from cursor to arraylist

I have a table with 10 columns.
String createQuery = " CREATE TABLE IF NOT EXISTS profile (
_id integer primary key autoincrement,
name text,
longi real,
lati real,
vibration integer,
sound integer,
brightness integer,
mdata,
bluetooth,
wifi);";
How can I get all table data in an ArrayList?
You need to do a few things. You need to create a sub-class of SQLiteDatabase. Once you have that, you can get run a query inside a method in this class like this:
Cursor cursor = getReadableDatabase().query("profile", // table name
new String[] { // columns
"_id",
"name",
"longi",
"lati",
"vibration",
"sound",
"brightness",
"mdata",
"bluetooth",
"wifi"
},
null, null, null, null, null);
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
Long id = cursor.getLong("_id");
String name = cursor.getString("name");
// and so on, list of your columns you want to get.
cursor.moveToNext();
}
Obviously you won't be able to put one whole row into an array list because you have different column types. But you can process one row at a time inside the while loop after you get all the values.
Most of this info is taken from here, which is a great source of info:
http://www.vogella.com/articles/AndroidSQLite/article.html

Android SqLite no such column _id exception

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.

NOT NULL columns in SQLite and error catching

I have a database that is being filled by user defined EditTexts. None of the edit texts should allow empty fields. I know that I can check for this with a couple simple if-statements: if myEditText.getText().toString().equals("") // display error. However I would perfer to use this opportunity to brush up on my SQLite and error catching (as demonstrated in my add method). How would I go about altering the columns in the table below to NOT NULL and generating/catching an error when a user attempts to add/update with empty fields?
My database table:
db.execSQL("CREATE TABLE inventory (category TEXT, itemNum TEXT, quantity INTEGER, price REAL, image INTEGER, UNIQUE(category, itemNum) ON CONFLICT FAIL);");
My add method:
... fill ContentValues values
try{
db.getWritableDatabase().insertWithOnConflict(DatabaseHelper.TABLE_NAME, DatabaseHelper.CATEGORY, values, SQLiteDatabase.CONFLICT_FAIL);
fillItemNumbers();
}
catch(SQLiteConstraintException e)
{
Toast
.makeText(MyActivity.this, etItemNum.getText().toString() + " already exists in " + catSpinner.getSelectedItem().toString() +". Consider using Update.",Toast.LENGTH_LONG)
.show();
}
My update method:
... fill ContentValues values
String[] args = {catSpinner.getSelectedItem().toString(), etItemNum.getText().toString()};
int rowsAffected = db.getWritableDatabase().update(DatabaseHelper.TABLE_NAME, values, DatabaseHelper.CATEGORY + "=? AND " + DatabaseHelper.ITEM_NUMBER + "=?" , args);
UPDATE:
I did a little digging and came up with this:
db.execSQL("CREATE TABLE inventory (category TEXT NOT NULL, itemNum TEXT NOT NULL, quantity INTEGER NOT NULL, price REAL NOT NULL, image INTEGER NOT NULL, UNIQUE(category, itemNum) ON CONFLICT FAIL);");
Is this what I am looking for? If so, how can I use this to my advantage (see above)?
I am not sure if you can actually Alter Column Definition for table. I know you can Alter Table itself, like adding new Column to Table. You might need little trick to modify your database if there is lot of data in it that you want to preserve.
One way to it to create new table and try copying data to new table and afterwards remove old table and rename new Table. It's not most efficient way to do it but it'll get the job done though.
http://www.sqlite.org/lang_altertable.html
EDIT
Here you go
CREATE TABLE inventory (category TEXT not null, itemNum TEXT not null, quantity INTEGER not null, price REAL not null, image INTEGER not null, UNIQUE(category, itemNum) ON CONFLICT FAIL);
EDIT 2
Try this
CREATE TABLE inventory (category TEXT not null ON CONFLICT FAIL, itemNum TEXT not null ON CONFLICT FAIL, quantity INTEGER not null ON CONFLICT FAIL, price REAL not null ON CONFLICT FAIL, image INTEGER not null ON CONFLICT FAIL, UNIQUE(category, itemNum) ON CONFLICT FAIL);
All you need to do is set the columns to NOT NULL.
Then use
insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm)
and
updateWithOnConflict(String table, ContentValues values, String whereClause, String[] whereArgs, int conflictAlgorithm)`
There are several constants you can use for the conflictAlgorithm, depending on exactly what you want to happen. If you want to simply not enter the data into the table, CONFLICT_IGNORE will do the trick. If you want a return code letting you know so you can act on it (let the user know) then you might want CONFLICT_FAIL.
See this for further information.
Hope this helps.

Android SQLite Repeated Elements

I have an issue with SQLite on android. Right now, I'm pulling a JSON object from a server, parsing it, and putting each sub-object in a Table with things such as the Name, Row_ID, unique ID, etc. using this code:
public void fillTable(Object[] detailedList){
for(int i=0;i<detailedList.length;++i){
Log.w("MyApp", "Creating Entry: " + Integer.toString(i));
String[] article = (String[]) detailedList[i];
createEntry(article[0], article[1], article[2], article[3], article[4], article[5]);
}
}
createEntry does what it sounds like. It takes 6 strings, and uses cv.put to make an entry. No problems.
When I try to order them however, via:
public String[] getAllTitles(int m){
Log.w("MyApp", "getTitle1");
String[] columns = new String[]{KEY_ROWID, KEY_URLID, KEY_URL, KEY_TITLE, KEY_TIME, KEY_TAGS, KEY_STATE};
Log.w("MyApp", "getTitle2");
Cursor c = ourDatabase.query(DATABASE_TABLENAME, columns, null, null, null, null, KEY_TIME);
Log.w("MyApp", "getTitle3");
String title[] = new String[m];
Log.w("MyApp", "getTitle4");
int i = 0;
int rowTitle = c.getColumnIndex(KEY_TITLE);
Log.w("MyApp", "getTitle5");
for(c.moveToFirst();i<m;c.moveToNext()){
title[i++] = c.getString(rowTitle);
Log.w("MyApp", "getTitle " + Integer.toString(i));
}
return title;
}
Each entry actually has many duplicates. I'm assuming as many duplicates as times I have synced. Is there any way to manually call the onUpgrade method, which drops the table and creates a new one, or a better way to clear out duplicates?
Secondary question, is there any way to order by reverse? I'm ordering by time now, and the oldest added entries are first (smallest number). Is there a reverse to that?
If you don't want duplicates in one column then create that column with the UNIQUE keyword. Your database will then check that you don't insert duplicates and you can even specify what should happen in that case. I guess this would be good for you:
CREATE TABLE mytable (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
theone TEXT UNIQUE ON CONFLICT REPLACE
)
If you insert something into that table that already exists it will delete the row that already has that item and inserts your new row then. That also means that the replaced row gets a new _id (because _id is set to automatically grow - you must not insert that id yourself or it will not work)
Your second question: you can specify the direction of the order of if you append ASC (ascending) or DESC (descending). You want DESC probably.
Cursor c = ourDatabase.query(DATABASE_TABLENAME, columns, null, null, null, null, KEY_TIME + " DESC");

Categories

Resources