How to get specific column using Android Room - android

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.

Related

Android Room: select list of custom objects removing duplicates by a specific property

I'm using Room library to retrive a List without duplicates for property "text".
This is the code of the query in MyObjectDao class:
#Query("SELECT DISTINCT * FROM historyentity WHERE text LIKE :inputText || '%'")
List<MyObject> findByText(String inputText);
I also post MyObject class:
#Entity
public class MyObject {
#PrimaryKey(autoGenerate = true)
public int uid;
#ColumnInfo(name = "text")
public String text;
#ColumnInfo(name = "timestamp")
public Long timestamp;
}
Anyway I still get get results with duplicates for field "text". How can I get a List that does not contains duplicates on field "text"?
For example if I have the following three elements in the database
database.addMyObject(new MyObject("dog", System.currentTimeInMills());
database.addMyObject(new MyObject("cat", System.currentTimeInMills());
database.addMyObject(new MyObject("dog", System.currentTimeInMills());
when I call my query I want to get only the first two elements.
If you want just list with unique text values you can try this one:
#Query("SELECT DISTINCT text FROM historyentity WHERE text LIKE :inputText || '%'")
List<String> findByText(String inputText); // <-- changed type to List<String>
UPDATE
You can try this query (it gets only one item with text value - with maximal id - or you can use maximal (or minimal) timestamp):
Select * from historyentity as t1
INNER JOIN (select text,max(uid) as uid from historyentity WHERE text LIKE :inputText group by text) t2
ON t1.text = t2.text and t1.uid = t2.uid

android.database.sqlite.SQLiteException: near "?": syntax error (code 1):

I'm using Room library (MVVM Pattern), and one of the Dao functions returns this error message:
android.database.sqlite.SQLiteException: near "?": syntax error (code 1): , while compiling: UPDATE parcel_table SET possibleDeliveryPersonsList = ?,? WHERE id = ?
This is the Dao code:
#Dao
public interface ParcelDao {
#Insert
void insert(Parcel parcel);
#Delete
void delete(Parcel parcel);
#Query("UPDATE parcel_table SET shippingDate=:shippingDate WHERE id = :id")
void updateShippingDate(String shippingDate, int id);
#Query("UPDATE parcel_table SET parcelStatus=:status WHERE id = :id")
void updatePackageStatus(Enums.ParcelStatus status, int id);
#Query("UPDATE parcel_table SET deliveryPersonName=:deliveryPersonName WHERE id = :id")
void updateDeliveryPersonName(String deliveryPersonName, int id);
#Query("UPDATE parcel_table SET possibleDeliveryPersonsList = :possibleList WHERE id = :tid")
void updatePossibleDeliveryPersonsList(List<String> possibleList, int tid);
#Query("DELETE FROM parcel_table")
void deleteAllParcels();
#Query("SELECT * from parcel_table")
LiveData<List<Parcel>> getParcels();
}
And this is part of the Parcel class:
#Entity(tableName = "parcel_table")
public class Parcel {
private Enums.ParcelType parcelType;
private boolean isFragile;
private Enums.ParcelWeight parcelWeight;
private LatLng warehouseLocation;
private String recipientName;
private LatLng recipientAddress;
private String recipientEmail;
private String recipientPhone;
private String dateReceived;
private String shippingDate;
private Enums.ParcelStatus parcelStatus;
private String deliveryPersonName;
private String fireBasePushId;
private List<String> possibleDeliveryPersonsList;
#PrimaryKey(autoGenerate = true)
#NonNull
private int id;
//and more...
}
The List<String> Type Converter:
#TypeConverter
public String listToString(List<String> list) {
String joined = TextUtils.join(", ", list);
return joined;
}
#TypeConverter
public List<String> stringToList(String string) {
List<String> myList = new ArrayList<String>(Arrays.asList(string.split(",")));
return myList;
}
I have no idea what to do, because the SQLite code is supposedly automatically generated by the Dao and I have no effect on it ...
Two more workarounds in addition to Bob Snyder' answer (but they need to be thoroughfully tested):
To "imitate" TypeConverter (from List to String) by yourself (it's a tricky thing, I've not tried it in practice!):
In DAO change the type of possibleList to String:
#Query("UPDATE parcel_table SET possibleDeliveryPersonsList = :possibleList WHERE id = :tid")
void updatePossibleDeliveryPersonsList(String possibleList, int tid);
add auxiliary method for conversion (you can place it at DAO as well):
void updatePossibleDeliveryPersonsList(List<String> possibleList, int tid) {
String listToString = TextUtils.join(", ", possibleList);
// copied from your converter, it could be put in some common function to follow DRY
updatePossibleDeliveryPersonsList(listToString, tid);
}
and call it from Repository/ViewModel:
db.ParcelDao().updatePossibleDeliveryPersonsList(possibleList, tid);
To replace your multiple updateXXX methods in DAO with single update (you have a lot of fields in your table, may be it would be better to try some universal way to update any combinations of them?):
#Update
void update(Parcel parcel);
Add to your DAO method for searching parcel by id:
#Query("SELECT * from parcel_table where id = :id")
Parcel getParcel(int id);
And in your Repository/ViewModel at first get Parcel, then change it (status, name whatever) and then update database:
Parcel parcel = db.ParcelDao().getParcel(id); // let's say it can't be null
parcel.shippingDate = yourShippingDate; // or change here any of your other fields, including list
db.ParcelDao().update(parcel);
The documentation for the Query annotation explains a feature of Room argument binding:
As an extension over SQLite bind arguments, Room supports binding a
list of parameters to the query. At runtime, Room will build the
correct query to have matching number of bind arguments depending on
the number of items in the method parameter.
This feature was likely intended to be used in the where-clause (as shown in the documentation example), but appears to be applied everywhere in the query statement.
In your case, the desired behavior is to have Room apply your type converter, but instead Room is ignoring the type converter and generating the special list binding.
I think you are going to have to work around this limitation of the current Room implementation. You might want to write a Room bug-report to get confirmation that the explanation provided here is correct.
One option for workaround that will use the type converters is to define this class:
public class DeliveryPersonsUpdate {
public int id;
public List<String> deliveryPersons;
public DeliveryPersonsUpdate(int id, List<String> deliveryPersons) {
this.id = id;
this.deliveryPersons = deliveryPersons;
}
}
then add this method to your Dao:
#Update(entity = Parcel.class)
void update(DeliveryPersonsUpdate update);
Example invocation:
db.ParcelDao().update(new DeliveryPersonsUpdate(id, personsList);

Room SQL query to retrieve entity key and sum of values

My database contains a list of these "Movement" class:
#PrimaryKey (autoGenerate = true)
private int NumeroDeOperacion;
private int FechaYear;
private int FechaMonth;
private int FechaDay;
private String TipoOperacion;
private String Category;
private String Notas;
private float Quantity;
In one of my queries I'd like to retrieve only Category and Quantity so I created another POJO object like this:
public class Category {
#ColumnInfo(name = "Category")
public String name;
#ColumnInfo(name = "Quantity")
public float quantity;
public Category(){}
}
What I'm triying to get is all the different Category in a specific year and the SUM of all Quantity.
So, let's say I have something like this:
Category Quantity
A 5
B 10
C 5
A 15
B 20
I'd like to get:
Category Quantity
A 20
B 30
C 5
I tried with this query but it is only getting the last entry of each Category:
#Query("SELECT DISTINCT Category, Quantity FROM OperacionesTable WHERE FechaYear = :year GROUP BY CategoryORDER BY Quantity DESC")
LiveData<List<Category>> getGastosCategoryTotalsInYear(int year);
Any help would be much appreciated.
Thanks in advance
To sum the quantities, you have to use SUM.
Also, there is no need for DISTINCT, since GROUP BY already takes care of that. My guess is that you added distinct, because there was an error. The error was there, because Quantity was neither in the group by columns, nor used in an aggregate function. This is typically an error (except in MySQL, which will just give you a random result).
Anyway, this should do the trick:
SELECT
Category,
SUM(Quantity) as TotalQuantity -- Column alias, to give the column a proper name
FROM
OperacionesTable
WHERE
FechaYear = :year
GROUP BY
Category
ORDER BY SUM(Quantity) DESC

Query type-converted list of objects in Room

I have a table that looks like following
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Product
{
#PrimaryKey
#ColumnInfo(name = "ID")
#JsonProperty("ID")
public int id;
#ColumnInfo(name = "Name")
#JsonProperty("Name")
public String name;
#ColumnInfo(name = "Documents")
#JsonProperty("Documents")
#TypeConverters(DocumentConverter.class)
public List<Document> documents;
}
//...
#TypeConverters(DocumentConverter.class)
#JsonIgnoreProperties( ignoreUnknown = true )
#JsonTypeName("Documents")
public class Document
{
#JsonProperty("Name")
public String name;
#JsonProperty("URL")
public String url;
}
I am able to retrieve a product based on its name by doing something like this
#Query("SELECT * FROM Product WHERE Name = :name")
List<Product> getProducts(String name);
And I would then be able to access the list of documents from each Product object. However I would also like to only deal with Products that has certain documents. I could get all Products via a query like above, then manually filter for the documents that I want, but it becomes quite a pain when I'm only looking for very specific documents.
Is it possible to also query based on Document variables without it being a separate table?
Something like...
#Query("SELECT * FROM Product WHERE Name = :name AND Document.name = :documentName")
List<Product> getProducts(String name, String documentName);
Thanks.
You could use LIKE sql statement to search inside your json column with converted documents list. Example:
Assume that we have document converted like this for storing in db:
{
name: "Title",
url: "Your_url"
}
So your query for product with such document in list should be like this:
SELECT * FROM Product WHERE Name = :name AND Documents LIKE :documentLikeExpr
Where
String documentLikeExpr = "%name: \"Title\"%";
% in expression mean zero, one or multiple characters.
So the only thing we are doing here - is searching for part of string inside column using SQL language features.
You cannot query a Document class variables as it is not stored as a separate table. #TypeConverter annotation converts your Document list to some predefined data types such as String. Basically it stores list of Document as a string Gson in a column of Product table, So we cannot access the field name of Document class in SQL query like Document.name.
Read the Option #2 given by #CommonsWare here
So, to access it you have to create a separate table for Document.

How to filter a nested relation in Room?

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));
}
}

Categories

Resources