Join 2 foreign key relationships into one object in query - android

Given
#Entity(
tableName = "state",
foreignKeys = arrayOf(
ForeignKey(entity = Foo::class, parentColumns = arrayOf("id"), childColumns = arrayOf("foo_id")),
ForeignKey(entity = Bar::class, parentColumns = arrayOf("id"), childColumns = arrayOf("bar_id"))
)
)
data class State(
#PrimaryKey(autoGenerate = true) val id: Long = 1
) {
#ColumnInfo(name = "foo_id")
var fooId: Long? = null
#ColumnInfo(name = "bar_id")
var barId: Long? = null
}
#Entity(tableName = "foo")
open class Foo(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
open val id: Long,
#ColumnInfo(name = "foo")
val foo: String?,
)
#Entity(tableName = "bar")
open class Bar(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
open val id: Long,
#ColumnInfo(name = "bar")
val bar: String?,
)
I am trying to create a Join POJO to store the results of the query:
class FooBar(
#Embedded
val foo: Foo,
#Embedded
val bar: Bar
)
And my failed attempt at a query:
#Query("SELECT foo.*, bar.* FROM foo, bar JOIN state ON foo.id == state.foo_id JOIN bar ON bar.id == session.bar_id ")
fun getFooBar(): LiveData<FooBar>
However I get an error when compiling. Do I need to deconflict the id fields in foo and bar since they are named the same?
I have tried with a prefix, no luck:
class FooBar(
#Embedded(prefix = "foo_")
val foo: Foo,
#Embedded(prefix = "bar_")
val bar: Bar
)
Any ideas?

Here is an example that might help you.
#Query("SELECT RoomArticle.*, RoomBranch.id AS 'RoomBranch_id', RoomBranch.name AS 'RoomBranch_name' "
Data entity:
public class RoomArticleOfBranch {
#Embedded
public RoomArticle article;
#Embedded(prefix = "RoomBranch_")
public RoomBranch branch;
Notice I provide prefix both in query and in #Embedded.

As you're trying Embedded try to change the query as follows
#Dao
interface TestDao {
#Query("SELECT foo.id as foo_id,bar.id as bar_id,bar.bar as bar_bar FROM foo, bar JOIN state ON foo_id = state.foo_id and bar_id = state.bar_id")
fun getFooBar(): LiveData<FooBar>
}
There are two things need to be fixed.
You need to rename the column name during the select query.
fixing your joining query.

Related

How to Query in a many to many relationship Room database?

I have a many to many relationship Room database with three tables:
First one :
data class Name(
#PrimaryKey(autoGenerate = true)
var nameId : Long = 0L,
#ColumnInfo(name = "name")
var name : String = "",
#ColumnInfo(name = "notes")
var notes: String=""
)
Second:
#Entity(tableName = "tags_table")
data class Tag(
#PrimaryKey(autoGenerate = true)
var tagId : Long = 0L,
#ColumnInfo(name = "tag_name")
var tagName : String = ""
)
Third:
#Entity(
tableName = "tagInName_table",
primaryKeys = ["nameId", "tagId"],
foreignKeys = [
ForeignKey(
entity = Name::class,
parentColumns = ["nameId"],
childColumns = ["nameId"]
),
ForeignKey(
entity = Tag::class,
parentColumns = ["tagId"],
childColumns = ["tagId"]
)
]
)
data class TagInName(
#ColumnInfo(name = "nameId")
var nameId: Long = 0L,
#ColumnInfo(name = "tagId")
var tagId: Long = 0L
)
The data class I use for a return object in a Query:
data class NameWithTags(
#Embedded
val name: Name,
#Relation(
parentColumn = "nameId",
entityColumn = "tagId",
associateBy = Junction(value = TagInName::class)
)
val listOfTag : List<Tag>
)
This is how I query to get all NamesWithTags:
#Query("SELECT * FROM names_table")
#Transaction
fun getNamesWithTags() : LiveData<List<NameWithTags>>
So the thing I need to do is, I need to Query to return LiveData<List<NameWithTags>> where every NamesWithTags has a list which contains the Tag ID that I Query for.
From my interpretation of what you say you need to do, then :-
#Transaction
#Query("SELECT names_table.* FROM names_table JOIN tagInName_table ON names_table.nameId = tagInName_table.nameId JOIN tags_table ON tagInName_table.tagId = tags_table.tagId WHERE tags_table.tagId=:tagId ")
fun getNameWithTagsByTagId(tagId: Long): LiveData<List<NamesWithTags>>
Note the above is in-principle code and has not been compiled or tested, so it may contain some errors.
A NameWithTags will contain ALL related tags whcih should be fine according to (where every NamesWithTags has a list which contains the Tag ID ), if you wanted just certain Tags in the List of Tags then it's a little more complex, this is explained in a recent answer at Android Room query with condition for nested object

room many to many error : columns returned by the query does not have the fields

i have a room database with two entities and am many to many relation
but get a query error
im getting an error when using android room
error: The columns returned by the query does not have the fields [nameOfWorkout,dateOfWorkout,caloriesBurntInWorkout,workoutId] in com.example.sporttracker.room.relations.WorkoutWithExercises even though they are annotated as non-null or primitive. Columns returned by the query: [amountExercise,caloriesBurntProExercise,exerciseName,exerciseId]
public abstract androidx.lifecycle.LiveData<java.util.List<com.example.sporttracker.room.relations.WorkoutWithExercises>> fetchWorkoutWithExercises(long id);
my entities:
#Entity(tableName = "exercise_table")
data class Exercise(
#NonNull
#ColumnInfo(name = "amountExercise")
var amountExercise: String,
#NonNull
#ColumnInfo(name = "caloriesBurntProExercise")
var caloriesBurntProExercise: String,
#NonNull
#ColumnInfo(name = "exerciseName")
var exerciseName: String,
#PrimaryKey(autoGenerate = true)
#NonNull
#ColumnInfo(name = "exerciseId")
var exerciseId: Long = 0L
#Entity(tableName = "workout_table")
data class Workout(
#NonNull
#ColumnInfo(name = "nameOfWorkout")
var nameOfWorkout: String,
#NonNull
#ColumnInfo(name = "dateOfWorkout")
var dateOfWorkout: String,
#NonNull
#ColumnInfo(name = "caloriesBurntInWorkout")
var caloriesBurntInWorkout: String,
#PrimaryKey(autoGenerate = true)
#NonNull
#ColumnInfo(name = "workoutId")
var workoutId: Long = 0L
my dao:
#Query("""
SELECT *
FROM exercise_table
WHERE exerciseId = :id
""")
fun fetchWorkoutWithExercises(id: Long): LiveData<List<WorkoutWithExercises>>
my relation:
data class WorkoutWithExercises(
#Embedded val workout: Workout,
#Relation(
parentColumn = "workoutId",
entityColumn = "exerciseId",
associateBy = Junction(ExerciseWorkoutCrossRef::class)
)
val exercises: List<Exercise>
)
// region: entity
#Entity(
primaryKeys = ["exerciseId", "workoutId"],
foreignKeys = [
ForeignKey(
entity = Exercise::class,
parentColumns = ["exerciseId"],
childColumns = ["exerciseId"]
),
ForeignKey(
entity = Workout::class,
parentColumns = ["workoutId"],
childColumns = ["workoutId"]
)
]
)
//endregion
// region: data class
data class ExerciseWorkoutCrossRef(
#ColumnInfo(name = "exerciseId")
val exerciseId: Long,
#ColumnInfo(name = "workoutId", index = true)
val workoutId: Long
)
//endregion
I believe that you can resolve this by amending WorkoutWithExercises to define the relationship between exercise_table and the ExerciseWorkoutCrossRef table.
Foreign keys only define the constraint (rule) not a relationship (even though one implicitly exists)).
I believe changing WorkoutWithExercises to be like the following code will move you along:-
data class WorkoutWithExercises(
#Embedded val workout: Workout,
#Relation(
entity = ExerciseWorkoutCrossRef::class,
parentColumn = "workoutId",
entityColumn = "workoutId"
)
val workoutwithexercises: List<WorkoutWithExercises>,
#Relation(
entity = Exercise::class,
entityColumn = "exerciseId",
parentColumn = "workoutid",
associateBy = (Junction(ExerciseWorkoutCrossRef::class))
)
val exercises: List<Exercise>
)
i.e. the first #Relation describes the relationship between the Workout and the ExerciseWorkoutCrossRef table
the second #Relation describes the relationship between the ExerciseWorkoutCrossRef and the exercise_table.
(so all three tables are related as opposed to 2)
I believe what was happing is that you were getting the rows from the exercise_table BUT this could not be linked (via the ExerciseWorkoutCrossRef table) to get the appropriate workout_table rows and thus columns and hence the compile message.

Android Room and nested relationships error

I have the following 3 entities:
#Entity(tableName = "PROPERTY",
indices = [
androidx.room.Index(value = ["id"], unique = true)
])
data class Property (
#Expose #PrimaryKey override val id: UUID = UUID.randomUUID(),
#SerializedName("type") #ColumnInfo(name = "type") val type: ItemPropertyType,
#SerializedName("code") #ColumnInfo(name = "code") val code: String,
#SerializedName("default_description") #ColumnInfo(name = "default_description") val defaultDescription: String?,
#SerializedName("description_id") #ColumnInfo(name = "description_id") val descriptionId: UUID?,
#SerializedName("default_property") #ColumnInfo(name = "default_property") val defaultProperty: Boolean,
#SerializedName("entity") #ColumnInfo(name = "entity") val entity: String = "ITEM"
)
#Entity(tableName = "PROPERTY_SET_DETAIL",
indices = [
Index(value = ["id"], unique = true),
Index(value = ["property_id"], unique = false)
],
foreignKeys = [
ForeignKey(entity = PropertySet::class, parentColumns = ["id"], childColumns = ["property_set_id"]),
ForeignKey(entity = Property::class, parentColumns = ["id"], childColumns = ["property_id"])])
data class PropertySetDetail (
#Expose #PrimaryKey override val id: UUID = UUID.randomUUID(),
#SerializedName("property_id") #ColumnInfo(name = "property_id") val propertyId: UUID,
#SerializedName("value") #ColumnInfo(name = "value") var value: String?,
#SerializedName("rank") #ColumnInfo(name = "rank") val rank: Int,
#SerializedName("property_set_id") #ColumnInfo(name = "property_set_id") val propertySetId: UUID
)
#Entity(tableName = "PROPERTY_SET",
indices = [Index(value = ["id"], unique = true), Index(value = ["properties_charset"], unique = false), Index(value = ["hashkey"], unique = false)])
data class PropertySet (
#Expose #PrimaryKey override val id: UUID = UUID.randomUUID(),
#SerializedName("item_id") #ColumnInfo(name = "item_id") val itemId: UUID,
#SerializedName("properties_charset") #ColumnInfo(name = "properties_charset") var propertiesCharset: String?,
#SerializedName("hashkey") #ColumnInfo(name = "hashkey") var hashkey: String?,
)
and also the following data classes that I need for the nested relationships among the 3 entities declared above:
data class PropertySetAndDetails(
#Embedded
val set: PropertySet,
#Relation(
entity = PropertySetDetail::class,
parentColumn = "id",
entityColumn = "property_set_id"
) val details: List<PropertyDetailAndProperty>,
)
data class PropertyDetailAndProperty(
#Embedded
val property: Property,
#Relation(
parentColumn = "id",
entityColumn = "property_id"
) val detail: PropertySetDetail
)
and the following DAO query function:
#Transaction
#Query("select * from property_set where item_id = :itemId")
suspend fun getSetsForItem(itemId: UUID): List<PropertySetAndDetails>
There is no way to make it work. I get the following build error:
error: There is a problem with the query: [SQLITE_ERROR] SQL error or
missing database (no such column: type)
I also declared the following convert functions for
ItemPropertyType:
#TypeConverter
fun toItemPropertyType(v: Byte?): ItemPropertyType {
return when (v) {
null -> ItemPropertyType.Unknown
else -> ItemPropertyType.getByValue(v)
}
}
#TypeConverter
fun fromItemPropertyType(t: ItemPropertyType?): Byte {
return when (t) {
null -> ItemPropertyType.Unknown.value
else -> t.value
}
}
Can someone explain me why this nested relationship doesn't work ? Thanks in advance.
Can someone explain me why this nested relationship doesn't work ?
I can't (let's call it Room's magic), but I can hint you how to make this work. Try to change your PropertyDetailAndProperty class to this:
data class PropertyDetailAndProperty(
#Embedded
val detail: PropertySetDetail,
#Relation(
parentColumn = "property_id",
entityColumn = "id"
) val property: Property
)
It seems nested class should contain entity you use in outer class ( PropertySetDetail in your case) beyond #Relation. Why? Well, I guess it just was developers' design.
Have you tried using the converter? Room cannot identify this data ("ItemPropertyType"). This is why you are getting this error.
Doc

Listing All data Based on foreign keys android room

I've googled my question and i can't find the answer yet.
I want to get all the meals that is in favorite, below is the code and what i have tried.
The result is that i only get a single Meal, even though there are 3 data in dbfavoritemeal.
The expected result is that i will get all the meals based on all the mealid in dbfavoritemeal.
Please guide me
I have a meal class
#Entity
data class DbMeal(
#PrimaryKey val id: Long,
val name: String,
val thumbnailUrl: String,
val category: String,
val instructions: String = "",
) {
And then i have favorite class
#Entity(
foreignKeys = [
ForeignKey(
entity = DbMeal::class,
parentColumns = ["id"],
childColumns = ["mealId"],
onDelete = ForeignKey.CASCADE
)],
indices = [Index(
value = ["mealId"],
unique = true
)]
)
data class DbFavoriteMeal(
#PrimaryKey
val mealId: Long
)
What i've tried is in DAO
#Query("select * from dbMeal where id = (select mealId from dbfavoritemeal)")
suspend fun getAllFavoriteDbMeal(): List<DbMeal>
You can change your DAO like
#Query("select * from dbMeal where id in (select mealId from dbfavoritemeal)")
suspend fun getAllFavoriteDbMeal(): List<DbMeal>
or you can add isFavorite parameter to your Entity.
#Entity
data class DbMeal(
#PrimaryKey val id: Long,
val name: String,
val thumbnailUrl: String,
val category: String,
val instructions: String = "",
val isFavorite: Boolean = false,
)
And your DAO should look like
#Query("select * from dbMeal where isFavorite = 1")
suspend fun getAllFavoriteDbMeal(): List<DbMeal>

many-to-many relation in Room database. Kotlin Junction class ignored

I have the following Kotlin entities for Room ver. 2.2.5.
PARENT ENTITY
#Entity(tableName = "ITEM",
indices = [Index(value = ["id"], unique = true), Index(value = ["code"], unique = true), Index(value = ["status"], unique = false)]
)
data class Item (
#PrimaryKey #ColumnInfo(name = "id") override val id: UUID = UUID.randomUUID(),
#ColumnInfo(name = "code") #NotNull val code: String,
#ColumnInfo(name = "valid") val valid: Boolean = true,
#ColumnInfo(name = "value") val value: Double?,
#ColumnInfo(name = "price") val price: Double?,
#ColumnInfo(name = "default_description") val defaultDescription: String?,
#ColumnInfo(name = "description") val description: String?
)
CHILD ENTITY
#Entity(tableName = "LOCATION",
indices = [Index(value = ["id"], unique = true), Index(value = ["code"], unique = true)]
)
data class Location (
#ColumnInfo(name = "id") #PrimaryKey override val id: UUID = UUID.randomUUID(),
#ColumnInfo(name = "code") val code: String,
#ColumnInfo(name = "latitude") val latitude: Double?,
#ColumnInfo(name = "longitude") val longitude: Double?,
#ColumnInfo(name = "default_description") val defaultDescription: String?
)
JUNCTION ENTITY
#Entity(
tableName = "ITEM_LOCATION_L",
primaryKeys = [
"item_id", "location_id"
],
foreignKeys = [
ForeignKey(entity = Item::class, parentColumns = ["id"], childColumns = ["item_id"]),
ForeignKey(entity = Location::class, parentColumns = ["id"], childColumns = ["location_id"])
],
indices = [
Index("id"),
Index("item_id"),
Index("location_id")])
data class ItemLocationLink (
#ColumnInfo(name = "id") override val id: UUID = UUID.randomUUID(),
#ColumnInfo(name = "item_id") val itemId: UUID, /** Item ID - parent entity */
#ColumnInfo(name = "location_id") val locationId: UUID, /** Location ID - child entity */
#ColumnInfo(name = "quantity") val quantity: Double /** Quantity of the item in the referenced location */
)
RESULT CLASS
class ItemLocationRelation {
#Embedded
lateinit var item: Item
#Relation(
parentColumn = "id",
entityColumn = "id",
associateBy = Junction(value = ItemLocationLink::class, parentColumn = "item_id", entityColumn = "location_id")
) lateinit var locations: List<Location>
}
DAO INTERFACE
#Dao
interface ItemLocationLinkDao {
#Transaction
#Query("SELECT * FROM ITEM WHERE id = :itemId")
fun getLocationsForItem(itemId: UUID): List<ItemLocationRelation>
}
DATABASE TYPE CONVERTER
class DBTypesConverter {
#TypeConverter
fun fromUUID(uid: UUID?): String? {
if (uid != null)
return uid.toString()
return null
}
#TypeConverter
fun toUUID(str: String?): UUID? {
if (str != null) {
return UUID.fromString(str)
}
return null
}
#TypeConverter
fun fromDate(d: Date?): Long? {
return d?.time
}
#TypeConverter
fun toDate(l: Long?): Date? {
return if (l != null) Date(l) else null
}
}
When I call getLocationsForItem I get in return an instance of ItemLocationRelation with a valid Item but no child objects. I've checked the generated code and there is no sign of the Junction class. The generated code behaves like it is not a many-to-many relation, the Junction class is completely ignored, I can even specify a fake class in the #Junction attribute of the relation and the result would be exactly the same without errors.
If I add a function to the DAO class that returns the results of the following query:
select * from item
inner join item_location_l as link on link.item_id = item.id
inner join LOCATION as l on link.location_id = l.id
where item.id = '99a3a64f-b0e6-e911-806a-68ecc5bcbe06'
I get 2 rows as expected. So the SQLite database is ok. Please help.
So, in the end the problem was really subtle. For reasons I cannot explain the project I was working on had the following gradle settings (app gradle):
kapt "android.arch.persistence.room:compiler:2.2.5"
I replaced it with
kapt "androidx.room:room-compiler:2.2.5"
And then everything was fine. The hard-coded query generated by the plugin now contains the JOIN and it works....

Categories

Resources