I have a list of notes in room database. I want to choose the list of specific notes depending on some characteristic. For this propose I add a second field note_second_id to a PrimaryKey:
#PrimaryKey(autoGenerate = true)
private long note_id;
private long note_second_id;
Then I create the next query:
#Query("SELECT * FROM " + Constants.TABLE_NAME_NOTE + " WHERE note_second_id = :second_id ")
List<Note> getNotes(long second_id);
When I want to add a new note I use:
long j = activityReference.get().dataBase.getNoteDao().insertNote(note);
deck.setNote_id(j);
deck.setNote_second_id(note000);
where methods .setNote_id(), and setNote_second_id() look like:
public void setNote_id(long deck_id) {this.note_id = note_id; }
public void setNote_second_id(long notesecondid) {
this.note_second_id = notesecondid; }
and note000 is a constant:
Long note000 = 11111L;
And in the activity, where I want these list to be shown I write:
private class RetrieveTaskDeck extends AsyncTask<Void,Void,List<Deck>> {
private WeakReference<MainActivity> activityReference;
// only retain a weak reference to the activity
RetrieveTaskDeck(MainActivity context) {
activityReference = new WeakReference<>(context); }
#Override
protected List<Note> doInBackground(Void... voids) {
if (activityReference.get()!=null)
return activityReference.get().dataBase.getNotesDao().getNotes(note000);
else
return null; }
#Override
protected void onPostExecute(List<Note> notes) {
if (notes !=null && notes.size()>0 ){
activityReference.get().notes.clear();
activityReference.get().notes.addAll(notes);
// hides empty text view
activityReference.get().textViewMsgDeck.setVisibility(View.GONE);
activityReference.get().decksAdapter.notifyDataSetChanged(); } } }
The problem is that this way does not work. It does not show any of the notes. Only when I use the query:
#Query("SELECT * FROM " + Constants.TABLE_NAME_NOTE)
List<Note> getAllNotes();
and
activityReference.get().dataBase.getNotesDao().getAllNotes();
only then I get the list of all notes, which I have. But I need only some specially marked notes to be shown.
What is wrong and what can I do? It seems like something is wrong with query... Maybe someone, who had an experience working with room databes and queries for it, may give some advice...
Actually, I have solved the problem. Here is a solution.
I used the next query:
#Query("SELECT * FROM " + Constants.TABLE_NAME_NOTE + " where note_content LIKE :second_id ")
List<Note> getNotes(String second_id);
Where "note_content" is a name of a column and "second_id" is a first row, written after the column name:
#ColumnInfo(name = "note_content")
private String second_id;
Pay attention, if I write String second_id somewhere after the first String value (second or third) after the proclamation of the column, it does not make search by this word. But in this way, written above, it works.
So, I can set this String value "second_id" of the column dynamically, depending on the task, and a user does not need to know that it even exists. I tried playing with ID in PrimaryKey to solve the same task, but it didn't work. Actually, using of the String value in Column works. Hope it will help someone. Also, if anyone knows how to solve the same task using IDs in PrimaryKey, let me know writing your solution here.
Related
My Problem:
I'm struggling to eliminate the compiling error on the following Room #Query statement in a Room DAO. As you can see, the SQLite query statement is joining various fields from different tables. The missing fields identified by the error are a part of the Notes class constructor identified in the List type for the method. I think I need to change the List type identified. If I'm right, I need some guidance/suggestion on how I should resolve it. Do I need to create a new Class and DAO with just those specific fields queried? Or maybe just a class since there is not table specific to these fields only. The error is:
error: The columns returned by the query does not have the fields [commentID,questionID,quoteID,termID,topicID,deleted] in com.mistywillow.researchdb.database.entities.Notes even though they are annotated as non-null or primitive. Columns returned by the query: [NoteID,SourceID,SourceType,Title,Summary]
List getNotesOnTopic(String topic);
#Query("SELECT n.NoteID, s.SourceID, s.SourceType, s.Title, c.Summary FROM Comments as c " +
"LEFT JOIN Notes as n ON n.CommentID = c.CommentID " +
"LEFT JOIN Sources as s ON n.SourceID = s.SourceID " +
"LEFT JOIN Topics as t ON n.TopicID = t.TopicID WHERE t.Topic = :topic AND n.Deleted = 0")
List<Notes> getNotesOnTopic(String topic);
What I'm trying to do:
I'm attempting to convert and existing Java desktop app with an embedded an SQLite database. The above query does work fine in that app. I only want to pass field data from these tables.
What I've tried:
I've done some googling and visited some forums for the last few days (e.g. Android Forum, Developer.Android.com) but most of the Room #Query examples are single table full field queries (e.g. "Select * From table"). Nothing I found yet (there is probably something) quite addresses how and what to do if you are joining and querying only specific fields across tables.
I think I may have fixed my issue. I just created a new class called SourceTable and designated the queried fields in the constructor. The only catch was I, according to a follow up error, was that the parameters had to match the field names.
public class SourcesTable {
private int NoteID;
private int SourceID;
private String SourceType;
private String Title;
private String Summary;
public SourcesTable(int NoteID, int SourceID, String SourceType, String Title, String Summary){
this.NoteID = NoteID;
this.SourceID = SourceID;
this.SourceType = SourceType;
this.Title = Title;
this.Summary = Summary;
}
}
and then I update my list method:
List<SourcesTable> getNotesOnTopic(String topic);
Currently, we have the following database table
#Entity(
tableName = "note"
)
public class Note {
#ColumnInfo(name = "body")
private String body;
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
The length of the body string, can be from 0 to a very large number.
In certain circumstance, we need to
Load the all notes into memory.
A LiveData which is able to inform observers, if there's any changes made in the SQLite note table.
We just need the first 256 characters of body. We do not need entire body. Loading entire body string for all notes might cause OutOfMemoryException.
We have the following Room Database Dao
#Dao
public abstract class NoteDao {
#Query("SELECT * FROM note")
public abstract LiveData<List<Note>> getAllNotes();
}
getAllNotes able to fulfill requirements (1) and (2), but not (3).
The following getAllNotesWithShortBody is a failed solution.
#Dao
public abstract class NoteDao {
#Query("SELECT * FROM note")
public abstract LiveData<List<Note>> getAllNotes();
#Query("SELECT * FROM note")
public abstract List<Note> getAllNotesSync();
public LiveData<List<Note>> getAllNotesWithShortBody() {
MutableLiveData<List<Note>> notesLiveData = new MutableLiveData<>();
//
// Problem 1: Still can cause OutOfMemoryException by loading
// List of notes with complete body string.
//
List<Note> notes = getAllNotesSync();
for (Note note : notes) {
String body = note.getBody();
// Extract first 256 characters from body string.
body = body.substring(0, Math.min(body.length(), 256));
note.setBody(body);
}
notesLiveData.postValue(notes);
//
// Problem 2: The returned LiveData unable to inform observers,
// if there's any changes made in the SQLite `note` table.
//
return notesLiveData;
}
}
I was wondering, is there any way to tell Room database Dao: Before returning List of Notes as LiveData, please perform transformation on every Note's body column, by trimming the string to maximum 256 characters?
Examining the source code generated by Room Dao
If we look at the source code generated by Room Dao
#Override
public LiveData<List<Note>> getAllNotes() {
final String _sql = "SELECT * FROM note";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
...
...
final String _tmpBody;
_tmpBody = _cursor.getString(_cursorIndexOfBody);
_tmpPlainNote.setBody(_tmpBody);
It will be great, if there is a way to supply transformation function during runtime, so that we can have
final String _tmpBody;
_tmpBody = transform_function(_cursor.getString(_cursorIndexOfBody));
_tmpPlainNote.setBody(_tmpBody);
p/s Please do not counter recommend Paging library at this moment, as some of our features require entire List of Notes (with trimmed body String) in memory.
You can use SUBSTR, one of SQLite's built-in functions.
You need a primary key in your #Entity. Assuming that you call it id, you can write a SQL like below.
#Query("SELECT id, SUBSTR(body, 0, 257) AS body FROM note")
public abstract LiveData<List<Note>> getAllNotes();
This will return the body trimmed to 256 chars.
With that being said, you should consider segmenting your rows. If you have too many rows, they will eventually use up your memory at some point. Using Paging is one way to do it. You can also use LIMIT and OFFSET to manually go through segments of rows.
I'm trying to get id column from my database, ad it to ArrayList and to each id add "\t0",
My database is created using Room, i have a lot of column which one of them is
#PrimaryKey(autoGenerate = true)
private int id;
I am operating using ItemDAO and i have there function
#Query("SELECT * FROM item")
List<Item> getItems();
Which writes to ArrayList<Items> all of contents
I was thinking of running it trough the loop getting id and adding to ArrayList<String> but this doesn't seems to be eficient.
Your DAO:
#Query("SELECT Id FROM item")
List<Integer> getAllIds();
Your model:
#ColumnInfo(name = "Id")
#PrimaryKey(autoGenerate = true)
private int id;
In you query SELECT * FROM item * means select All, put there your column name and you will get list of objects from that column
Example: Select all items in id column SELECT id FROM item
I tried to modify and test #Valgaal 's solution. It turns out that Room can also return other type of values, more than just id (or integer).
For example, you can write an item class like this:
#Entity(tableName = Item.TABLE_NAME)
public class Item {
public static final String TABLE_NAME = "ItemsTable";
public static final String COL_DESC = "Description";
#PrimaryKey(autoGenerate = true)
private int id;
#ColumnInfo(name = COL_DESC)
private String description;
// getter & setter...
}
And then, you can write Dao like this:
#Dao
public interface ItemDao {
#Query("SELECT * FROM " + Item.TABLE_NAME)
List<Item> getItems();
#Query("SELECT " + Item.COL_DESC + " FROM " + Item.TABLE_NAME)
List<String> getItemDescriptions();
}
And it's functional as it should be.
I guess all of the other data types that Room can save (including custom types?) can be queried (and returned lists of specific column data) by the same logic above. Hope this would help someone in the future!
For returning multiple columns, create a pojo class that can be set as a return type for your DAO function
Note the select query should contain the Pojo class variable name (can be done via AS keyword)
Detailed answer here
https://stackoverflow.com/a/50802209/1029110
I landed on this question for my issue...but didnt find answer. So this may help others.
Let's take this example: I have a form, which has several sections, each having questions. Sideways, I have answers that are mapped to questions and they have another column that I want to filter on when querying:
So I have the following entities:
#Entity(tableName = "sections")
public class Section {
#PrimaryKey
public long id;
public String title;
}
#Entity(tableName = "questions")
public class Question {
#PrimaryKey
public long id;
public String title;
public long sectionId;
}
#Entity(tableName = "answers")
public class Answer {
#PrimaryKey
public long id;
public long questionId;
public int otherColumn;
}
In the section DAO I want to retrieve all of them.
Here's the POJO that I want filled by this query:
class SectionWithQuestions {
#Embedded
public Section section;
#Relation(parentColumn = "id", entityColumn = "sectionId", entity = Question.class)
public List<QuestionWithAnswer> questions;
public static class QuestionWithAnswer {
#Embedded
public Question question;
#Relation(parentColumn = "id", entityColumn = "questionId", entity = Answer.class)
List<Answer> answers;
}
}
In another application, the query would be:
SELECT s.*, q.*, a.*
FROM sections s
LEFT JOIN questions q ON q.sectionId = s.id
LEFT JOIN answers a ON a.questionId = q.id
WHERE s.id = :sectionId and a.otherColumn = :otherColumn
However in Room I have found out that if you want an object and their relations (like a user and its pets in the example), you only select the object, and the relations are queried in a second query. That would be:
#Query("SELECT * FROM sections WHERE id = :sectionId")
Then in the generated code there would be (pseudo code):
sql = "SELECT * FROM sections WHERE id = :sectionId" // what's inside #Query
cursor = query(sql)
int indexColumn1 = cursor.getColumnIndex(col1)
int indexColumn2
... etc
while (cursor.moveToNext) {
masterObject = new object()
masterObject.property1 = cursor.get(indexColumn1)
... etc
__fetchRelationshipXXXAsYYY(masterObject.relations) // fetch the child objects
}
and this __fetch XXX as YYY method is as follows:
sql = "SELECT field1, field2, ... FROM a WHERE foreignId IN (...)"
similar algo as previously: fetch column indices, and loop through the cursor
So basically it creates 2 queries: one for the master object and one for the relations. The 2nd query is automatically created and we have no control over it.
To get back to my problem where I want relations but also filter on the child column, I'm stuck:
in the 1st query I can't reference the otherColumn column because it doesn't exist
in the #Relation I can't either because the only properties of this annotation are the join column and entity definition
Is this possible in Room or do I have to make the subqueries myself?
Bonus question: why don't they join tables in a single query but create 2 queries instead? Is this for performance reasons?
Edit to clarify what I expected:
That's what I expected to write:
#Query("SELECT s.*, q.*, a.* " +
"FROM sections s " +
"LEFT JOIN questions q ON q.sectionId = s.id " +
"LEFT JOIN answers a ON a.questionId = q.id " +
"WHERE s.id = :sectionId and a.otherColumn = :additionalIntegerFilter")
SectionWithQuestionsAndAnswers fetchFullSectionData(long sectionId);
static class SectionWithQuestionsAndAnswers {
#Embedded Section section;
#Relation(parentColumn = "id", entityColumn = "sectionId", entity = Question.class)
List<QuestionWithAnswers> questions;
}
static class QuestionWithAnswers {
#Embedded Question question;
#Relation(parentColumn = "id", entityColumn = "questionId", entity = Answer.class)
Answer answer; // I already know that #Relation expects List<> or Set<> which is
// not useful if I know I have zero or one relation (ensured
// through unique keys)
}
That's pseudo code that I imagined to be implemented by Room as the generated code:
function fetchFullSectionData(long sectionId, long additionalIntegerFilter) {
query = prepare(sql); // from #Query
query.bindLong("sectionId", sectionId);
query.bindLong("additionalIntegerFilter", additionalIntegerFilter);
cursor = query.execute();
Section section = null;
long prevQuestionId = 0;
Question question = null;
while (cursor.hasNext()) {
if (section == null) {
section = new Section();
section.questions = new ArrayList<>();
section.field1 = cursor.get(col1); // etc for all fields
}
if (prevQuestionId != cursor.get(questionIdColId)) {
if (question != null) {
section.questions.add(question);
}
question = new Question();
question.fiedl1 = cursor.get(col1); // etc for all fields
prevQuestionId = question.id;
}
if (cursor.get(answerIdColId) != null) { // has answer
Answer answer = new Answer();
answer.field1 = cursor.get(col1); // etc for all fields
question.answer = answer;
}
}
if (section !=null && question != null) {
section.questions.add(question);
}
return section;
}
That's one query, and all my objects fetched.
I find Room Relations hard to work with, not very flexible and much of the work is done under the hood in a way that is hard to really be sure how.
In my projects, most of the time I just create presentation objects - objects dedicated for some UI presentation that can be filled with a custom select.
That way I have much more control over what I want to fetch from DB (i.e. what I really need), and I fill that into that custom presentation object.
I'm just pasting the information provided on the feature request I posted (see my comment on my question):
Hi there - we have recently released a new feature where relational query methods can be defined with Multimap return types. With this new feature, you should be able to achieve the results discussed in this thread. For more info on this new feature, you can check out the following resources:
Define relationships between objects: https://developer.android.com/training/data-storage/room/relationships
Relational Query Methods in ADS 2021: https://youtu.be/i5coKoVy1g4?t=344
The new MapInfo annotation: https://developer.android.com/reference/androidx/room/MapInfo
I know link-only answers aren't great, but I didn't have the opportunity to test this. If someone has a better answer, I'll accept it.
I found a better solution for this. Instead of aliasing all columns you can use #RawQuery annotation.
First of all, add a prefix for embedded table annotation using table name or its alias like #Embedded(prefix = "P.") or #Embedded(prefix = "Post."):
public class UserPost {
#Embedded
private User user;
#Embedded(prefix = "P.")
private Post post;
}
Then in your Dao, create a function to run a raw query, and create another function to run a raw query:
#Dao
public interface UserDao {
String USER_POST_QUERY = "SELECT U.*, P.* FROM User as U " +
"INNER JOIN Post as P ON U.id = P.userId " +
"WHERE P.status = 1";
#RawQuery
LiveData<List<UserPost>> rawQuery(SimpleSQLiteQuery query);
default LiveData<List<UserPost>> getAlertViolationsAsync() {
return rawQuery(new SimpleSQLiteQuery(USER_POST_QUERY));
}
}
I have three ArrayLists, two are Strings and the last one is an Integer.
The first ArrayList contains the specific Variant (variantArray) for a certain Product like the flavor variant of a cola, the second one contains the Unit (unitArray) that contains the unit like the size (80oz, 500mL, 1L) of the product, and the last one is the quantity *(quantityArray).
This is the class I use.
public class CurrentOrderClass {
//ArrayLists
private ArrayList<String> variantArray = new ArrayList<String>();
private ArrayList<String> unitArray = new ArrayList<String>();
private ArrayList<Integer> quantityArray = new ArrayList<Integer>();
//TODO ArrayList functions
public ArrayList<String> getUnitArray() {
return unitArray;
}
public void setUnitArray(ArrayList<String> unitArray) {
this.unitArray = unitArray;
}
public void addToUnitArray(String unit){
this.unitArray.add(unit);
}
public ArrayList<Integer> getQuantityArray() {
return quantityArray;
}
public void setQuantityArray(ArrayList<Integer> quantityArray) {
this.quantityArray = quantityArray;
}
public void addToQuantityArray(int quantity){
this.quantityArray.add(quantity);
}
public ArrayList<String> getVariantArray() {
return variantArray;
}
public void setVariantArray(ArrayList<String> variantArray) {
this.variantArray = variantArray;
}
public void addToVariantArray(String variantArray){
this.variantArray.add(variantArray);
}
#Override
public String toString() {
return "[ product=" + productName + ", variants=" +
variants + " , unit=" + unit + " , quantity=" + quantity + "]";
}
}
I take in user input so the user chooses the Variant, Unit, and Quantity and the input is then stored in their respective ArrayLists.
However, I'm having a problem updating the ArrayLists when the user inputs a Variant and a Unit that already exists in the ArrayLists but only with a different Quantity. What I want to do is not to add the new entry, but update the current ArrayLists in such a way that when the user inputs a variant and a unit but different quantity, I'd only update the quantity.
The first thing I tried was to see if the input already exists in the ArrayList by using indexOf(userInputVariant) and then check if it matches with a indexOf(userInputUnit), this would mean that the user input repeated already. However, I don't think indexOf runs on that logic and the value it returns is the value where it found the first instance of the userInputVariant string.
The second attempt I tried was using a for each loop, however, I'm once again having a hard time returning the index I want properly.
I instantiate an object of the CurrentOrderClass named currentOrder and prepopulated the first 10 elements of its variantArray with "Grape"
After that, I tried this if-else statement:
if( (currentOrder.getVariantArray().indexOf(product.getVariant()[variantPosition]) ==
currentOrder.getUnitArray().indexOf(product.getUnit()[position])
However, as mentioned above, indexOf returns an int where it first found the String that I told it to find, I don't think it checks if it exists after that certain position.
The second code I tried was to use a for-each and return the position from there, but again, it only returned position = 0.
for( String Grape : currentOrder.getVariantArray() ){
Log.d("Angelo", "Retrived Element: " + Grape
+ " at position = " + currentOrder.getVariantArray().indexOf(Grape));
}
I'm currently thinking of running a for-each loop inside a for-each loop but I don't know how to make it return the proper position that I want.
Does anyone have an idea on how to make it work properly? I need to return the position of an item that appears multiple times in my variantArray by "cross-referencing" it with my unitArray. Thanks.
One for loop is enough in this case.
This will some thing like:
isExist = false;
for(index = 0; index<variantArray.size(); index++){
if(variantArray.get(index) == userChoiceVariant &&
unitArray.get(index) == userChoiceUnit){
//update quantity
isExist = true;
}
}
if(!isExist){
//insert order
}
The first solution I reach is: "Search all the indexes matching with the user input in the variant array, store this in another ArrayList<Integer>, name it, matchingVariants for now. Then, for each of the ints in matchingVariants get that position of the units list. If one of this items matches, then you should update, otherwise insert.
However, I think a better solution is wrap Variants and Units in one class, for example Product with attributes String variant and String unit. Implementing equals method you can forget about your variants and units lists and have only one ArrayList<Product>
As a next step, I would use a Map with Product as key and Integer as values. So I can just update the Map rather than having all the lists you have.