Get column from another entity using foreign keys in Room - android

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.

Related

How to use onDelete = RESTRICT in Room?

To prevent the deletion of a parent row which has one or more related child rows in my Room database, I've set my ForeignKey onDelete method to RESTRICT.
My database has two tables: products and document_products which has the ForeignKey on products, during the application usage, the user is able to delete all items from the products table but I need to still keep the items in document_products but the RESTRICT seems not to be working as even with it I'm getting:
FOREIGN KEY constraint failed (code 1811 SQLITE_CONSTRAINT_TRIGGER)
My DocumentProduct entity looks like this:
#JsonClass(generateAdapter = true)
#Entity(
tableName = "document_products",
foreignKeys = [
ForeignKey(
entity = Document::class,
parentColumns = ["id"],
childColumns = ["documentId"],
onDelete = CASCADE
),
ForeignKey(
entity = Product::class,
parentColumns = ["products_id"],
childColumns = ["document_products_productIdMap"],
onDelete = RESTRICT
)
],
indices = [Index("document_products_productIdMap"), Index("documentId"), Index(
value = ["document_products_productIdMap", "documentId", "labelType"],
unique = true
)]
)
data class DocumentProduct(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "document_products_id")
var id: Long,
#ColumnInfo(name = "document_products_productIdMap")
var productId: String,
#ColumnInfo(name = "document_products_quantity")
var quantity: Float,
var orderQuantity: Float,
#ColumnInfo(name = "document_products_purchase")
var purchase: Float,
var documentId: Long,
var labelType: String?,
var timestamp: Long?
)
While Product:
#Entity(tableName = "products")
open class Product(
#PrimaryKey(autoGenerate = false)
#ColumnInfo(name = "products_id")
open var id: String,
open var description: String?,
#ColumnInfo(defaultValue = "PZ")
open var unitOfMeasure: String,
#ColumnInfo(name = "products_purchase")
open var purchase: Float,
open var price: Float,
#ColumnInfo(name = "products_quantity")
open var quantity: Float
)
And in the application settings the user is able to run the following query from ProductDAO:
#Query("DELETE FROM products")
suspend fun deleteAll(): Int
What I'm looking for is a solution in which I can keep the parent rows which has one or more related child rows OR where I can keep the ForeignKey without a real relation.
RESTRICT is working as intended, it is not meant to exit quietly and leave things as before. Rather it is used to immediately exit rather than at the end of the current statement as per:-
RESTRICT: The "RESTRICT" action means that the application is prohibited from deleting (for ON DELETE RESTRICT) or modifying (for ON UPDATE RESTRICT) a parent key when there exists one or more child keys mapped to it. The difference between the effect of a RESTRICT action and normal foreign key constraint enforcement is that the RESTRICT action processing happens as soon as the field is updated - not at the end of the current statement as it would with an immediate constraint, or at the end of the current transaction as it would with a deferred constraint. Even if the foreign key constraint it is attached to is deferred, configuring a RESTRICT action causes SQLite to return an error immediately if a parent key with dependent child keys is deleted or modified.
https://www.sqlite.org/foreignkeys.html#fk_actions
You could simply not use Foreign Keys they are not mandatory for a relationship to exist. They are to enforce referential integrity.
An alternative approach with referential integrity would be to have the Products and DocumentsProducts independent relationships wise (i.e. drop the Foregin Keys and the productId column) and to then have an table for any relationships this catering for a many-many relationship between Products and DocumentProducts (which inherently supports 1-many and 1-1).
Such a table (a mapping table/crossref table/associative table ....) would have 2 columns one for the reference/map/association with the Product, the other for the DocumentProduct. You could have 2 Foreign Keys and also you could CASCADE for when a deletion happens.
The Delete (and update if coded) would CASCADE to this table not to the Product or the DocumentProduct, thus just removing cross reference between the two.
The Primary Key would be a composite of the two columns, you would have to use the primaryKey parameter of the #Entity annotation to define this.
The following code is along the lines of what would suit:-
#Entity(
tableName = "document_products",/*
foreignKeys = [
ForeignKey(
entity = Document::class,
parentColumns = ["id"],
childColumns = ["documentId"],
onDelete = CASCADE
),
ForeignKey(
entity = Product::class,
parentColumns = ["products_id"],
childColumns = ["document_products_productIdMap"],
onDelete = RESTRICT
)
],*/
indices = [/*Index("document_products_productIdMap"),*/ Index("documentId"), Index(
value = [/*"document_products_productIdMap",*/ "documentId", "labelType"],
unique = true
)]
)
data class DocumentProduct(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "document_products_id")
var id: Long,
//#ColumnInfo(name = "document_products_productIdMap")
//var productId: String,
#ColumnInfo(name = "document_products_quantity")
var quantity: Float,
var orderQuantity: Float,
#ColumnInfo(name = "document_products_purchase")
var purchase: Float,
var documentId: Long,
var labelType: String?,
var timestamp: Long?
)
#Entity(tableName = "products")
open class Product(
#PrimaryKey(autoGenerate = false)
#ColumnInfo(name = "products_id")
open var id: String,
open var description: String?,
#ColumnInfo(defaultValue = "PZ")
open var unitOfMeasure: String,
#ColumnInfo(name = "products_purchase")
open var purchase: Float,
open var price: Float,
#ColumnInfo(name = "products_quantity")
open var quantity: Float
)
#Entity(
primaryKeys = ["productIdMap","documentProductIdMap"],
foreignKeys = [
ForeignKey(
entity = Product::class,
parentColumns = ["products_id"],
childColumns = ["productIdMap"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
ForeignKey(
entity = DocumentProduct::class,
parentColumns = ["document_products_id"],
childColumns = ["documentProductIdMap"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
]
)
data class ProductDocumentProductMap(
val productIdMap: Long,
#ColumnInfo(index = true)
var documentProductIdMap: Long
)
note commenting out code has been used to indicate code that isn't needed or must be changed to suit.

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.

Room. error: Multiple fields have the same columnName

I am trying to create relation between tables where one of the tables references another one twice. Here is the code:
#Entity(tableName = "message",
foreignKeys = [
ForeignKey(entity = Contact::class,
parentColumns = ["id"],
childColumns = ["toContactId"],
onDelete = NO_ACTION
),
ForeignKey(entity = Contact::class,
parentColumns = ["id"],
childColumns = ["fromContactId"],
onDelete = NO_ACTION)
], indices = [Index("toContactId"), Index("fromContactId")]
)
data class Message(
#PrimaryKey var id: String,
var creationDate: Date,
var messageStatus: MessageStatus,
var toContactId: String,
var fromContactId: String,
var text: String
)
#Entity(tableName = "contact")
data class Contact(
#PrimaryKey val id: String,
val firstName: String,
val lastName: String,
val cellPhone: String,
val email: String
)
And here is how I have created "relation" class:
data class MessageRelations(#Embedded var message: Message,
#Embedded var toContact: Contact,
#Embedded var fromContact: Contact)
This approach results in
error: Multiple fields have the same columnName: id. Field names: message > id, toContact > id, fromContact > id.
I also tried to add prefixes to annotation #Embedded(prefix = "to_") and #Embedded(prefix = "from_"). But in this case Room can't find matches between fileds returned by the query and those in MessageRelations class.
I'll be grateful for any hint on how to solve this issue.
I beleieve that using #Relation instead of #Embed will resolve those issues as rather than including the fields/variables from the Entity it builds the object according to the relationship.
So you would have something like :-
data class MessageRelations(#Embedded var message: Message,
#Relation(parentColumn = "toContactId", entityColumn = "id") var toContact: Contact,
#Relation(parentColumn = "fromContactId", entityColumn = "id") var fromContact: Contact)
Note the above is in-principle code and has not been tested or run.

Querying table A with inner join on table B does not return columns from table B in an Android application using Room database

I'm trying to query from posts table with inner join on users and its respective primary and foreign key:
#Query("SELECT p.*, u.id AS userId, u.name AS userName, u.username AS userUsername FROM posts p JOIN users u ON p.userId = u.id")
fun fetchAllPostsAndUsers(): Observable<List<Post>>
These are the POJOs. First, User:
#Entity(tableName = "users")
data class User(
#PrimaryKey(autoGenerate = true)
#SerializedName("id")
#ColumnInfo(name = "id")
val uid: Int,
val name: String,
val username: String,
val email: String,
#Embedded
val address: Address?,
val phone: String,
val website: String,
#Embedded
val company: Company?
)
and Post:
#Entity(
tableName = "posts",
foreignKeys = [ForeignKey(
entity = User::class,
parentColumns = ["id"],
childColumns = ["userId"],
onDelete = ForeignKey.CASCADE
)]
)
data class Post(
#PrimaryKey(autoGenerate = true)
#SerializedName("id")
#ColumnInfo(name = "id")
val pid: Int,
val userId: Int,
val title: String,
val body: String
)
But the result of the query above is only the columns from the posts table, without any column from the users', despite being included in the query when fetching from the database.
I guess you should take a look to this thread.
Post entity do not contain any User object. You should create a model that hold both.
The solution for this was to add fields matching the name of the columns or alias from the SQL query, and not these #Embeddeds.

Categories

Resources