Deconflict column names in "join" entity - android

I have 2 entities:
#Entity(tableName = "author")
data class Author(
#PrimaryKey
#ColumnInfo(name = "id")
val id: String,
#ColumnInfo(name = "name")
val name: String
)
data class Book(
#ColumnInfo(name = "id")
val id: String,
#ColumnInfo(name = "title")
val title: String,
#ColumnInfo(name = "author_id")
var authorId: String
)
And I would like to join them in a query:
#Query("SELECT * FROM book JOIN author ON author.id = book.author_id AND author.id = :authorId WHERE book.id = :bookId")
fun item(authorId: String, bookId: String): LiveData<BookWithAuthor>
Into this entity:
#Entity
data class BookWithAuthor(
#Relation(parentColumn = "author_id", entityColumn = "id")
val author: Author,
#Embedded
val book: Book
)
However when I do that I get back a BookWithAuthor object in which the author.id and book.id are the same id, in this case they are both the author's id. How do I deconflict the "id" property in the entities in the "join" object?

You can use the #Embedded's prefix to disambiguate the names.
e.g. use :-
#Embedded(prefix="book_")
val book: Book
along with :-
#Query("SELECT author.*, book.id AS book_id, book.title AS book_title, book.author_id AS book_author_id FROM book JOIN author ON author.id = book.author_id AND author.id = :authorId WHERE book.id = :bookId")
Note the above is in-principle code, it has not been tested or run.
You would then change BookWithAuthor to use the prefixed column so :-
#Entity /* not an Entity i.e. Entity = table this just needs to be a POJO */
data class BookWithAuthor(
#Embedded(prefix = "book_")
val book: Book,
/* with (makes more sense to have parent followed by child) */
#Relation(/*entity = Author::class,*/ parentColumn = "book_author_id", entityColumn = "id")
val author: Author
)
However, your comment it assumes that all book ids are unique. In my case I could potentially have duplicate book ids for different authors.
appears to not fit in with the table/entities (schema) you have coded. i.e.
Author entity is fine.
Book though does not have #Entity annotation, nor does it have the obligatory #PrimaryKey annotation if it is an Entity defined in the entities=[....] lis. The assumption made is that the id is/would be the primary key and annotated accordingly and thus unique.
BookWithAuthor You will see that BookWithAuthor has been commented with Not an Entity (table). You cannot have the #Relationship annotation in an Entity that is defined as an Entity to the database (i.e. one of the classes in the entities=[....] list of the #Database annotation).
So unless the primary key of the Book entity/table is not the id or that the authorid is a list of authors then a Book can have only one author. As such it would appear that you only need #Query("SELECT * FROM book WHERE id=:bookId"): LiveData<BookWithAuthor>
if not then coding #Relation will basically ignore your JOIN and select ALL authors but then only pick the first which would be an arbitrary author to complete the author. That is #Relation works by obtaining the parent(s) and then build it's own underlying query to access ALL children of the parent. So whatever Query you supply it ONLY uses this to ascertain the parents.
I suspect that what you want is that a book can have a number of authors and that an author can be an author of a number of books. In this scenario you would typically use a mapping table (can be called other names such as link, reference, associative .... ). If this is the case and you can't ascertain how to create the mapping table via room, then you could ask another question in that regard.

I think the problem here is defining the relation.
My understanding is this is a one to many relationship: One Author (parent) has zero or more Books (entities).
What your #Relation defines is a 1:1 relationship.
If what you want eventually is a BookWithAuthor, why not embedd the Author in Book directly? You would then have the following tables:
#Entity(tableName = "author")
data class Author(
#PrimaryKey
#ColumnInfo(name = "author_id")
val id: String,
#ColumnInfo(name = "name")
val name: String
)
#Entity(tableName = "BookWithAuthor")
data class Book(
#PrimaryKey
#ColumnInfo(name = "id")
val id: String,
#ColumnInfo(name = "book_id")
val id: String,
#ColumnInfo(name = "title")
val title: String,
#Embedded
val author: Author
)
And your query can look like this:
#Query("SELECT * FROM BookWithAuthor WHERE book_id = :bookId AND author_id = :authorId")
fun item(authorId: String, bookId: String): LiveData<BookWithAuthor>
After embedding, Book takes the same columns of Author with their exact names. So we need to at least rename either of the id columns to resolve ambuiguity.
Since book ids can be duplicate, we need to introduce a new column as the PrimaryKey for Book.

Related

Room database query for several Entities

Could you please help me? I try to get data from TMDB via Retrofit2 to Room database and display it in Recyclerview. I make 3 api calls to get data about popular movie id/poster/overview/title/genres, with the second call I get movie duration by movie id, and with the 3rd call I get movie cast by movie id.
#GET("movie/popular")
suspend fun getPopularMovies(#Query("api_key") apiKey: String?): PopularResponseModel
#GET("/movie/{movie_id}")
suspend fun getMovieDuration(#Query("api_key") apiKey: String?): MovieDuration
#GET("/movie/{movie_id}/credits")
suspend fun getCrewAndCast(#Query("api_key") apiKey: String?): Cast
And I get 3 models:
#Entity(tableName = "movie")
data class MovieResponse(
#PrimaryKey(autoGenerate = false)
#ColumnInfo(name = "movie_id")
val id: Int,
#ColumnInfo(name = "movie_image")
val poster_path: String,
#ColumnInfo(name = "movie_overview")
val overview: String,
#ColumnInfo(name = "movie_title")
val title: String,
#ColumnInfo(name = "movie_genres")
val genre_ids: ArrayList<Int>)
#Entity(tableName = "movie_duration")
data class MovieDuration(
#PrimaryKey(autoGenerate = false)
#ColumnInfo(name = "movie_id")
val id: Int,
#ColumnInfo(name = "duration")
val runtime: Int
)
#Entity(tableName = "cast")
data class Cast(
#PrimaryKey(autoGenerate = false)
#ColumnInfo(name = "movie_id")
val id : Int,
val cast :ArrayList<Cast>
)
The first question is how do I make a #Query request (in the #Dao interface) that will help me to display the popular movie name, title, genre + movie duration associated with that movie in a Recyclerview. I guess they should be mapped somehow by movie id...
For example (John Wick - Action/crime, 2h 10m).
The second question is how to make a #Query request that will help me to associate the exact movie I click on with the movie cast?
(movie John Wick - John Wick: Keanu Reeves...)
The first question is how do I make a #Query request (in the #Dao interface) that will help me to display the popular movie name, title, genre + movie duration associated with that movie in a Recyclerview. I guess they should be mapped somehow by movie id...
As there is a one to one relationship between movie and it's duration then you could simplify matters by including the duration field in the movie. For example instead of MovieResponse and MovieDuration have just MovieResponse as:-
#Entity(tableName = "movie")
data class MovieResponse(
#PrimaryKey(autoGenerate = false)
#ColumnInfo(name = "movie_id")
val id: Int,
#ColumnInfo(name = "movie_image")
val poster_path: String,
#ColumnInfo(name = "movie_overview")
val overview: String,
#ColumnInfo(name = "movie_title")
val title: String,
#ColumnInfo(name = "movie_genres")
val genre_ids: ArrayList<Int>,
#ColumnInfo(name = "duration")
val runtime: Int
)
Otherwise you would typically have a POJO class that reflects the two clasess/entities/tables that has two fields; one a MovieResponse and the other a MovieDuration.
e.g.
Option 1 (via #Embedded annotation):-
data class MovieWithEmbeddedDuration(
#Embedded
val movieResponse: MovieResponse,
#Embedded(prefix = "duration_") /* prefix required to disambiguate movie_id column that is in both */
val movieDuration: MovieDuration
)
This would require the use of a JOIN such as #Query("SELECT movie.*, movie_duration.movie_id AS duration_movie_id, movie_duration.duration AS duration_duration FROM movie JOIN movie_duration ON movie.movie_id = movie_duration.movie_id") fun getMoviesWithEmbeddedDuration(): List<MovieWithEmbeddedDuration>
note the complexity due to the need to disambiguate the movie_id column which appears in both tables. AS is used to alter the name of the column when it is output (much easier to have unique column names, as the movie_id is a reference/map/relation to the parent perhaps name the column movie_id_map which better describes the column's usage).
Option 2 (via #Relation annotation)
data class MovieWithRelatedDuration(
#Embedded
val movieResponse: MovieResponse,
#Relation(
entity = MovieDuration::class,
parentColumn = "movie_id",
entityColumn = "movie_id"
)
val movieDuration: MovieDuration
)
this doesn't require the join you simple use a query to get the parent(s) BUT get the POJO with #relation e.g. #Transaction #Query("SELECT * FROM movie") fun getMoviesWithRelatedDuration(): List<MovieWithRelatedDuration>
Here #Relation does the work BUT it works by running separate queries to get the children (mimicking the JOIN), hence why the #Transaction annotation (not needed but warned if omitted). Thus this is less efficient.
Option 2 (via field for the duration)
Instead of embedding the MovieDuration object, you could extract just the duration and have a field for that e.g.
data class MovieWithDurationAsField(
#Embedded
val movieResponse: MovieResponse,
val duration: Int
)
This could be used using #Query("SELECT movie.*,duration FROM movie JOIN movie_duration ON movie_duration.movie_id = movie.movie_id") fun getMoviesWithDurationAsField(): List<MovieWithDurationAsField>
A JOIN is used so this is efficient not that the value for the field is determined according to the column that matches it's name. So if runtime were used then you'd have to rename the output column from duration to runtime.
The second question is how to make a #Query request that will help me to associate the exact movie I click on with the movie cast?
This has basically been explained above (see Option 2). Create the POJO with the parent #Embedded and the children with #Relation annotation and the val being a List/Array of the child objects rather than a single object.
e.g. something like:-
data class MovieWithCastList(
#Embedded
val movieResponse: MovieResponse,
#Relation(
entity = Cast::class,
parentColumn = "movie_id",
entityColumn = "movie_id"
)
val castList: List<Cast>
)
The Query/function would be along the lines of #Query("SELECT * FROM movie") fun getMovieWithCastList(): List<MovieWithCastList>
Note obviously with Retrofit you adapt the queries accordingly.

Saving Multiple Nested lists into room database

I'm working on dictionnary application in which the api request have many nested lists , i have tried to insert all the nested lists but i'm getting different error each time , i would like to know what is the best way to save multiple nested lists , should i use room relations or something else , thank you in advance for help , i m really stuck with this for few days now
This is sample schema of how are the lists nested
This is the parent list
#Entity(tableName = "DICTIONNARYTABLE")
#TypeConverters(DictionnaryModelConverter::class)
class DictionnaryModel : ArrayList<DictionnaryModelItem>() {
#PrimaryKey(autoGenerate = true)
#NotNull
val wordId: Long = 0
}
The parent list has two lists as well
#Entity
data class DictionnaryModelItem(
#PrimaryKey val dictionnaryModelId: Long = 0,
#TypeConverters(DictionnaryMeaningConverter::class)
val meanings: MutableList<Meaning>,
#TypeConverters(DictionnaryPhoneticsConverter::class)
val phonetics: MutableList<Phonetic>,
val word: String
)
//---------------------------
#Entity
data class Meaning(
#PrimaryKey val meaningId: Long = 0,
#TypeConverters(DictionnaryDefinitionConverter::class)
val definitions: List<Definition>,
val partOfSpeech: String
)
///-------------------------------
#Entity
data class Phonetic(
#PrimaryKey val phoneticId: Long = 0,
val audio: String,
val text: String
)
inside the meaning , i also have definition which another model
#Entity
data class Definition(
#PrimaryKey val definitionId: Long = 0,
val definition: String,
val example: String,
#TypeConverters(DictionnarySynonymsConverter::class)
val synonyms: List<String>
)
You need to create one-to-many relationship data model here. For instance each dictionary word has many meanings and many phonetics. Here Dictionary is a parent entity and Meaning and Phonetic are the child entities. Each child entity will have it's parent entity primary key stored in its table. You will need another data class to define this relationship.
data class DictionaryWithMeanings(
#Embedded val dictionary: Dictionary,
#Relation(
parentColumn = "dictionaryModelId",
entityColumn = "dictionaryId"
)
val meanings: List<Meaning>
)
Meaning table has to store dictionaryId as foreign key its table. Same has to be defined for phonetics. And Meaning table again has similar relationship with Definition and so on.

Many to many android room ref extra variable

I have a Many to Many relationship set up on my Room database denoted by the following diagram:
eveything works great but I would like to make the following addition:
My question is how do I go about having RecipeWithIngredients get this unit:String variable from the RecipeIngredientRef Entity when a recipeWithIngredients is constructed on a query?
#Entity(indices = [Index(value = ["name"],unique = true)])
data class Recipe(
var name: String,
val image: String?
) {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "recipeID")
var ID: Long = 0
}
#Entity(indices = [Index(value = ["name"],unique = true)])
data class Ingredient(
val name: String,
val image: String?,
val amount: Int
) {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "ingredientID")
var ID: Long = 0
}
#Entity(primaryKeys = ["recipeID", "ingredientID"])
data class RecipeIngredientRef(
val recipeID: Long,
val ingredientID: Long,
val unit: String
)
data class RecipeWithIngredients(
#Embedded
val recipe: Recipe,
#Relation(
parentColumn = "recipeID",
entity = Ingredient::class,
entityColumn = "ingredientID",
associateBy = Junction(
value = RecipeIngredientRef::class,
parentColumn = "recipeID",
entityColumn = "ingredientID"
)
)
val ingredients: List<Ingredient>
)
As far as I know there is no built-in way using current many-to-many Relations in Room for that. So I just want to save your time for researches.
This RecipeIngredientRef table is used internally just for two other tables' join and for now adding there additional fields will not help to get these fields with #Relations/Junction mechanism.
You can try workarounds, but they are no so elegant and they needed to dig deeper into Room's result's processing:
Don't use #Relation/Junction mechanism for RecipeWithIngridients at all, write your own query with two JOINs (since you should get data from 3 tables). As a result you'll get some raw set of records and then you should use loops and methods of Kotlin collections to turn result into needed form.
Use #Relation/Junction mechanism, but after getting result - make additional processing of result - make one more query to RecipeIngredientRef table and set missing unit value manually (again with loops and methods of Kotlin collections).

How to store a list of integers in Room database so that it can be easily queried later?

I'm storing podcast data in a Room database, where each podcast has a List<Int> called genreIds. I'd like to be able to store this in such a way that I can easily query it later by doing something like SELECT * FROM podcasts WHERE genreIds CONTAINS :genre, or whatever the command would be.
So what is the best way to store that list of Ints so that it can be easily queried later, and how would I do that using Room? I've used TypeConverters before, but that converts it to a string, which is difficult to query, so I'd like to be able to link it to another table or something that can be easily queried, I'm just not sure how to do that.
Thanks in advance.
The data stored on a the db with Room, depends on the data class you use. If you specify a data class with an Int member, that will be an Int on the db.
Example:
data class TrackPlayedDB (
#PrimaryKey(autoGenerate = true)
val _id: Int = 0,
val timesPlayed: Int,
val artist: String,
val trackTitle: String,
val playDateTime: LocalDateTime
)
here timesPlayed will be an Int on the DB (as _id). You'll specify your data classes like the following, this will build the corresponding tables.
#Database(entities = [TrackPlayedDB::class], version = 1, exportSchema = false)
#TypeConverters(Converters::class)
abstract class MyRoomDatabase : BaseRoomDatabase() {
Edit: Following author's comment, I stand corrected i didn't get the question right.
Author actually asks how to store a List<Int> as field on a table. There are 2 solutions to do that: one, as Author suggests, is to store the List as String and use Like keyword to write queries with a clause like the following:
SELECT * FROM mytable
WHERE column1 LIKE '%word1%'
OR column1 LIKE '%word2%'
OR column1 LIKE '%word3%'
as a simple search on SO would have shown: SQL SELECT WHERE field contains words
The Author says he used TypeConverters so i'll skip how to convert a List<Int> into a string
The other solution to this problem is to realise that nothing was understood about the theory of Transactional Databases. In fact, when you have a many-to-many relationship, as in the case of podcast and genre, theory dictates that you build a table that links the ids of podcasts and the ids of genres, as it is explained here: https://dzone.com/articles/how-to-handle-a-many-to-many-relationship-in-datab
and other countless books, videos and blogs.
This benefits the db with added clarity, performance and scalability.
Bottom line, Author's db design is wrong.
I found [this article on Medium][1] that I found very helpful. What I'm trying to do is a many to many relationship, which in this case would be done something like the following:
Podcast class:
#Entity(tableName = "podcasts")
data class Podcast(
#PrimaryKey
#ColumnInfo(name = "podcast_id")
val id: String,
// other fields
}
Genre class:
#Entity(tableName = "genres")
data class Genre (
#PrimaryKey
#ColumnInfo(name = "genre_id")
val id: Int,
val name: String,
val parent_id: Int
)
PodcastDetails class:
data class PodcastDetails (
#Embedded
val podcast: Podcast,
#Relation(
parentColumn = "podcast_id",
entityColumn = "genre_id",
associateBy = Junction(PodcastGenreCrossRef::class)
)
val genres: List<Genre>
)
PodcastGenreCrossRef:
#Entity(primaryKeys = ["podcast_id", "genre_id"])
data class PodcastGenreCrossRef (
val podcast_id: Int,
val genre_id: Int
)
And access it in the DAO like this:
#Transaction
#Query(SELECT * FROM podcasts)
fun getPodcastsWithGenre() : List<PodcastDetails>
[1]: https://medium.com/androiddevelopers/database-relations-with-room-544ab95e4542

Android Room [SQLITE_ERROR] SQL error or missing database when using Relation and Junction

this is my first question here.
I'm building an app using Room database and I tried following this tutorial because I need to implement a many-to-many relation.
However I keep getting the following error as soon as I try to build the app:
error: There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such table: OwnerDogCrossRef)
private final java.util.List dogs= null;
My entities, with the cross reference data class:
#Entity(tableName = "owner_table")
data class Owner(
#ColumnInfo(name = "owner_id")
val id: String,
#PrimaryKey
#ColumnInfo(name = "owner_name", index = true)
val name: String,
// some other columns
#Entity(tableName = "dog_table")
data class Dog(
#PrimaryKey
#ColumnInfo(name = "dog_name")
val name: String
// some other columns
#Entity(primaryKeys = ["owner_name", "dog_name"])//, "move_learned_by"])
data class OwnerDogCrossRef(
val owner_name: String,
#ColumnInfo(index = true)
val dog_name: String
// some other columns
My junction data class:
data class OwnerWithDogs(
#Embedded val owner: Owner,
#Relation(
parentColumn = "owner_name",
entityColumn = "dog_name",
associateBy = Junction(OwnerDogCrossRef::class)
)
val dogs: List<Dog>
)
My DAO:
#Dao
inteface OwnerDao {
#Transaction
#Query("SELECT * FROM owner_table WHERE owner_name = :name")
fun getOwnerWithDogs(name: String): LiveData<List<OwnerWithDogs>>
}
I also added the OwnerDogCrossRef to my database as below:
#Database(
entities = [Owner::class, Dog::class, OwnerDogCrossRef::class],
version = 2,
exportSchema = false
)
#TypeConverters(Converters::class)
abstract class MainDatabase : RoomDatabase() {
//some logic
}
Thank you for your help
Go to your Database.kt file and make sure the cross-reference table has been included in the list of entities there. Yours may differ to the example below, but I hope you can see what you may have missed.
#Database(entities = [Owner::class, Dog::class, OwnerDogCrossRef::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
...
}
This is because the Owner, Dog, and OwnerDogCrossRef are all tables that the database needs to know about, whereas OwnerWithDogs is merely going to join the relevant tables in a transaction, as multiple queries will be run, as per documentation. See also here for the database documentation showing how to include the entities for a particular database.
data class OwnerWithDogs(
#Embedded val owner: Owner,
#Relation(
parentColumn = "owner_name",
entity = Dog.class,
entityColumn = "dog_name",
associateBy = #Junction(
value=OwnerDogCrossRef.class,
parentColumn = "owner_name",//variable name in your OwnerDogCrossRef
entityColumn = "dog_name")
)
val dogs: List<Dog>
)
Try this code.
For further ideas ,try this link
https://developer.android.com/reference/androidx/room/Junction

Categories

Resources