I am trying to make very simple stuff, but I came with the conclusion that my code looks very ugly and I am pretty sure that there are some ways to improve the code performance and to clearness.
How can I do it only with an SQL statement in Android SQLITE?
I have two tables: A and B.
In A table columns d, p, u. And in table B columns d, p, u. The columns have the same names.
I need to find the last added row in each table where column p is some value. And update columns d and u from table B with values of columns d and u from table A.
final String selection = A.p + SQL_LIKE;
final String[] selectionArgs = new String[]{phone};
final String sortOrder = A.d + " DESC LIMIT 1";
final Cursor cursorA = getContentResolver().query(URI_A,
null, selection, selectionArgs, sortOrder);
if (cursorA != null && cursorA.moveToFirst()) {
final long dateTimeMillis = cursorA.getLong(cursorA.getColumnIndex(A.d));
final String selectionB = B.p + SQL_LIKE;
final String[] selectionArgsB = new String[]{'%' + phone};
final Cursor cursorB = getContentResolver().query(URI_A, null,
selectionB, selectionArgsB, A.d + " DESC LIMIT 1");
if(cursorB != null && cursorB.moveToFirst()){
final EntityB entityB = new EntityB().getUnitFromCursor(cursorB);
final ContentValues contentValues = new ContentValues();
contentValues.put(B.d, dateTimeMillis);
contentValues.put(B.u, durationMillis);
final String where = B._ID + SQL_ADD_VALUE;
final String[] whereArgs = new String[]{entityB.getId()};
getContentResolver.update(URI_B, values, where, whereArgs)
cursorB.close();
}
callLogCursor.close();
}
Question: How can I improve my code so in future if I need something the same I do not need to write so much boilerplate and it looks more clean. I am sure this code can be changed on a single SQL statement - I think this is the best solution for such problem.
UPDATE B
SET d = (SELECT d
FROM A
WHERE p LIKE ...
ORDER BY d DESC
LIMIT 1),
u = (SELECT u
FROM A
WHERE p LIKE ...
ORDER BY d DESC
LIMIT 1)
WHERE ID = (SELECT ID
FROM B
WHERE p LIKE ...
ORDER BY d DESC
LIMIT 1);
If it is possible that A does not have rows for a specific p in B, then you must add another filter to the outer WHERE clause (EXISTS (SELECT * FROM A WHERE ...)).
Related
i'm new to programming with DB and not an expert in android programming so bear with me!
I have a DB with 2 tables (A and B) where I get a list of ID from the table A (1 to 100 rows) and get rows from table B for each id I got from table A giving me a total of 400 to 800 rows from table B.
This approach is not ideal for my app as it take 4 to 10+ seconds to process where I would ideally want less than 1s.
I'm trying to understand what would be best in a case like this.
Would having less row but more content in each help?
the DB is aprox 15mb Would loading it all in the background be better (I guess not as it would mean 5 min+ of loading)?
what is most expensive / have the worst performance: queries, cursor iteration, loading data from a field?
I have no specific index with my DB, would generating some help? If so how can I do that?
I currently have the bellow code collecting my data:
Long start = System.currentTimeMillis();
while (cursorTableA.moveToNext()) {
long id = cursorTableA.getLong(0);
int paragraphNunber = cursorTableA.getInt(1);
boolean isPoetry = (cursorTableA.getInt(2) != 0);
Paragraph mParagraph = new Paragraph(id, paragraphNunber,isPoetry);
// GET WORDS
String selectionWords = DbContract.WordsEntry.CONNECTED_PARAGRAPH_ID+ " = ?";
String[] selectionWordsArgs = new String[]{ Long.toString(paragraphNunber) };
String sortOrder = DbContract.WordsEntry.WORD_NUMBER+ " ASC";
Cursor cursorTableB = db.query(
DbContract.WordsEntry.TABLE_NAME,
DbContract.WordsEntry.DEFAULT_QUERY_COLUMNS_TO_RETURN,
selectionWords, selectionWordsArgs, null, null, sortOrder
);
while (cursorTableB.moveToNext()) {
String word = cursorTableB.getString(0);
String thesaurusRef = cursorTableB.getString(1);
String note = cursorTableB.getString(2);
mParagraph.addWord(new Word(word,thesaurusRef,note));
}
cursorTableB.close();
long finish = System.currentTimeMillis();
timeElapsed = finish- start;
System.out.println("DB Query => timeElapsed(s): "+(timeElapsed/1000)+" timeElapsed(ms): "+timeElapsed);
}
I should add that my DB is only used in read only, I copy it on first execution to the data/data/.../databases folder I never write in it.
I suggest you use a JOIN of your two tables and get just one cursor to iterate instead of get two and nest them. Try something like this:
final String MY_QUERY = "SELECT * FROM table_a a INNER JOIN table_b b ON a.id = b.other_id";
Cursor cursorTable = db.rawQuery(MY_QUERY, null);
while (cursorTable.moveToNext()) {
String id = cursorTable.getString(0);
String paragraphNunber = cursorTable.getString(1);
boolean isPoetry = (cursorTable.getInt(2) != 0);
String word = cursorTable.getString(3);
String thesaurusRef = cursorTable.getString(4);
String note = cursorTable.getString(5);
}
cursorTableB.close();
long finish = System.currentTimeMillis();
timeElapsed = finish- start;
System.out.println("DB Query => timeElapsed(s): "+(timeElapsed/1000)+"
timeElapsed(ms): "+timeElapsed);
Of course you will have to handle the logic of the creation of the object Paragraph as you will now have a cursor with as many rows as in tableB and "paragraph_id".
I'm working on an small android app that maintains a small database of tools which I lend out to other people.
As part of the app, I am incorporating an sqllite database, where I am having a bit of trouble performing queries and working with cursors once the queries have been executed.
The code in question is as follows:
String COLUMN_NAME = "toolName";
String[] columns = { COLUMN_NAME };
String selection = COLUMN_NAME + " =?";
String[] selectionArgs = {tool};
Cursor cursor = mToolDb.query(ToolStatisticContract.ToolStatisticEntry.TABLE_NAME, columns,
selection, selectionArgs, null, null, null, null);
return Integer.parseInt(cursor.getString(3));
The contract for the database is as follows:
public class ToolStatisticContract {
public static final class ToolStatisticEntry implements BaseColumns {
public static final String TABLE_NAME = "tooltable";
public static final String COLUMN_TOOL_NAME = "toolName";
public static final String COLUMN_LIFESPAN = "lifespan";
public static final String COLUMN_USAGE = "usageTime";
}
}
I am essentially trying to extract out the value from COLUMN_USAGE, which seems to be producing errors with regards to parsing the value to an integer. The value in the COLUMN is actually an integer typecasted as a String from a previous segment of code, so I'm fairly certain the error is encompasssed with the code snippets above.
Thanks again in advance for all your help!
The code in question is as follows
The net SQL statement is something like:
SELECT toolName FROM tooltable WHERE toolName = ?
And there is no column with index 3, since you are only returning 1 column.
You need to:
Have usageTime in your column list (COLUMNS)
Move the Cursor to a valid row (as it initially is positioned before the first row)
Pass getInteger() the value that lines up with COLUMNS to retrieve usageTime
You could use the following. This uses null instead of columns, which will get all columns (i.e. resolves to SELECT * FROM table). It checks that a row has been returned and only then does it try to get the data. It also closes the cursor (you should close a cursor when done with it). It uses cursor.getInt() to get the integer value rather than convert it from a string to int. It assumes that you'll only get 1 row (if no rows then 0 will be returned).
int returnvalue = 0;
String COLUMN_NAME = "toolName";
String[] columns = { COLUMN_NAME };
String selection = COLUMN_NAME + " =?";
String[] selectionArgs = {tool};
Cursor cursor = mToolDb.query(ToolStatisticContract.ToolStatisticEntry.TABLE_NAME, null,
selection, selectionArgs, null, null, null, null);
if (cursor.getCount() > 0) {
cursor.moveToFirst();
returnvalue = cursor.getInt(2);
//or returnvalue = Integer.parseInt(cursor.getString(2));
}
cursor.close();
return returnvalue;
Note! I haven't checked this just coded it from memory, so apologies for the odd mistake.
To do the above using specific columns then you could use:-
String COLUMN_NAME = "toolName";
String[] columns = { COLUMN_USAGE };
String selection = COLUMN_NAME + " =?";
String[] selectionArgs = {tool};
Cursor cursor = mToolDb.query(ToolStatisticContract.ToolStatisticEntry.TABLE_NAME, columns,
selection, selectionArgs, null, null, null, null);
In which case the column index would be 0 (that is the index is according to the column's in the cursor). However it might be better to use, the following which gets the column index according to the column's name:-
cursor.getInt(cursor.getColumnIndex(COLUMN_USAGE);
The easiest way to read a single value from the database is to use a helper function that allows you to avoid having to handle cursor objects:
String query = "SELECT usageTime FROM tooltable WHERE toolName = ?";
String[] selectionArgs = { tool };
long returnvalue = DatabaseUtils.longForQuery(mToolDb, query, selectionArgs);
I am developing an application where the user inputs title and the date. I want to prevent the duplicated titles being inputted on the same day in to database. I am checking if the title exists on the selected date. However my query seems not to work and i don't know why, the application just crashes.Is this query correct? Can someone help?
public boolean checkExist(String title, String date) {
SQLiteDatabase db = this.getWritableDatabase();
Cursor c = db.rawQuery("SELECT * FROM "+TABLE_NAME+" WHERE "+TITLE+"=?" +"AND" + DATE+"=?", new String[] {title,date});
boolean exists = c.moveToFirst();
c.close();
return exists;
}
One issue that you have is that c.moveToFirst will always fail if a match does not exist as you are trying to move to a row in an empty cursor.
The resolution is to not use c.moveToFirst and instead get the count of the rows and then set the return value accordingly.
e.g.
public boolean checkExist(String title, String date) {
SQLiteDatabase db = this.getWritableDatabase();
Cursor c = db.rawQuery("SELECT * FROM "+TABLE_NAME+" WHERE "+TITLE+"=?" +"AND" + DATE+"=?", new String[] {title,date});
boolean exists = c.getCount() > 0;
c.close();
return exists;
}
The second issue is that the query itself is wrong as you do not have spaces either side of the AND keyword. That is instead of
Cursor c = db.rawQuery("SELECT * FROM "+TABLE_NAME+" WHERE "+TITLE+"=?" +"AND" + DATE+"=?", new String[] {title,date});
You should have
Cursor c = db.rawQuery("SELECT * FROM "+TABLE_NAME+" WHERE "+TITLE+"=?" +" AND " + DATE+"=?", new String[] {title,date});
Personally, I setup constants for SQL keywords that include the space and then use these. So I'd have something along the lines of +TITLE+"=?" + SQLAND + DATE+"=?". Where SQLAND would be defined along the lines of String SQLAND=" AND ";
PS look at Cricket_007's answer, the code is neater/better it's easier to read.
Your spacing is off. TITLE+"=?" +"AND" + DATE becomes TITLE=?ANDDATE=?
I would suggest this. See DatabaseUtils.queryNumEntries
public boolean checkExist(String title, String date) {
SQLiteDatabase db = getReadableDatabase();
String[] args = new String[] {title,date};
String filter = String.format("%s=? AND %s=?", TITLE, DATE);
return DatabaseUtils.queryNumEntries(db, TABLE_NAME, filter, args) > 0;
}
you should be using c.getCount() instead of c.moveToFirst()
if the value is greater than 0, then it exists
I have tried to get attendance name and emails using belwo code, but still not working.
Cursor cur = contentResolver.query(CalendarContract.Attendees.CONTENT_URI, new String[]{Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_RELATIONSHIP, Attendees.ATTENDEE_EMAIL}, Attendees.EVENT_ID +"= "+Long.parseLong("10"), null, null);
if(cur!=null){
SapGenConstants.showLog("cur size: "+cur.getCount());
while(cur.moveToNext()){
attendee_status = cur.getString(cur.getColumnIndex(Attendees.ATTENDEE_STATUS));
attendee_name = cur.getString(cur.getColumnIndex(Attendees.ATTENDEE_RELATIONSHIP));
attendee_Email = cur.getString(cur.getColumnIndex(Attendees.ATTENDEE_EMAIL));
SapGenConstants.showLog("attendee_status 2: "+attendee_status);
SapGenConstants.showLog("attendee_name 2: "+attendee_name);
SapGenConstants.showLog("attendee_Email 2: "+attendee_Email);
}
cur.close();
}
This is how you do it:
final String[] attendeeProjection = new String[]{
CalendarContract.Attendees._ID,
CalendarContract.Attendees.EVENT_ID,
CalendarContract.Attendees.ATTENDEE_NAME,
CalendarContract.Attendees.ATTENDEE_EMAIL,
CalendarContract.Attendees.ATTENDEE_TYPE,
CalendarContract.Attendees.ATTENDEE_RELATIONSHIP,
CalendarContract.Attendees.ATTENDEE_STATUS
};
final String query = "(" + CalendarContract.Attendees.EVENT_ID + " = ?)";
final String[] args = new String[]{"YOUR_EVENT_ID"};
final Cursor cursor = contentResolver.query(CalendarContract.Attendees.CONTENT_URI, attendeeProjection, query, queryArgs, sortOrder);
cursor.moveToFirst();
while (cursor.moveToNext()) {
// process the cursors
}
Since these queries are IO operations, it is advised to call them on a non-UI thread; my tip would be to go with RxJava.
If you don't want to work with cursors and queries (and a lot of boilerplate code), take a look at the CalendarWrapper library. It takes care of mapping objects and database rows to and from, CRUD operations can be performed with object methods. For example, this is how you'd query all attendees for an event ID:
final String query = "(" + CalendarContract.Attendees.EVENT_ID + " = ?)";
final String[] args = new String[]{"YOUR_EVENT_ID"};
final List<Attendee> attendees = Attendee.getAttendeesForQuery(query, args, null, getContentResolver());
i just ran into the same issue - and it was because of a mixup between the instance id and the event id, two distinct elements.
The important thing is that the attendees table is based on the EVENT id - and not the instance id - so it may explain your problem. Where do you get the hardcoded "10" from ?
If it's an instance id, then extract the EVENT id of the event series it belongs to, and pass that on to the ATTENDEES table. See if that helps.
I have database which contains "date" column and "item" column.
I want that user could update specific row in the database.
I trying to do it with update method in SQLiteDatabase class.
My problem is that i dont know how to make update method find exactly the row i want.
I saw some example that use it with parameters from one word.
like this:
ourDatabase.update(tableName, cvUpdate, rowId + "=" + item , null);
My problem is that i want to update the row that have specific item and date. so the name of the item alone is not enough.
I tried this code below but its didnt work, hope youll can help me.
public void updateEntry(String item, String date) throws SQLException{
String[] columns = new String[]{myItem, myDate};
Cursor c = ourDatabase.query(tableName, columns, null, null, null, null, null);
long position;
ContentValues cvUpdate = new ContentValues();
cvUpdate.put(date, myDate);
cvUpdate.put(item, myExercise);
int itemAll = c.getColumnIndex(myItem);
int dateAll = c.getColumnIndex(myDate);
for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()){
if (c.getString(itemAll).equals(myItem) && c.getString(dateAll).equals(myDate))
{
position = c.getPosition();
break;
}
}
ourDatabase.update(tableName, cvUpdate, rowId + "=" + position , null);
}
First, the columns String[] is supposed to contain column names, such as "_ID", or whatever are the column names you have used. Given that you compare the content of the column myItem with the object myItem, I assume there is a confusion somewhere here.
Secondly, rowId and position are different things in SQL, especially if you delete rows, as the row id usually is autoincrement, and especially since your query is not explicitely sorted. Replacing c.getPosition() by c.getLong(c.getColumnIndex(ID_COLUMN)) would make more sense.
Thirdly, sql is nice because you can query it. For example, rather than get all items and loop to find the matching date and item, you can :
String whereClause = ITEM_COLUMN + " = ? and " + DATE_COLUMN + " = ?";
String[] whereArgs = new String[] { item, date };
Cursor c = ourDatabase.query(tableName, columns, whereClause, whereArgs, null, null, null);
instead of your for loop.
Forthly, you can even make the query in the update :
String whereClause = ITEM_COLUMN + " = ? and " + DATE_COLUMN + " = ?";
String[] whereArgs = new String[] { item, date };
ourDatabase.update(tableName, cvUpdate, whereClause, whereArgs);
Extra tip: use full caps variable names for contants such as column names, it help with readability.