So I have a room database all set up, everything is fine, so I can make queries and inserts, delete etc no problems, however i've just run into a situation where id like to return entries by their Ids and duplicates should be allowed, however room is removing the duplicates, so for instance I send it a list of ids say <1,2,3,2,3> and it returns items by their ids but only sends me <1,2,3> removing the duplicate entries. The query I'm making is below (btw complete noob at sql)
#Query("SELECT * FROM card WHERE cardId IN(:cardId)")
LiveData<List<Card>> getCardsByIds(List<Integer> cardId);
Im using it via a repository I created (just a level of abstraction) and calling this repo from a ViewModel, this ViewModel has a mutable live data integer list containing the ids and using a SwitchMap I get the latest live data. ill include the relevant pieces below
CARD REPO calls my Daos method like this
public LiveData<List<Card>> getCardsByIds(List<Integer> cardIds){
return cardDao.getCardsByIds(cardIds);
}
ViewModel calls for them
private MutableLiveData<List<Integer>> cardIds;
//** constructor etc
cards = Transformations.switchMap(cardIds, id -> cardRepository.getCardsByIds(id));
and through the magic of SwitchMap when the cardIds list updates a new query is made and I observe the ViewModel from my fragment. I've debugged it so I know the list of Ids is correct and has the duplicates Ids, but the returned LiveData list is missing the duplicate Ids. any help?
Edit:
The SQLiteDatabase always presents the results as a Cursor in a table format that resembles that of a SQL database.
Source : Google Developer Training
Before the results are returned by the query, the results are stored in a table, and if there are rows with duplicate primary keys, they would be omitted by default.
To achieve what you intend, you can execute a query to find single element by id in loop and append the result to a list.
Updated DAO method:
#Query("SELECT * FROM card WHERE cardId=:cardId")
LiveData<Card> getCardById(Integer cardId);
Update Repository method:
public LiveData<List<Card>> getCardsByIds(List<Integer> cardIds){
List list = new ArrayList();
for(Integer cardId: cardIds){
list.append(cardDao.getCardById(cardId));
}
return list;
}
Hope this helps.
Original Answer:
If id is the primary key of your model, It doesn't allow duplicate data to be entered. Hence while retrieving you might find duplicates missing.
If you have id with duplicate, create another attribute for primary key. (use autogenerate if you don't have any primary key attribute)
A primary key is by default UNIQUE and NOT NULL.
Related
I have a Room table of objects and when I get my network call to refresh the list of objects the items could have been changed. So I need to remove any data that is no longer in the new fetched list.
Pre-Fetch Ids in Table:
|ObjectId|
|:-------|
|1|
|2|
|3|
|4|
Post-Fetch Ids in Table:
|ObjectId|
|:-------|
|1|
|2|
|4|
So Objects removed:
|ObjectId|
|:-------|
|3|
My Quesiton: Is the best way to do this just Delete all entries in the table before calling an #insert method or is there another way using the #update that will allow me to simply fetch the new data and remove all the old data that is no longer in the new list of objects? My understanding is that an #update will simply update objects 1,2 & 4 and leave 3 as it was.
As you have a list of id's (assuming these uniquely identify the rows) then you could update according to the list and delete (before or after the updates) according to the list using a WHERE clause that utilises NOT IN operator(s)
Note that the deletion should be done via an #Query not #Delete e.g. #QUERY("DELETE FROM the_table WHERE id NOT IN(:the_id_list);"
This would minimise the number of deletions. Deletiong and Updates should be done within a single transaction.
I am working on Room database and trying to insert list of items(eg. list of Quotes which contains author name and a quote in my case).
Following is the code I am using:
// view model
BaseApp.daoInstance?.appDao()?.insertQuotes(response!!)
// dao
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertQuotes(listData: MutableList<Quote>)
When I try to insert the same data again, it always inserts as a new data instead of replacing with the current items.
I have researched a lot for this OnConflictStrategy.REPLACE but could not find any proper answer.
Is there anyone facing the same issue and found solution or am I doing anything wrong?
Thank you in advance...!!!
Room, will not check and compare if you have the quote already in the DB.
What it will do is look if the primary key already exists in the DB if it does, Room will replace all old data with the new one.
In your case, you are not specifying an ID so the DB is generating a unique one for you.
What you should do is create a Query that will search for this quote in the DB something like this:
#Query("SELECT * from quote_table WHERE author = :author AND quote = :quote")
List<Quote> getQuoteByAuthorAndQuote(string author, string quote);
This should return a list with a single quote if one is found and empty if it does not exist.
If you would like to override the old one just update the data in the Quote POJO and insert it to the DB using Room.
Have you tried to index your main column and mark it as unique?
#Index(value = {"quote"}, unique = true)}
It suppose to search for your unique or primary key and compare then replace, while in your case you're not defining an ID so it will generate a unique one for you, so it won't even compare and will consider any item as a new one.
Write a new query and function to solve this issue.
When I had same problem, changes in imports did the trick, added following import:
import androidx.room.*;
I have created a room database, and showed my data in a RecyclerView. I know how to add, delete, deleteAll, update data in room data base, but the problem is I don't know how to perform search in room data base?
I have added a search view in the tool bar. But I don't know how to add Queries in NoteDao and the remaining classes, e.g. in my adpter, MainActivity etc.
If you're able to show your Room database in the RecyclerView, this would mean you should know how to use the #Query() tag for fetching your data.
Searching is done using the same #Query tag using the WHERE keyword.
For example, if you wish to find a specific note:
#Query("SELECT * FROM notes WHERE id == :id")
public Note getNote(String id)
However, that's just the simplest way to use WHERE. Frequently, you'll be combining it with other keywords such as IN, LIKE, or BETWEEN.
So for your situation of using a search, you would most likely want to use the LIKE keyword, which can query your database for a field that contains parts of its data that matches the search criteria:
For example:
#Query("SELECT * FROM notes WHERE note_title LIKE :search OR note_message LIKE :search")
public List<Note> getSearchedNote(String search)
This would search your database and return a list of Notes that has either a Note Title or a Note Message that contains the search term you want.
I have in my local database a table called event with the fields:
id (PK)
name (text)
date (time)
favorite (int, 0 or 1)
The server would give me a list of events, without the favorite field, the favorite field is only local.
I'm using this code to insert events
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insertEvents(List<Event> events);
The thing is that, if an event is already stored in the local database, it will loose the favorite property, and it will always be false after is inserted and replaced.
Is there a way to bulk insert like this, but keep the favorite field?
I have had the same situation. This is how I solved it.
Since the data coming from the server is immutable and would reset local values, I decided to keep a table for the data coming from the server and another for the "local" data.
Table ServerData for remote data.
Table ServerDataAttrs for local only data.
ServerDataAttrs contains all local columns along with the primary key id from table ServerData.
ServerData.id == ServerDataAttrs.id
This way, each row from ServerDataAttrs is related to its correspondent row in ServerData (the other way around is not required, that is why I use later LEFT JOIN). You can always replace data on ServerData and local data from ServerDataAttrs will remain untouched.
I did not set a foreign key to avoid accidental removal. I deleted the local data only when calling delete method from its DAO.
For querying the data, you can create a POJO that has all remote and local values together.
public class ServerDataWithAttrsPojo {
private long id;
private String name;
...
private int favorite;
// public constructor, getters and setters...
}
The query must be then a JOIN between both tables.
SELECT sd.id, sd.name, ...., sda.favorite
FROM ServerData sd LEFT JOIN ServerDataAttrs sda ON sd.id = sda.id
I use LEFT JOIN just in case an entity from ServerData does not have a row in ServerDataAttrs as I mentioned before.
I've had the same problem and this is the scenario of how I've solved it.
Check if there are favorite items in your DB and fetch them.
Fetch the data from API.
Insert all of the data into DB.
Update your DB with the previously fetched items that have been marked as favorite.
I'm using ActiveAndroid in an app for database interaction. I need to join a few tables and then filter the results, which I would usually accomplish using a SQL query with a sub-query.
How might one accomplish a sub-query using ActiveAndroid?
If a sub-query cannot be done, is there a way to only return unique Model objects? The join in my query has the effect of creating duplicate rows in the join table, but I only want one instance of each Model to be returned.
EDIT: Providing more info
The model I'm trying to retrieve is called Profile.
With regard to the query, there are 3 tables:
Profiles, Roles, and ProfileRoleJoins
There is a many-to-many relationships between Profiles and Roles and that relationship is maintained by the join-table called ProfileRoleJoins.
My goal is to obtain all Profiles that have a certain "event_id" and are connected to one or more Roles specified by a list of Role types.
Of interest to this query are the following fields:
Profiles
Id
event_id (not a foreign key, just a data field)
Roles
Id
type (TEXT)
ProfileRoleJoins
profile (foreign key)
role (foreign key)
What I've Tried
String eventId = //provided as input to this function
String[] roles = //array of "types" of roles we want to limit to
String whereClause = //built in a loop, looks like "Roles.type = ? OR Roles.type = ?..."
new Select()
.from(ProfileEntity.class)
.where("event_id = ?", eventId)
.join(ProfileRoleJoinsTable.class)
.on("Profiles.Id = ProfileRoleJoins.profile")
.join(RoleEntity.class)
.on("ProfileRoleJoins.role = Roles.Id")
.where(whereClause, roles)
.execute();
The above query has the problem that it returns duplicate Profiles because a single Profile can be associated with multiple Roles which creates duplicates during the join process.
I need to retrieve a list of unique Profiles.
I tried using Distinct(), but that qualifier applies per set of columns - I need entire Profiles back.
The way I would normally accomplish the uniqueness is by doing the above query in a sub-query and return only DISTINCT Profiles.Id values. Then I would run a filter in the main query against the original Profiles table.
I still don't know how to do sub-queries with ActiveAndroid (if its even possible), but I found out the source of my problem. I was using an outdated version of the library and there was some kind of error in the query system that screwed up the results. With the current version of the library I do not get duplicates.