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

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

Related

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 data modeling with embedded FKs

I'm trying to represent the following entities:
Warning: A warning entity with a unique id, title, subtitle, and up to 6 actions (below)
Action: An action entity with a unique id, text, icon, etc. relevant to zero or more warnings
Examples of this relationship would be:
Missile Lock warning with actions Evasive Maneuver, Release Chaff, and Deploy Flare.
Incoming gunfire warning with actions Evasive Maneuver, Return Fire, and GTFO
Here's what I have:
The Warning entity
#Entity(tableName = WARNING_TABLE,
indices = [Index(unique = true, value = [WARNING_TITLE, WARNING_SUBTITILE])]
)
data class Warning(
#field:PrimaryKey #ColumnInfo(name = WARNING_ID) var id: String,
#ColumnInfo(name = WARNING_TITLE) var text1: String,
#ColumnInfo(name = WARNING_SUBTITILE) var text2: String?,
#ColumnInfo(name = WARNING_ACTIVE, defaultValue = "0") var active: Boolean = false
)
The Action entity
#Entity(tableName = ACTION_TABLE,
indices = [
Index(unique = true, value = [ACTION_TEXT]),
Index(unique = true, value = [ACTION_ICON])
]
)
data class Action(
#field:PrimaryKey #ColumnInfo(name = ACTION_ID) var id: String,
#ColumnInfo(name = ACTION_TEXT) var text: String,
#ColumnInfo(name = ACTION_ICON) var icon: String,
#ColumnInfo(name = ACTION_ICON_SRC, defaultValue = "$DRAWABLE_SOURCE_RESOURCES") var iconSrc: DrawableSource? = DrawableSource.Resources
)
... and the table that links the two, the warning_action table:
#Entity(tableName = WARNING_ACTION_TABLE,
primaryKeys = [WARNING_ID, ACTION_ID],
foreignKeys = [
ForeignKey(entity = Warning::class,
parentColumns = [WARNING_ID],
childColumns = [WARNING_ID],
onUpdate = ForeignKey.NO_ACTION,
onDelete = ForeignKey.CASCADE),
ForeignKey(entity = Action::class,
parentColumns = [ACTION_ID],
childColumns = [ACTION_ID],
onUpdate = ForeignKey.NO_ACTION,
onDelete = ForeignKey.CASCADE)
],
indices = [Index(ACTION_ID), Index(WARNING_ID)]
)
data class WarningAction(#ColumnInfo(name = ACTION_ID) val action: String,
#ColumnInfo(name = WARNING_ID) val warning: String)
Now what I want to do is to be able to load a list of all Warnings and their associated Actions (or a Single warning and its associated actions).
This is what I have so far, but it isn't working:
data class WarningWithActions(#Embedded val warning: Warning,
#Relation(parentColumn = WARNING_ID,
entityColumn = ACTION_ID,
associateBy = Junction(WarningAction::class))
val actions: List<Action>){
val id get() = Warning.id
}
... and in the DAO:
#Query("SELECT * FROM '$WARNING_TABLE' WHERE $WARNING_ID = :id")
fun load (id:String): Maybe<WarningWithActions>
I also tried:
data class WarningWithActions(#Embedded val warning: Warning,
#Relation(parentColumn = WARNING_ID,
entity= Action::class,
entityColumn = ACTION_ID,
associateBy = Junction(value = WarningAction::class, parentColumn = WARNING_ID, entityColumn = ACTION_ID))
val actions: List<Action>){
val id get() = warning.id
}
I get an empty list of actions and I'm pretty sure I'm missing something that tells room how to get those, but I can't figure out what I'm missing here.
I've already used Database Inspector and verified that the actions exist, the warnings exist, and the link entries in the link table also exist.
UPDATE:
Turns out the above implementation is correct. The DAO method that was adding the action to the warning had a Single<Long> (RX) return value:
#Transaction
#Query ("INSERT OR REPLACE INTO '$WARNING_ACTION_TABLE' ($WARNING_ID, $ACTION_ID) VALUES (:wid, :aid)")
fun addAction (eid:String, aid:String):Single<Long>
I guess at some point while writing the unit test that tested this, I was distracted and forgot to subscribe()

Android Room cascade delete for a one to many relationship

Say I have two entities, Workout and Exercise and a one to many relationship exists between Workout (one) and Exercise (many). The entities are setup like this
Workout Entity:
#Entity(
tableName = "workouts",
indices = [Index("startDate")]
)
data class Workout(
#PrimaryKey
val startDate: String,
val workoutName: String
)
Exercise Entity:
#Entity
data class Exercise(
#PrimaryKey(autoGenerate = true)
val exerciseId: Long = 0,
val workoutId: String,
val name: String
)
Workout with Exercises:
#Entity(
foreignKeys = [ForeignKey(
entity = Workout::class,
parentColumns = arrayOf("startDate"),
childColumns = arrayOf("workoutId"),
onDelete = ForeignKey.CASCADE
)]
)
data class Exercise(
#PrimaryKey(autoGenerate = true)
val exerciseId: Long = 0,
val workoutId: String,
val name: String
)
This is how I get the exercises related to a workout:
#Transaction
#Query("SELECT * FROM workouts WHERE startDate = :startDate")
suspend fun getWorkoutWithExercises(startDate: String): WorkoutWithExercises
So my question is, if the workout instance containing exercises is deleted, will the related exercises also be deleted? If not, how would this be accomplished?
Thanks
The exercises will also be deleted as you have created a Foreign Key for the table Exercise.

Join 2 foreign key relationships into one object in query

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.

Get column from another entity using foreign keys in Room

I have two different entities. One has two references to the other one and I need to get a attribute of the reference.
my_main_table.primary_type is a foreign key of types._id and my_main_table.secondary_type is a foreign key of types._id that can be null.
Is a prepopulated database copied using RoomAsset library, so the scheme is already done in the database. Here is the diagram of the database:
Here is my main entity:
#Entity(
tableName = "my_main_table",
foreignKeys = [
ForeignKey(
entity = Type::class,
parentColumns = ["_id"],
childColumns = ["secondary_type"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
),
ForeignKey(
entity = Type::class,
parentColumns = ["_id"],
childColumns = ["primary_type"],
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
)
]
)
data class MainTable(
#PrimaryKey
#ColumnInfo(name = "_id", index = true)
val id: Int,
#ColumnInfo(name = "number")
val number: String,
#ColumnInfo(name = "name")
val name: String,
#ColumnInfo(name = "primary_type")
val primaryType: String,
#ColumnInfo(name = "secondary_type")
val secondaryType: String?
)
And here is my reference:
#Entity(tableName = "types")
data class Type(
#PrimaryKey
#ColumnInfo(name = "_id")
val id: Int,
#ColumnInfo(name = "name")
val name: String
)
Finally the SQL code for #Query:
SELECT p._id AS _id,
p.number AS number,
p.name AS name,
pt.name AS primary_type,
st.name AS secondary_type
FROM my_main_table p
INNER JOIN types pt ON p.primary_type == pt._id
LEFT JOIN types st ON p.secondary_type == st._id
What I want is to get the value of types.name throught the relation. But I can't figure out how. Should I need another method in my repository to get the value of the name?
Thanks.

Categories

Resources