SQLite query: selecting the newest row for each distinct contact - android

I'm working with the Android SQLite db, and I have a table with messages in it. Each message has a ContactId. I need to get a list of the newest message associated with each ContactId.
My query looks like this so far:
SELECT * FROM messages GROUP_BY ContactId HAVING ( COUNT(ContactId) = 1 ) ORDER_BY MessageTime DESC
However when I run the query I get this exception:
near "ContactId": syntax error: , while compiling: SELECT * FROM messages GROUP_BY ContactId HAVING ( COUNT(ContactId) = 1 ) ORDER_BY MessageTime DESC
Here's the table definition in case it helps:
create table messages (_id integer primary key autoincrement, ContactId text not null, ContactName text not null, ContactNumber text not null, isFrom int not null, Message text not null, MessageTime int not null);

NOTE: My answer below does not appear to be working as of SQLite 3.7.5, I suggest using the "JOIN" query suggested by Larry.
You are close. What you need to do is sort all the records first, using a subquery table, and then group them. The values that are returned in the result set will be from the last row in each group. So you actually want newer messages to appear at the bottom if you are trying to get the newset message. The "GROUP BY" already ensures you get one row per ContactId.
SELECT * FROM (SELECT * FROM messages ORDER BY MessageTime) GROUP BY ContactId
The HAVING clause is not needed. I haven't used it before but according to the docs the HAVING clause will discard whole groups that don't match, but it doesn't sound like you want any groups discarded, you want results from every ContactId.
Also note there is no underscore in "ORDER BY" or "GROUP BY".

Here are two ways to do it.
This query builds a list of the most recent times for each user, then JOINs that back to the message table to get the message information:
SELECT M1.* FROM messages M1 JOIN
(SELECT ContactId, MAX(MessageTime) AS MessageTime FROM messages GROUP BY ContactId) M2
ON M1.ContactID = M2.ContactID AND M1.MessageTime = M2.MessageTime;
This query does something slightly different. It looks at each message and asks if there exists any later message for the same contact. If not, the row must be the most recent one:
SELECT M1.* FROM messages M1
WHERE NOT EXISTS (SELECT * FROM M2
WHERE M2.ContactID = M1.ContactID AND M2.MessageTime > M1.MessageTime)

Related

How to insert into SQLite database without affecting distinct?

I am using an SQLite database that holds different messaging conversations in a thread id. To get the main conversation list I use the following code:
database.query(true, MessagesHelper.CONVERSATION_TABLE, inboxCols, null, null, MessagesHelper.THREAD_ID, null, "${MessagesHelper.DATE} DESC", null)
The issue is that I provide a function to load in older messages that are not showing, by which I use:
database.insert(MessagesHelper.CONVERSATION_TABLE, null, values)
The issue is that after inserting messages, which are older than the most recent one that the distinct list used to call, it now shows the older added messages in the inbox list, which makes the list all out of order and causes a lot of confusion.
Here is the create statement:
private const val CONVO_CREATE = "CREATE TABLE $CONVERSATION_TABLE($NAME VARCHAR(255), $THREAD_ID VARCHAR(255), $MESSAGE_ID VARCHAR(40), $ADDRESS VARCHAR(14), $BODY VARCHAR(500), $SUBJECT VARCHAR(100), $MMS VARCHAR(255), $MESSAGE_TYPE VARCHAR(7), $MMS_TYPE VARCHAR(20), $CONVERSATION_TYPE VARCHAR(20), $GROUP_ADDRESS VARCHAR(255), $GROUP_NAME VARCHAR(255), $READ VARCHAR(10), $WHO VARCHAR(3), $COUNT INTEGER, $DATE INTEGER);"
Inbox columns are:
val inboxCols = arrayOf(MessagesHelper.COUNT, MessagesHelper.GROUP_ADDRESS, MessagesHelper.NAME, MessagesHelper.ADDRESS, MessagesHelper.READ, MessagesHelper.GROUP_NAME, MessagesHelper.BODY, MessagesHelper.DATE, MessagesHelper.THREAD_ID, MessagesHelper.CONVERSATION_TYPE, MessagesHelper.MESSAGE_ID, MessagesHelper.WHO)
Is there any way to insert into the database but have the distinct query still sort by each thread_id by date as well?
Thanks!
I believe the issue is that DISTINCT considers the entire row being extracted, thus it is likely, based upon the column names, that both the COUNT column and the DATE column would or could likely be different when inserting a new message (e.g. perhaps count would initially be 0?) and thus cause them to be inserted as they make a new row DISTINCT (not a duplicate).
e.g. Consider this table :-
Then if DISTINCT is used just on the idbet column, the result is 2 rows 1 for where idbet is 3000 and another for where idbet is 1981 as per :-
However, if columns idbet and gamble are extracted then all 4 columns are extracted as there are now no duplicates, as per :-
If the row with _id 350 had WIN in the gamble column then 3 rows would be extracted as rows with _id's 349 and 350 would be a duplicate according to the idbet and gamble columns, as per :-
Perhaps rather than DISTINCT, or just DISTINCT you should use a WHERE condition or conditions (4th and 5th parameters of query). Perhaps "count < 1" as the 4th parameter (5th null) this does assume that count will initially be 0. An alternative would be to reduce the columns extracted but that may not be practical.

Content provider with multiple joins on same table

I'm working with a ListView that has a CursorAdapter and the query is done via LoaderManager to my ContentProvider. Everything seems to work fine, but when I try to do a multiple query with the same table I get a duplicated column error:
table rating:
CREATE TABLE rating ( _id INTEGER PRIMARY KEY, message TEXT NOT NULL,
from_user_id INTEGER NOT NULL, to_user_id INTEGER NOT NUL);
table users:
CREATE TABLE users ( _id INTEGER PRIMARY KEY, name TEXT NOT NULL,
avatar TEXT, GENDER TEXT);
QueryBuilder:
queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(rating LEFT JOIN users ON
rating.from_user_id = users._id LEFT JOIN users
ON rating.to_user_id = users._id );
Is there any way to use an alias or any similar approach?
Yes, you can use the as keyword to set an alias for a table. Check out https://www.sqlite.org/syntax/join-clause.html and https://www.sqlite.org/syntax/table-or-subquery.html.
Your join clause would look like this:
rating LEFT JOIN users as from_users ON rating.from_user_id = from_users._id
LEFT JOIN users as to_users ON rating.to_user_id = to_users._id
Actually you only need to set an alias for one of both occurrences of users, but setting an alias for both makes it easier to read and understand.
Note that you also need to prefix all columns that come from the users table in your projection and all other parts of the query (e.g. where, order by).
So given that you're interested in the user names of both users (the rating user and the rated user), your projection would look like this:
new String[]{"from_users.name", "to_users.name"};

Distinct values and count in MySQL

I have a table that contains messages that have been saved into a ORM database.I have a messages table and Users table using which I am fetching and saving values.
This is my sql query:
Select DISTINCT ch.user AS _id,
con.name,
con.image,
con.color,
con.contact_id
from messages ch
inner join users con
on con.phone = ch.user
order by ch.timestamp ASC
I also have a boolean field status in the messages table. I want to get the count of all rows that have status = false.
Can it be integrated in my above prepared query, so that I can get all the results in a single query? Or will I need to execute another one for fetching count of rows?
You could use a sub select (check this Fiddle):
Select DISTINCT ch.user AS _id,
con.name,
con.contact_id,
(SELECT COUNT(*)
FROM messages ch2
WHERE status = false AND ch.user = ch2.user) AS MY_COUNT
from messages ch
inner join users con
on con.phone = ch.user
order by ch.timestamp ASC;
Or you you select from the result of the select, what might be better in terms of performance.

store the incoming messages in sorted order(as per the incoming numbers)

I am designing an app in that i am storing the incoming messages in my own created database and from this database i am displaying these messages in my application.
for this i am using this code,
To insert the data,
mydb = openOrCreateDatabase("db",MODE_PRIVATE, null);
mydb.execSQL("CREATE TABLE IF NOT EXISTS sms_inbox (SMS_TEXT varchar , SENDER_NUMBER varchar );");
mydb.execSQL("INSERT INTO sms_inbox VALUES('" + stBody + "', '" + stPhoneNumber + "');");
To read the data,
Cursor c = mydb.rawQuery("SELECT * from sms_inbox", null);
and then i am displaying these messages,
So now my problem is this that I want to store these messages according to the phone numbers,
example,
phoneno.-12345
"ALL the messages of 12345 will be shown here"
phone no.-23456
"ALL the messages of 23456 will be shown here"
Well, you could store those messages as another entry in sms_inbox table, but it would be helpful to store a timestamp as well to be able to distinguish each message.
When you query you could pass the phone number as what is the phone number you're interested into: SELECT * from sms_inbox where 'SENDER_NUMBER' = '?'
Or you could make a select ordered by SENDER_NUMBER: SELECT * from sms_inbox order by 'SENDER_NUMBER' desc;
In the end it all depends on the use-cases you have. You could also use another table for keeping only the messages for a number and map a one-to-many relationship.

Problem in getting filtered results from Content Resolver (Inbox Messages)

I am trying to get all Inbox Messages using ContentResolver based on message ids stored in another table..
I want to exclude all Inbox SMSs whose message id is not in my custom table(spam_msgids).
What i am doing is (I dont know whether this is correct or not):
Uri uriSms=Uri.parse("content://sms/inbox");
Cursor cursor = context.getContentResolver().query(uriSms, null,"_id NOT IN (SELECT msg_id FROM spam_msgids)",null,null);
But its giving me an error though table is already created ...
Error : 07-19 17:04:16.412: ERROR/DatabaseUtils(141):
android.database.sqlite.SQLiteException: no such table: spam_msgids: , while compiling: SELECT * FROM sms WHERE (type=1) AND (_id NOT IN (SELECT msg_id FROM spam_msgids)) ORDER BY date DESC
Can anyone please tell me where i am going wrong ?
You can fetch all IDs from you database, build a comma separated string and pass it to NOT IN so that it would look like:
Cursor cursor = context.getContentResolver().query(uriSms, null,"_id NOT IN (" + stringOfCommaSeparatedStringOfIDs + ")",null,null);
You are querying from android internal database and surely that database doesn't include spam_msgids

Categories

Resources