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.
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.
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.
Basically, I have a list of items that I have downloaded over an API and at some point, I record some of those items into my SQLite Database. I perform the CRUD operations such as (Insert, Query, Update, Delete) via a content provider and now I would like to know how to implement a function using or in my content provider that checks whether a specific record already exists in my database so that I would not need to insert it again. More specifically, for my code, each recorded item has an id (which is not the primary key) that was received from the API and I would like to check against that id (which of course is stored into a column in my database).
Define the column with the UNIQUE constraint and use INSERT OR IGNORE when inserting.
e.g.
Create the table like :-
CREATE TABLE IF NOT EXISTS atable (id INTEGER UNIQUE, mydata TEXT);
Insert like :-
INSERT OR IGNORE INTO atable VALUES(10,'myvalue');
Obviously just adapt this as needed according to your top secret mustn't be shown code. :)
I would like to add a string -- a value entered and changed, occasionally, by the user -- as a tag on all of the records downloaded from a server and inserted into a local, Room managed, db. I would like my DAO method to take as arguments, the tag and a list of entities.
It looks to me as if there is no straightforward way to do this (insert a constant value for all rows) with a Room-managed db.
Iterating over all the records and setting the constant value seems pretty inefficient.
Is there a good way to:
Define a table that contains columns that are never in POJOs (used only as query filters)?
Insert a constant value for all rows added in a single insert?
I am using a SQLite database to store data that can be used to reconstruct some objects that I am using in the application I am developing. I am storing CheckIns, Recipients, and ContactMethods.
These objects are related as follows:
CheckIn <--many -- to -- many--> Recipient
Recipient <--one -- to -- many--> ContactMethod
In Java, these objects' fields are defined as follows:
public class CheckIn {
private int id;
private boolean isEnabled;
private Date startTime;
private Repetition repetition;
private Set<Recipient> recipients;
}
public class Recipient {
private String name;
private Set<ContactMethod> contactMethods;
}
public class ContactMethod {
private String type;
private String address;
}
The database schema I have come up with for these objects is defined as follows:
CREATE TABLE checkIn(
_id INTEGER PRIMARY KEY AUTOINCREMENT,
isEnabled INTEGER,
startTime INTEGER,
repetitionNum INTEGER,
repetitionUnits TEXT
);
CREATE TABLE recipient(
_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT
);
CREATE TABLE contactMethod(
_id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT,
address TEXT,
recipientID INTEGER,
FOREIGN KEY(recipientID) REFERENCES recipient(_id) ON DELETE CASCADE
);
CREATE TABLE checkIn_recipient(
checkInID INTEGER,
recipientID INTEGER,
FOREIGN KEY(checkInID) REFERENCES checkIn(_id),
FOREIGN KEY(recipientID) REFERENCES recipient(_id)
);
I have two questions.
1. How do I efficiently INSERT a CheckIn to the database, along with its related objects?
To be more specific, if I have a CheckIn object in Java, not yet committed to the database, how can I structure an INSERT statement that will insert the CheckIn to the CheckIn table, but also store the new CheckIn's relation to one or more Recipients? It seems like to store the relation to the Recipients, I would need to already know the checkIn._id, which hasn't been set yet, since the CheckIn hasn't been entered into the database yet.
2. In Android, what is the best way to rebuild a CheckIn object, for example, from a database query?
I think I know the SQL query that I will need to get the right data:
SELECT checkIn.*, recipient.name, contactMethod.type, contactMethod.address
FROM checkIn
JOIN checkIn_recipient
ON (checkIn._id = checkIn_recipient.checkInID)
JOIN recipient
ON (checkIn_recipient.recipientID = recipient._id)
JOIN contactMethod
ON (recipient._id = contactMethod.recipientID)
This query will get me rows of data containing all of the information I need to build an object for every CheckIn and Recipient in the database, and I know how to get a Cursor object that will iterate through these rows. However, since the data required for a single CheckIn appears on multiple rows, I am confused about the best way to construct individual CheckIn objects. If I am trying to write a method like public Set<CheckIn> getAllCheckIns() that will return a set of all CheckIns that are stored in the database, do I need to run the query above, then loop through each row with the same checkIn._id, and within that loop, every row with the same recipient._id, and so forth? Is there any better way to do this?
I am sorry for the long question. I have only been working with SQL beyond single table databases since this morning, and I didn't want to leave out any important information. Let me know if I missed something.
Answer to Question 1: There are 2 possible ways.
a. You find the ID of the inserted row and use that to insert into the 2nd table. You can find the ID of inserted row if you are using the Android Insert method as documented here:
http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#insert%28java.lang.String,%20java.lang.String,%20android.content.ContentValues%29
Here you must ensure that all DB tables are committed or none.
b. You can create triggers. Triggers are database operations that are automatically performed when a specified database event occurs.
Learn how to create triggers: http://www.sqlite.org/lang_createtrigger.html
So for e.g. you can create a AFTER INSERT trigger that will be fired when a row in inserted in check in table. Then in that trigger you can insert a row into another table.
When the trigger is fired, you have access to the newly inserted row.
Answer to question 2:
I usually do the following :
Select from table 1 - Check In table
Iterate over the cursor and prepare the Check In object.
Within the loop, select from table 2 - Recipient table
Iterate over the recipient table and prepare the Check in object.
This would involve too many DB selects.
Alternatively, you could select all data once and then iterate to prepare the objects.
The point I am trying to make is that you have to iterate :D