android left join query with room - android

I am trying to change my sqlite database with room library. I am little confuse with left join query.
I have implemented it with sqlite, but don't know how can I achieve same withh room?
Here is my table creation:
first table: Notification
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_NAME ($COLUMN_ID INTEGER PRIMARY KEY, $ICON TEXT, $TITLE INTEGER," +
" $DATE INTEGER, $TYPE INTEGER,$URL TEXT, $MESSAGE INTEGER, FOREIGN KEY($TITLE) REFERENCES ${TableNotificationsTrans.getTableName(this)}(id)," +
"FOREIGN KEY($MESSAGE) REFERENCES ${TableNotificationsTrans.getTableName(this)}(id))")
second table: Notification_Trans
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_NAME ($COLUMN_ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, $COLUMN_EN TEXT, $COLUMN_GU TEXT, $COLUMN_HI TEXT)")
What I am doing is I am storing notification in notification table but its name and description will be stored with specific language, in notification_trans.
Query to achieve
DatabaseHelper.database!!.rawQuery("SELECT A.$COLUMN_ID, A.$ICON, N.${language.toLowerCase()} $TITLE, A.$DATE, A.$TYPE, A.$URL, M.${language.toLowerCase()} $MESSAGE FROM $TABLE_NAME A LEFT JOIN NotificationsTrans N ON A.$TITLE = N.id LEFT JOIN NotificationsTrans M ON A.$MESSAGE = M.id ORDER BY $DATE DESC LIMIT $pageNum*10, 10", null)
Question
How can I achieve same with room?
Edit
My application is multi-language application, where I am getting notification title with specific language, like Hindi or Gujarati. I am storing notification details in notification table, while title in notification_trans.
NotificationTrans have column with id, english, hindi, gujarati.
When user asked for gujarati, I am retriving notification title from notificationTrans's column gujarati.
I am able do so, in sqlite.
But now I want it with Room

First You have to make the model classes for both, You may have already declared them , You just need to make a few changes if they already exists.
#Entity
public class Notification {
#PrimaryKey
int id;
String icon;
#ForeignKey(entity = Notification_Trans.class, parentColumns = "col_id", childColumns = "id")
String title;
int date;
int type;
String url;
int msg;
}
#Entity
public class Notification_Trans {
#PrimaryKey(autoGenerate = true)
int col_id;
String column_en;
String column_gu;
String column_hi;
This makes for your POJO, I couldn't understand your Foreign key constraints, so Pardon me for that, you can make changes as you see fit.
You can Declare your DAO as per this`
#Dao
public interface DAO {
#Query("SELECT note.id, note.title, note.description, category.name as categoryName " +
"FROM note " +
"LEFT JOIN category ON note.category_id = category.id")
List getCategoryNotes();
}
`
I haven't made changes in the query, which I found at Link here. As your query was a complex one, But, it'll give you an Idea about how to do that.,
After this, You just need to access your Dao interface from your Database class object, which will handle the creation & all other things room, like this one below`
#Database(entities = {Notification.class, NotificationTrans.class}, version = 3)
public abstract class AppDatabase extends RoomDatabase {
private static AppDatabase instance;
public static AppDatabase getAppDatabase(Context context) {
if (instance == null) {
instance =
Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "database_name")
// allow queries on the main thread.
// Don't do this on a real app! See PersistenceBasicSample for an example.
//.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build();
}
return instance;
}
public static void destroyInstance() {
instance = null;
}
public abstract Dao notificationDao();
It helps creating a separate class for Database, & keeping track of object from it.
& you can access your data with AppDatabase.getAppDatabase(context).notificationDao().yourQueryMethodName();
You may require to refer to this to understand the relations between room, & implement your requirement,
EDIT 1:
Here's how your DAO should look like ,`
#Insert
void insert(Notifications object);
//This will insert a single Item
#Insert
void insertAll(Notifications... objects);
While this can enter a list of Data,
You can call this methods with your Database object, likeAppDatabase.getAppDatabase(context).notificationDao().yourQueryMethodName() here instead of yourQueryMethod(), if you call insert() & pass the object you need to store in the database, It'll do it,
For E.g.db.parcelDao().insert(parcel);
this is how I insert Data in my ParcelDao, db is Database object, & parcel is the object of data need to be stored. One more thing, you can't call this method on main thread, so you may need to use Handler or AsyncTask for the purpose, Sorry I forgot to mention that.
Have a look at Room Training at Android Developers for implementation of basic functionality of room

#1 - Need to create a model class that matches with the result of the query
data class ClientAndCity(
#ColumnInfo(name="id") val id: Long,
#ColumnInfo(name="client_name") val clientName: String?,
#ColumnInfo(name="city_name") val cityName: String?
)
#2 - Inside your DAO create your query
#Query("SELECT clients.id, clients.name AS client_name, cities.name AS city_name FROM clients LEFT JOIN cities ON cities.id = clients.city_id WHERE clientes.id = :clientId")
fun getClientAndHisCity(clientId: Long): ClientAndCity?
#3 - Use you function
CoroutineScope(Dispatchers.IO).launch{
val result: ClientAndCity = clientDAO.getClientAndHisCity(clientId)
//do something with it
}

Related

LiveData and room using inheritance

I have an app Im trying to write that has 3 objects that inherit from one object, all entities,
Im using room to store those locally.
In the dao of each entity I have "getAll" function that returnes a livedata<List>.
My question is,
is there a way, to get all of the lists from the database as one list (since they all inherit from the same class)?
Unless Im wrong, if I'll just use "getAll" on the superclass it wont give me the specific fields for every class.
and I have one recyclerView that holds those objects as 1 list so I need a way to combine them.
I tried looking it up but when it comes to inheritance its not really clear how Room handle stuff.(for example in the documentation google gives an example using inheritance with both objects having uniqe id's, but when i tried i got an error that the superclass id will be overwritten by the subclass id.).
If anyone could help, or provide a link to where i can learn more about it I'll greatly appriciate it.
Thanks, and have a great day!
Leaving this here in-case someone else needs it.
There are multiple ways to go about solving this one.
The first one is using a POJO as "MikeT" stated on his answer.
The second one is adding a "type" property to the superclass and get the whole
superclass list, and on the runtime select the proper object and create it.(using
the id since its the same).
the downside is that you access the db multiple times which can reduce
performance. (the solution I was going for before this morning)
The third way(that I ended using) is in this post answer by "Danail Alexiev"
Polymorphic entities in Room
creating a custom MediatorLiveData implementation that zipps the 2 (or more)
livedata objects and returns one.
I believe that you could use a POJO (or perhaps a suitable Entity if one exists) that includes ALL fields and utilise a getAll #Query that includes a UNION. Of course the better way is to perhaps reconsider the design.
The following is an example of a Parent (BaseObject) from which 2 Objects are inherited, name ChildType1 and ChildType2.
In this example a ChildType2 to has 2 additional fields one of which a ChildType1 has as it's only additional field. Hence a ChildType2 is suitable for holding all the fields of a ChildType1.
However, to enable a ChildType1 to be correctly extracted it has to mimic the additional field of the ChildType2. This can be done easily with the SQL in the getAll() method in the Dao Alldao.
The following is the code utilised:-
BaseObject from which the two ChildTypes inherit:-
class BaseObject {
#ColumnInfo(name = BaseColumns._ID)
Long id;
String name;
long createdTimestamp = System.currentTimeMillis() / 1000;
int type;
}
ChildType1 :-
#Entity(primaryKeys = {BaseColumns._ID})
class ChildType1 extends BaseObject {
public static final int TYPE = 1;
String ct1;
}
As will be seen the id column (_id) has been inherited and later that it causes no issues.
ChildType2 :-
#Entity(primaryKeys = {BaseColumns._ID})
class ChildType2 extends BaseObject {
public static final int TYPE = 2;
String ct1;
String ct2;
}
AllDao where All the Dao's have been coded :-
#Dao
interface AllDao {
#Insert
long insert(ChildType1 childType1);
#Insert
long insert(ChildType2 childType2);
#Query("SELECT *, 'n/a' AS ct2 FROM ChildType1 UNION SELECT * FROM childtype2")
List<ChildType2> getAll();
}
The query being using a UNION of initially the childtype1 table filling in the missing ct2 field with the value n/a and the childtype2 table. Note that id's will probably be duplicated so to utilise an id you would have to determine the respective type (e.g. is ct2 = n/a then it's probably a ChildType1 (hence why I'd string suggest an indicator of the type which cannot be ambiguous)).
The #Database TheDatabase :-
#Database(entities = {ChildType1.class,ChildType2.class},version = 1)
abstract class TheDatabase extends RoomDatabase {
abstract AllDao getAllDao();
private volatile static TheDatabase instance;
public static TheDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(
context,
TheDatabase.class,
"thedatabase.db"
)
.allowMainThreadQueries()
.build();
instance.getOpenHelper().getWritableDatabase();
}
return instance;
}
}
And finally an Activity, MainActivity putting it all together to demonstrate :-
public class MainActivity extends AppCompatActivity {
TheDatabase db;
AllDao dao;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
db = TheDatabase.getInstance(this);
dao = db.getAllDao();
ChildType1 c1_1 = new ChildType1();
c1_1.name = "CT1 001";
c1_1.ct1 = "This is a CT1 for CT1 001";
c1_1.type = ChildType1.TYPE;
dao.insert(c1_1);
ChildType2 c2_1 = new ChildType2();
c2_1.name = "CT2 002";
c2_1.ct1 = "This is CT1 for CT2 002";
c2_1.ct2 = "This is CT2 for CT2 002";
dao.insert(c2_1);
for(ChildType2 c: dao.getAll()) {
Log.d("" +
"TYPEINFO",
"Name = " + c.name +
"\n\t Created = " + c.createdTimestamp +
"\n\t ID = " + c.id +
"\n\t type = " + c.type +
"\n\t CT1 = " + c.ct1 +
"\n\t CT2 = " + c.ct2
);
}
}
}
Result
When run the log contains :-
D/TYPEINFO: Name = CT1 001
Created = 1626589554
ID = 1
type = 1
CT1 = This is a CT1 for CT1 001
CT2 = n/a
D/TYPEINFO: Name = CT2 002
Created = 1626589554
ID = 1
type = 0
CT1 = This is CT1 for CT2 002
CT2 = This is CT2 for CT2 002
i.e. both types of children have been extracted into a single List of objects that can contain ALL the fields.

How to access columns of child entity with query while using room relation?

I have two entity order and customer. Each order is associated with zero or single customer and each customer is associated with zero or many orders.
#Entity
public class Order {
#PrimaryKey(autoGenerate = true)
public long id;
public long customerId;
....
}
#Entity
public class Customer {
#PrimaryKey(autoGenerate = true)
public long id;
public String name;
}
I wanted to query the order table with the corresponding customer if it exists.
So, I have created the relation following the documentation.
public class OrderAndCustomer {
#Embedded public Order order;
#Relation(
parentColumn = "customerId",
entityColumn = "id"
)
public Customer customer;
}
I can query the list of orders and the corresponding customer using dao.
#Dao
public interface OrderDao {
#Transaction
#Query("select * from `order`")
List<OrderAndCustomer> getOrderAndCustomer();
}
But when I try to access the columns of child entity, I got compile error. For instance, I wanted to query the order where customer name is like ****.
Therefore, my updated query:
#Query("select * from `order` where customer.name like '****'")
Is it possible to access the attribute of child entity in where clause?
So, the question arises that how the relation works!? I have figure out that it first query the order entity and then query the customer entity. Let me show the correct way If I am wrong.
I have another solution to query multiple tables but I am unable to use the relation feature what room provides or I am missing something!
I can follow this answer to use join and mapping to object.
public class OrderAndCustomer extends Order {
public String customerName;
}
Query:
#Query("select `order`.*, customer.name as customerName from `order` left outer join customer on `order`.customerId = customer.id where customerName like '****'")
List<OrderAndCustomer> getOrderAndCustomer();
But, Still have questions.
How do I map all columns of the order and customer table? Do I need to map all columns of customer with as in the query or there is another simplified approach available? If both tables has more columns and I need all of the columns of the first and second table to be fetched, then my query will be long enough. I wonder if there is the simplest thing to map both tables without or minimal using of as with all columns of the second table.
Help me with the right approach and better available solution.

Android Room: How to return rows from a joined table

I have the following tables:
category:
id integer primary key autoincrement;
name text not null;
transaction:
id integer primary key autoincrement;
amount real not null;
category_id integer references category(id);
And have the following entity classes
class Category {
#PrimaryKey int cat_id;
String name;
}
class Transaction {
#PrimaryKey int tx_id;
double amount;
#ForeignKey(entity = Category.class, parentColumns = "category_id", childColumns = "cat_id")
int category_id;
#Embedded Category category;
}
When I run the following query, Transaction.category is always null
select t.* from transaction t JOIN category c ON t.category_id = c.cat_id
So far, most of the tutorials online don't show how to handle this situation. In fact, it also turns out room inserts the #Embedded fields.
How can I structure the entities to make sure a single query to retrieve transactions returns with related categories? The relationship is always one-to-one.
I'm using RxJava and do not want to do second queries. I would like to have a single query return everything because it's displayed in a ReycyclerView.
Thanks
Ok, so I found the answer from here: https://medium.com/androiddevelopers/database-relations-with-room-544ab95e4542 and https://developer.android.com/training/data-storage/room/relationships
So basically, the solution is to use the #Relation annotation , which can only be used on POJOs only as described here:
Please note this is for 1:1 relationships.
A one-to-one relationship between two entities is a relationship where
each instance of the parent entity corresponds to exactly one instance
of the child entity, and vice-versa.
For example, consider a music streaming app where the user has a
library of songs that they own. Each user has only one library, and
each library corresponds to exactly one user. Therefore, there should
be a one-to-one relationship between the User entity and the Library
entity.
First, create a class for each of your two entities. One of the
entities must include a variable that is a reference to the primary
key of the other entity.
#Entity
public class User {
#PrimaryKey public long userId;
public String name;
public int age;
}
#Entity
public class Library {
#PrimaryKey public long libraryId;
public long userOwnerId;
}
In order to query the list of users and corresponding libraries, you
must first model the one-to-one relationship between the two entities.
To do this, create a new data class where each instance holds an
instance of the parent entity and the corresponding instance of the
child entity. Add the #Relation annotation to the instance of the
child entity, with parentColumn set to the name of the primary key
column of the parent entity and entityColumn set to the name of the
column of the child entity that references the parent entity's primary
key.
public class UserAndLibrary {
#Embedded public User user;
#Relation(
parentColumn = "userId",
entityColumn = "userOwnerId"
)
public Library library;
}
Finally, add a method to the DAO class that returns all instances of
the data class that pairs the parent entity and the child entity. This
method requires Room to run two queries, so add the #Transaction
annotation to this method to ensure that the whole operation is
performed atomically.
#Transaction
#Query("SELECT * FROM User")
public List<UserAndLibrary> getUsersAndLibraries();

How do I perform a Room DAO multi table join #Query using select fields?

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

How to get specific column using Android Room

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.

Categories

Resources