Content loader reverse cursor order - android

I have a simple chat application. I am using content loaders to display messages inside a channel. Since I want 100 recent messages I sort messages based on the time stamp desc and limit it to 100. but I want to display recent messages at the bottom of the screen. Right now the cursor adaptor displays recent message first. How do I reverse the order of the cursor ?
According to me, I will have to write some smart query inside content provider or somehow have to manipulate the cursor inside cursor adaptor's bindView.

Try to sort your messages based on timestamp ASC instead of DESC.
edit: Sorry, didn't grasp the subtility here.
You can try something like this to get data in reverse order:
for (cursor.moveToLast(); !cursor.isBeforeFirst(); cursor.moveToPrevious())
{
// do your magic
}

I would probably use subquery
SELECT * FROM table ORDER BY timestamp WHERE _id
IN (SELECT _id FROM table ORDER BY timestamp DESC LIMIT 100)
To use that with content provider:
String sel = BaseColumns._ID + " IN (SELECT " + BaseColumns._ID +
" FROM " + MyDb.TableName._TABLE_NAME + " ORDER BY " +
MyDb.TableName.TIMESTAMP + " DESC LIMIT 100)";
Cursor q = getContentResolver().query(MyDb.TableName.CONTENT_URI,
null, sel, null, MyDb.TableName.TIMESTAMP);
MyDb is yours class defining tables and its fields, UriS etc.

Related

SQLite Statement in Android Studio 3.5.3

I'm a newbie with Android Studio so please be patient... This forum often leads me with suggestions and examples (as a reader), but today I decided to ask for help:
Since hours, I try to build an SQLite statement in Android Studio: There is a column COLUMN_LAST_ATTEMPT with date and time as String, e.g. 2020-01-09 17:23, see screenshot, and I want to get the newest date (without time) from the table, e.g. 2020-09-01. I tried various options but I can't get it to run.
What I need is an Android SQLite Statement for
SELECT MAX(SUBSTR(last_attempt,11,20)) FROM quiz_questions
(which runs on DBBrowser), where 'last attempt' is a column of table 'quiz_questions', screenshot of that column in table 'quiz_questions'
I tried the following rawQueries, none of them works:
In QuizDBHelper-Class
//...
final QuizDbHelper dbHelper = QuizDbHelper.getInstance(this);
//...
public String newestQuiz(){
db = getReadableDatabase();
String result = null;
Cursor cursor = db.rawQuery("SELECT MAX(" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + ") FROM "
+ QuizContract.QuestionsTable.TABLE_NAME, null);
//Cursor cursor = db.rawQuery("SELECT MAX(SUBSTR(" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT +
// ",11,20)) FROM " + QuizContract.QuestionsTable.TABLE_NAME, null);
//Cursor cursor = db.rawQuery("SELECT " + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + " FROM " +
// QuizContract.QuestionsTable.TABLE_NAME, null);
if(cursor.moveToFirst()){
do {
result = cursor.getString(c.getColumnIndex(QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT));
} while (cursor.moveToNext());
}
cursor.close();
return result;
}
In Statistics-Class
String LastUse = dbHelper.newestQuiz();
LastUsage.setText("Letzte Challenge: " + LastUse);
//LastUsage is a TextView in activity_Statistics.xml
//attached with LastUsage = findViewById(R.id.text_lastUsage);
Either the SQLite statements are totally wrong or I make (basic?) mistakes in statistics class. I need ...newbie help!
I need something like Select column from table where substring of date-Entry == newest
Your issue appear to be column names. That is a Cursor only contains the columns extracted, not all the columns from the table. Although you are basing your query on the column as per QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT that will not be the column name in the cursor.
Rather it will will MAX(SUBSTR(" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT +
// ",11,20))
The simplest way of managing this is to give the column in the Cursor a specific name using AS. As such perhaps use :-
Cursor cursor = db.rawQuery("SELECT MAX(" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + ") AS " + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + " FROM "
+ QuizContract.QuestionsTable.TABLE_NAME, null);
However, you may prefere to use a column name (AS ????) specififc to the situation e.g.
........ AS max_" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + ........
You would then have to use :-
result = cursor.getString(c.getColumnIndex("max_" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT));
Alternately, as it's just a single value/column that is returned in the cursor you could use the column offset of 0, in which case the column name is irrelevant as long as it is valid. However, using offsets is not typically recommended due to the lack of validation of the column being accessed.
re the comment :-
I just need the date part
As the date is a recognised DateTime format (and also that such formats are directly sortable/orderable), use max(date(column_name)) or even max(column_name).

Getting raw SQL query after a prepared statement is built on android

I'm making an Android app and using a SQLite database. In particular I'm using the rawQuery method on a database obtained through a SQLiteOpenHelper. The query I build makes use of the ? marks as placeholders for the real values, which are passed along as an array of objects (e.g., select * from table where id = ?).
The question is, is it possible to get the query with the marks already replaced, at least from the cursor returned from the rawQuery method? I mean something like select * from table where id = 56. This would be useful for debugging purposes.
It's not possible. The ? values are not bound at the SQL level but deeper, and there's no "result" SQL after binding the values.
Variable binding is a part of the sqlite3 C API, and the Android SQLite APIs just provide a thin wrapper on top. http://sqlite.org/c3ref/bind_blob.html
For debugging purposes you can log your SQL with the ?, and log the values of your bind arguments.
You could form it as a string like this
int id = 56;
String query = "select * from table where id = '" + id + "'";
and then use it as a rawQuery like this (if I understood your question properly)
Cursor mCursor = mDb.rawQuery(query, null);
You can also use the SQLiteQueryBuilder. Here is an example with a join query:
//Create new querybuilder
SQLiteQueryBuilder _QB = new SQLiteQueryBuilder();
//Specify books table and add join to categories table (use full_id for joining categories table)
_QB.setTables(BookColumns.TABLENAME +
" LEFT OUTER JOIN " + CategoryColumns.TABLENAME + " ON " +
BookColumns.CATEGORY + " = " + CategoryColumns.FULL_ID);
//Order by records by title
_OrderBy = BookColumns.BOOK_TITLE + " ASC";
//Open database connection
SQLiteDatabase _DB = fDatabaseHelper.getReadableDatabase();
//Get cursor
Cursor _Result = _QB.query(_DB, null, null, null, null, null, _OrderBy);

Android - Database SQLite - convert boolean values

I am working on an Android project with a SQLite database and have a column in the database consisting of a boolean value (which store 1s and 0s) of whether an individual in the database is important.
What I am trying to do is output into a textview listview a "!" if an individual is important, and " " if they are not important using a cursor and cursor adaptor. I can get the "1" and "0"s to appear in the listview, but my question is how/where do I convert these to "!" and " "?
The query I am currently using is
return database.query("people", new String[] {"_id", "important"}, null, null, null, null, "important" + " DESC, " + "name" + " ASC");
The easiest way is to do this in the database query.
The query function is too inflexible for that, so you have to use rawQuery instead:
db.rawQuery("SELECT _id, " +
"CASE WHEN important THEN '!' ELSE ' ' END AS important " +
"FROM people " +
"ORDER BY important DESC, name ASC", null);
If it is a custom ListView which I am pretty sure it is, then you'll have to do that in your CustomAdapter for the ListView, which takes care of populating the ListView with the data you pass to it.Check this out.
Doing it is pretty simple, just loop through your Cursor and do if else statement.

How to construct a SQLite query to GROUP by ORDER?

I've got a rather interesting situation. I have a SQLite database full of addresses and messages (addresses are not unique; messages are). Each message also has a date associated with it. What I want to do is select the first message's address, message, date, and how many messages are associated with the address.
So, I thought, "I can just GROUP by the address to only get one message per address, then ORDER these by the date, and also fetch the COUNT of the address column."
I did that, and it works... kinda. It fetches the correct count, fetches only one message per address, and orders them by date--but it does not select the most recent message for the address. It appears to be arbitrary.
As an example, I have three messages (earliest to latest) A, B, C from address Y, and three messages D, E, F from address Z. The query may fetch messages B and E, then sort them by date. It should fetch messages C and F, and sort those by date.
Here is what I have so far:
// Expanded version:
Cursor cursor = db.query(
/* FROM */ "messages_database",
/* SELECT */ new String[]{ "*", "COUNT(address) AS count" },
/* WHERE */ null,
/* WHERE args */ null,
/* GROUP BY */ "address",
/* HAVING */ null,
/* ORDER BY */ "date DESC"
);
// Or, same code on one line:
Cursor cursor = db.query("messages_database", new String[]{ "*", "COUNT(address) AS count" }, null, null, "address", null, "date DESC");
I feel like this may have to do with the HAVING clause, but I really don't know. I've used MySQL a lot with PHP, but never had to touch HAVING before. I tried setting my HAVING clause to "MAX(date)", but it had no effect. If I set my GROUP BY clause to be "address, date", then they are sorted by date, but of course they are all individual instead of grouped (since the dates differ).
Google searches have proved fruitless; queries like "android sqlite order before group" and "android sqlite group by order" yield no related results.
How can I select the one latest message for each address without removing my GROUP clause (as COUNT() relies upon this)? Do I need two queries?
Edit: Based on the answer #Adrian linked me to in the comments, I came up with two queries which both produced the same result; one row, in which the count was 7 (which is the total number of addresses, not messages per address), and the address shown was not that of the latest message.
The two queries were:
Cursor cursor = db.rawQuery(
"SELECT t.*, COUNT(t.message_content) AS count "
+ "FROM messages_database t "
+ "INNER JOIN ("
+ " SELECT address, MAX(date) AS maxdate "
+ " FROM messages_database "
+ " GROUP BY address "
+ ") ss ON t.address = ss.address AND t.date = ss.maxdate",
null
);
Cursor cursor = db.rawQuery(
"SELECT t1.*, COUNT(t1.message_content) AS count "
+ "FROM messages_database t1 "
+ "LEFT OUTER JOIN messages_database t2 "
+ "ON (t1.address = t2.address AND t1.date < t2.date) "
+ "WHERE t2.address IS NULL",
null
);
SQLite has an extension that makes greatest-n-per-group problems much easier:
If you are using the MAX() or MIN() aggregate functions, and if you are selecting other columns at the same time without using them in an aggregate function or grouping by them, then the resulting values for those columns are guaranteed to come out of the same record that has the maximum/minimum value. (This is not allowed in other SQL dialects, and was introduced in SQLite 3.7.11.)
So, for your problem, you can use a query like this:
SELECT *, COUNT(address) AS count, MAX(date)
FROM messages_database
GROUP BY address
If you don't have SQLite 3.7.11 (which is likely on most Android versions) or using another SQL engine, the following query will work:
SELECT *,
(SELECT COUNT(address) AS count
FROM messages_database m2
WHERE m1.address = m2.address)
FROM messages_database m1
WHERE date = (SELECT MAX(date)
FROM messages_database m3
WHERE m1.address = m3.address)
GROUP BY address
Solved it! I ended up using a combination of #CL.'s method and the methods I outlined in my edited post (clarified in this answer, posted by #Adrian).
Because I didn't want to use 3 SELECT statements (as #CL.'s answer described), I used the same INNER JOIN concept as in the other statements, while retaining his methodology.
The result is this:
Cursor cursor = db.rawQuery(
"SELECT t.*, ss.count AS count "
+ "FROM messages_database t "
+ "INNER JOIN ("
+ " SELECT address, MAX(date) AS maxdate, COUNT(address) AS count "
+ " FROM messages_database "
+ " GROUP BY address "
+ ") ss ON t.address = ss.address AND t.date = ss.maxdate "
+ "GROUP BY t.address "
+ "ORDER BY t.date DESC ",
null
);
And it's working perfectly!

ContactsContract API - fetch display name and organization title

How can we fetch displayname and organization.data through ContactsContract APIs using impicit joins so that I can both these values in a single cursor?
You can use this code to get the organization name and display name:
Cursor organizationNameCursor = cr.query(ContactsContract.Data.CONTENT_URI,new String[] {Organization.TITLE,Organization.DISPLAY_NAME}, ContactsContract.Data.CONTACT_ID + " = " + contactId + " AND ContactsContract.Data.MIMETYPE = '"
+ ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE
+ "'",null,null);
organizationNameCursor.moveToNext();
organizationName.setText(organizationNameCursor.getString(organizationNameCursor.getColumnIndex(Organization.TITLE))+" "+organizationNameCursor.getString(organizationNameCursor.getColumnIndex(Organization.DISPLAY_NAME)));
The ContactsContact data can only be fetched by using content providers which does not allow us to have explicit joins in the query.
You can however have both the values using a single query on Data database as follows:
Cursor c = getContentResolver().query(Data.CONTENT_URI,new String[] {StructuredName.DISPLAY_NAME,Organization.COMPANY}, Data..CONTACT_ID + " = " + contactId,null,null)
In that case you wont be able to get the values directly.
You can very well fetch all the details using a single query by adding a parameter
Data.MIMETYPE IN (StructuredName,CONTENT_ITEM_TYPE, Organization.CONTENT_ITEM_TYPE)
however you need to have that logic in your code which would reorder your data.
Each MIMETYPE will fetch a separate record.
Similarly you can use RawContactsEntity for the same. It provides a join between Contacts and Data database internally.

Categories

Resources