I looked in the android documentation for an answer to my question, but I couldn't find it. To create a recyclerview using the information contained in these classes, how can I get a list of this information in Room
#Entity(
foreignKeys = [
ForeignKey(
entity = City::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("cityfk"),
onDelete = ForeignKey.NO_ACTION
)
]
)
data class Address(
#PrimaryKey
#ColumnInfo
var id: Long = 0
) : Serializable {
#ColumnInfo
var name: String = ""
#ColumnInfo(index = true)
var cityfk: Long = 0
}
#Entity(
foreignKeys = [
ForeignKey(
entity = State::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("statefk"),
onDelete = ForeignKey.NO_ACTION
)
]
)
data class City(
#PrimaryKey
#ColumnInfo
var id: Long = 0
) : Serializable {
#ColumnInfo
var name: String = ""
#ColumnInfo(index = true)
var statefk: Long = 0
}
#Entity
data class State(
#PrimaryKey
#ColumnInfo
var id: Long = 0
) : Serializable {
#ColumnInfo
var name: String = ""
}
How can I get a list of addresses listing the classes?
How to get a result like this in ANSI SQL:
select ADDRESS.NAME ADDRESS
, CITY.NAME CITY
, STATE.NAME STATE
from ADDRESS
join CITY
on CITY.ID = ADDRES.CITYFK
join STATE
on STATE.ID = CITY.STATEFK
You would typically have a POJO to represent the combined data. You can then either have a field/variable for the extracted columns noting that values are matched to the liked named variable.
You can use #Embedded to include an entity in it's entirety so in theory embed Address City and State.
see variable/column name issues
You can use #Embedded along with #Relation for the child (children) BUT not for grandchildren (e.g. State). You would need an underlying City with State POJO where City is embedded and State is related by an #Relation.
variable/column names are not an issue when using #Relation as room builds underlying queries from the parent.
Variable/Column name issues
Room maps columns to variable according to variable names. So there will be issues with id's and name columns if using the simpler #Embedded for all three entities.
I would suggest always using unique names e.g. addressId, cityId, StateId, (at least for the column names e.g. #ColumnInfo(name = "addressId")) but simpler to just have var addressid.
An alternative is the use the #Embedded(prefix = "the_prefix") on some, this tells room to match the variable to column name with the prefix so you need to use AS in the SQL. Obviously the_prefix would be changed to suit.
The Dao's
if using #Embedded with #Relation then you simply need to get the parent so
#Query("SELECT * FROM address")
fun getAddressWithCityAndWithState(): List<AddressWithCityAndWithState>
where AddressWithCityAndWithState is the POJO that has the Address #Embedded and the CityWithState with #Relation.
You would also need the accompanying CityWithState POJO with City #Embedded and State with #Relation.
If Embedding Address, City and State with City having a prefix of "city_" and state having a prefix of "state_" then you would use something like :-
#Query("SELECT address.*, city.id AS city_id, city.name AS city_name, state.id AS state_id, state.name AS state_name FROM address JOIN city ON address.cityfk = city.it JOIN state ON city.statefk = state.id")
fun getAddressWithCityAndWithState(): List<AddressWithCityAndWithState>
where AddressWithCityAndWithState is the POJO that has Address, City and State #Embedded
Note the above is in-principle.
Working Example
The following is a working example based upon
a) renaming the columns to avoid ambiguity and
b) using #Embedded of all three classes in the POJO AddressWithCityWithState
First changes to the Address, City and State to rename the columns :-
Address :-
#Entity(
foreignKeys = [
ForeignKey(
entity = City::class,
parentColumns = arrayOf("city_id"), //<<<<<<<<<< CHANGED
childColumns = arrayOf("cityfk"),
onDelete = ForeignKey.NO_ACTION
)
]
)
data class Address(
#PrimaryKey
#ColumnInfo(name ="address_id") //<<<<<<<<<< ADDED name
var id: Long = 0
) : Serializable {
#ColumnInfo(name = "address_name") //<<<<<<<<<< ADDDED name
var name: String = ""
#ColumnInfo(index = true)
var cityfk: Long = 0
}
City :-
#Entity(
foreignKeys = [
ForeignKey(
entity = State::class,
parentColumns = arrayOf("state_id"), //<<<<<<<<<< changed
childColumns = arrayOf("statefk"),
onDelete = ForeignKey.NO_ACTION
)
]
)
data class City(
#PrimaryKey
#ColumnInfo(name = "city_id") // <<<<<<<<<< ADDED name
var id: Long = 0
) : Serializable {
#ColumnInfo(name = "city_name") //<<<<<<<<<< ADDED name
var name: String = ""
#ColumnInfo(index = true)
var statefk: Long = 0
}
State :-
#Entity
data class State(
#PrimaryKey
#ColumnInfo(name = "state_id") // ADDED name
var id: Long = 0
) : Serializable {
#ColumnInfo(name = "state_name") // ADDED name
var name: String = ""
}
Next the POJO AddressWithCityWithState :-
data class AddressWithCityWithState (
#Embedded
val address: Address,
#Embedded
val city: City,
#Embedded
val state: State
)
due to unique column names no prefix = ? required
A suitable DAO :-
#Query("SELECT * FROM address JOIN city on address.cityfk = city.city_id JOIN state ON city.statefk = state.state_id")
fun getAllAddressesWithCityAndWithState(): List<AddressWithCityWithState>
simplified due to column renaming so * instead AS clauses for ambiguous column names
Using the above :-
allDao = db.getAllDao()
var state = State()
state.name = "State1"
var stateid = allDao.insert(state)
var city = City()
city.name = "City1"
city.statefk = stateid
var cityid = allDao.insert(city)
var address = Address()
address.name = "Address1"
address.cityfk = cityid
allDao.insert(address)
for(awcws: AddressWithCityWithState in allDao.getAllAddressesWithCityAndWithState()) {
Log.d("DBINFO","${awcws.address.name}, ${awcws.city.name}, ${awcws.state.name}")
}
The result in the log being :-
2021-11-22 07:43:28.574 D/DBINFO: Address1, City1, State1
Other working examples (without changing column names)
Without any changes to the Entities (Address, city and state). Here are working examples of the other options.
1- Get full address as a single string, all that is required is the query such as :-
#Query("SELECT address.name||','||city.name||','||state.name AS fullAddress FROM address JOIN city ON address.cityfk = city.id JOIN state ON city.statefk = state.id ")
fun getAddressesAsStrings(): List<String>
of course not much use say for a drop down selector as you can't ascertain where in the database the rows came from.
2 - Basic POJO with unambiguous column names
The POJO :-
data class AddressWithCityWithState(
var address_id: Long,
var address_name: String,
var city_id: Long,
var city_name: String,
var state_id: Long,
var state_name: String
)
The query :-
/*
* Returns multiple columns renamed using AS clause to disambiguate
* requires POJO with matching column names
* */
#Query("SELECT " +
"address.id AS address_id, address.name AS address_name, " +
"city.id AS city_id, city.name AS city_name, " +
"state.id AS state_id, state.name AS state_name " +
"FROM address JOIN city ON address.cityfk = city.id JOIN state ON city.statefk = state.id")
fun getAddressesWithCityAndStateViaBasicPOJO(): List<AddressWithCityWithState>
3- POJO using EMBEDS
The POJO :-
data class AddressWithCityWithStateViaEmbeds(
#Embedded
var address: Address,
#Embedded(prefix = cityPrefix)
var city: City,
#Embedded(prefix = statePrefix)
var state: State
) {
companion object {
const val cityPrefix = "city_"
const val statePrefix = "state_"
}
}
The query :-
/*
* Returns multiple columns renamed according to the prefix=? coded in the
* #Embedded annotation
*
*/
#Query("SELECT address.*, " +
"city.id AS " + AddressWithCityWithStateViaEmbeds.cityPrefix + "id," +
"city.name AS " + AddressWithCityWithStateViaEmbeds.cityPrefix + "name," +
"city.statefk AS " + AddressWithCityWithStateViaEmbeds.cityPrefix + "statefk," +
"state.id AS " + AddressWithCityWithStateViaEmbeds.statePrefix + "id," +
"state.name AS " + AddressWithCityWithStateViaEmbeds.statePrefix + "name " +
"FROM address JOIN city ON address.cityfk = city.id JOIN state ON city.statefk = state.id")
fun getAddressesWithCityAndStateViaEmbedPOJO(): List<AddressWithCityWithStateViaEmbeds>
4- POJO's with parent EMBED and child RELATE
The POJO's :-
data class CityWithState(
#Embedded
var city: City,
#Relation(
entity = State::class,
parentColumn = "statefk",
entityColumn = "id"
)
var state: State
)
and :-
data class AddressWithCityWithStateViaRelations(
#Embedded
var address: Address,
#Relation(
entity = City::class, /* NOTE NOT CityWithState which isn't an Entity */
parentColumn = "cityfk",
entityColumn = "id"
)
var cityWithState: CityWithState
)
and the query :-
#Transaction
#Query("SELECT * FROM address")
fun getAddressesWithCityAndStateViaRelations(): List<AddressWithCityWithStateViaRelations>
note the use of #Tranaction so the underlying queries, built by Room, are all done within a single database transaction.
Putting the above into use
The following code in an activity uses all 4 to output the same results :-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val TAG: String = "DBINFO"
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
var state = State(1)
state.name = "State1"
val state1Id = dao.insert(state)
state.id = 2
state.name = "State2"
val state2Id = dao.insert(state)
var city = City(10)
city.name = "City1"
city.statefk = state1Id
val city1Id = dao.insert(city)
city.id = 11
city.name = "City2"
city.statefk = state2Id
val city2Id = dao.insert(city)
city.id = 12
city.name = "City3"
city.statefk = state1Id
val city3Id = dao.insert(city)
var address = Address(100)
address.name = "Address1"
address.cityfk = city1Id
dao.insert(address)
address.id = address.id + 1
address.name = "Address2"
address.cityfk = city2Id
dao.insert(address)
address.id = address.id + 1
address.name = "Address3"
address.cityfk = city3Id
for (s: String in dao.getAddressesAsStrings()) {
Log.d(TAG + "STRG", s)
}
for (awcws: AddressWithCityWithState in dao.getAddressesWithCityAndStateViaBasicPOJO()) {
Log.d(TAG + "BASICPOJO", "${awcws.address_name}, ${awcws.city_name}, ${awcws.state_name}")
}
for (awcwsve: AddressWithCityWithStateViaEmbeds in dao.getAddressesWithCityAndStateViaEmbedPOJO()) {
Log.d(TAG + "EMBEDS","${awcwsve.address.name}, ${awcwsve.city.name}, ${awcwsve.state.name}")
}
for(awcwsvr: AddressWithCityWithStateViaRelations in dao.getAddressesWithCityAndStateViaRelations()) {
Log.d(TAG + "MIXED","${awcwsvr.address.name}, ${awcwsvr.cityWithState.city.name}, ${awcwsvr.cityWithState.state.name}")
}
}
}
The output to the log being :-
2021-11-22 12:33:54.322 D/DBINFOSTRG: Address1,City1,State1
2021-11-22 12:33:54.322 D/DBINFOSTRG: Address2,City2,State2
2021-11-22 12:33:54.324 D/DBINFOBASICPOJO: Address1, City1, State1
2021-11-22 12:33:54.324 D/DBINFOBASICPOJO: Address2, City2, State2
2021-11-22 12:33:54.326 D/DBINFOEMBEDS: Address1, City1, State1
2021-11-22 12:33:54.326 D/DBINFOEMBEDS: Address2, City2, State2
2021-11-22 12:33:54.332 D/DBINFOMIXED: Address1, City1, State1
2021-11-22 12:33:54.332 D/DBINFOMIXED: Address2, City2, State2
Related
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
Let's say I have an Article table like this:
Article {
id;
title;
content;
main_reference_id;
secondary_reference_id;
}
And there is a Reference table somewhat like this:
Reference {
id;
other_reference_related_columns...;
}
Now I want to fetch the Article while also fetching main reference, secondary reference with another POJO like this:
data class ArticleFull(
#Embedded
val article: article,
#Relation(parentColumn = "main_reference_id", entityColumn = "id")
val main_reference: Reference,
#Relation(parentColumn = "secondary_reference_id", entityColumn = "id")
val other_reference: Reference
)
But I'm not sure what I wrote is the right usage of #Relation annotation or not.
N.B.: I'm from the Laravel/Eloquent background, So I'm more familiar with these belongsTo, hasOne, hasMany, belongsToMany, and so on relationship types.
Thanks.
But I'm not sure what I wrote is the right usage of #Relation annotation or not.
Yes that is fine.
Here's a working example. That shows the use of the ArticleFull POJO:-
First the Entities (Tables):-
Reference :-
#Entity
data class Reference(
#PrimaryKey
val id: Long? = null,
val other_data: String
)
Article :-
#Entity(
foreignKeys = [
ForeignKey(
entity = Reference::class,
parentColumns = ["id"],
childColumns = ["main_reference_id"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
ForeignKey(
entity = Reference::class,
parentColumns = ["id"],
childColumns = ["secondary_reference_id"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
]
)
data class Article(
#PrimaryKey
val id: Long? = null,
val title: String,
val content: String,
#ColumnInfo(index = true)
val main_reference_id: Long,
#ColumnInfo(index = true)
val secondary_reference_id: Long
)
Foreign Key constraints added, they help to enforce referential integrity, they are optional. The onDelete and onUpdate are optional within the Foreign Key.
An #Dao class (abstract class rather than interface, abstract class is more versatile) ArticleAndReferenceDao :-
#Dao
abstract class ArticleAndReferenceDao {
#Insert
abstract fun insert(reference: Reference): Long
#Insert
abstract fun insert(article: Article): Long
#Transaction
#Query("SELECT * FROM article")
abstract fun getAllArticleFull(): List<ArticleFull>
#Transaction#Query("SELECT * FROM article WHERE id=:articleId")
abstract fun getArticleFullByArticleId(articleId: Long): List<ArticleFull>
}
An #Database class ArticleDatabase :-
#Database(entities = [Reference::class,Article::class],version = 1)
abstract class ArticleDatabase: RoomDatabase() {
abstract fun getArticleAndReferenceDao(): ArticleAndReferenceDao
companion object {
#Volatile
private var instance: ArticleDatabase? = null
fun getArticleDatabaseInstance(context: Context): ArticleDatabase {
if(instance == null) {
instance = Room.databaseBuilder(
context,
ArticleDatabase::class.java,
"article.db"
)
.allowMainThreadQueries()
.build()
}
return instance as ArticleDatabase
}
}
}
Finally some Activity code , noting that for convenience and brevity .allowMainThreadQueries has been used allow the code to be run on the main thread :-
class MainActivity : AppCompatActivity() {
lateinit var articleDatabase: ArticleDatabase
lateinit var articleAndReferenceDao: ArticleAndReferenceDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
articleDatabase = ArticleDatabase.getArticleDatabaseInstance(this)
articleAndReferenceDao = articleDatabase.getArticleAndReferenceDao()
val ref1 = articleAndReferenceDao.insert(Reference(other_data = "Reference1"))
val ref2 = articleAndReferenceDao.insert(Reference(other_data = "Reference2"))
val ref3 = articleAndReferenceDao.insert(Reference(other_data = "Reference3"))
val ref4 = articleAndReferenceDao.insert(Reference(other_data = "Reference4"))
articleAndReferenceDao.insert(Article(title = "Article1",main_reference_id = ref1,secondary_reference_id = ref2, content = "Content for Article1"))
articleAndReferenceDao.insert(Article(title = "Article2", main_reference_id = ref3, secondary_reference_id = ref4,content = "Content for Article2"))
// AND/OR
articleAndReferenceDao.insert(
Article(
title = "Article3",
content = "Content for Article 3",
main_reference_id = articleAndReferenceDao.insert(Reference(other_data = "Reference5")),
secondary_reference_id = articleAndReferenceDao.insert(Reference(other_data = "reference6"))
)
)
for(d: ArticleFull in articleAndReferenceDao.getAllArticleFull()) {
Log.d("ARTICLEINFO"," Article is ${d.article.content} ID is ${d.article.id} " +
"\n\tMain Reference is ${d.main_reference.other_data} ID is ${d.main_reference.id}" +
"\n\tSecondary Reference is ${d.other_reference.other_data} ID is ${d.other_reference.id}")
}
}
}
Running the above results in the log containing :-
D/ARTICLEINFO: Article is Content for Article1 ID is 1
Main Reference is Reference1 ID is 1
Secondary Reference is Reference2 ID is 2
D/ARTICLEINFO: Article is Content for Article2 ID is 2
Main Reference is Reference3 ID is 3
Secondary Reference is Reference4 ID is 4
D/ARTICLEINFO: Article is Content for Article 3 ID is 3
Main Reference is Reference5 ID is 5
Secondary Reference is reference6 ID is 6
Additional
You can also use #Embedded for all three parts.
The advantages are :-
more flexible filtering i.e. you can filter on the children (with #Relationship although you can, you need to defines the JOIN's)
Instead of multiple underlying queries for an #Relationship a single query retrieves all data
The disadvantages are:-
more complex query
requirement to use #ColumnInfo's prefix = annotation if column names are not unique to disambiguate them and thus more complex query to name the output columns accordingly.
So you could have :-
data class ArticleFullAlternative(
#Embedded
val article: Article,
#Embedded(prefix = "main_")
val main_reference: Reference,
#Embedded(prefix = "other_")
val other_reference: Reference
)
Along with an #Query such as :-
#Query("SELECT article.*, " +
/* as prefix = "main_" has been used then rename output columns accordingly */
"m.id AS main_id, m.other_data AS main_other_data, " +
/* as prefix = "other_" has been used then rename output columns accordingly */
"o.id AS other_id, o.other_data AS other_other_data " +
"FROM article " +
"JOIN reference AS m /*<<<<< to disambiguate column names */ ON main_reference_id = m.id " +
"JOIN reference AS o /*<<<<< to disambiguate column names */ ON main_reference_id = o.id ")
abstract fun getArticleFullAlternative(): List<ArticleFullAlternative>
An example use in an Activity could be :-
for(afa: ArticleFullAlternative in articleAndReferenceDao.getArticleFullAlternative()) {
Log.d("ALTARTICLEINFO"," Article is ${afa.article.content} ID is ${afa.article.id} " +
"\n\tMain Reference is ${afa.main_reference.other_data} ID is ${afa.main_reference.id}" +
"\n\tSecondary Reference is ${afa.other_reference.other_data} ID is ${afa.other_reference.id}")
}
This produces exactly the same output
NOTE: I cannot use relation as we had performance issue which is not reproduced on direct join query.
Until I added target user and from group user and corresponding
LEFT JOIN chat_user ON chat_user.chat_user_id = message_item.messages_target_user
LEFT JOIN chat_user ON chat_user.chat_user_id = message_item.messages_from_group_user
It worked well. But after addition, I can not figure out how to make those prefixes map in the query.
class ReadMessageEntity(
#Embedded
var message: MessageEntity,
#Embedded
var block: BlockEntity?,
#Embedded
var user: ChatUserRelationEntity,
#Embedded(prefix = "target_user_")
var targetUser: ChatUserEntity?,
#Embedded(prefix = "from_group_user_")
var fromGroupUser: ChatUserEntity?
)
This is which I'm trying to query:
#Transaction
#Query("""
SELECT * FROM message_item
LEFT JOIN block_item ON block_item.block_message_id = message_item.message_local_id
LEFT JOIN chat_user_relation ON chat_user_relation.chat_user_id = message_item.message_user_id
LEFT JOIN chat_user ON chat_user.chat_user_id = message_item.messages_target_user
LEFT JOIN chat_user ON chat_user.chat_user_id = message_item.messages_from_group_user
WHERE message_item.message_chat_id = :chatId
ORDER BY message_created_at ASC
""")
fun getMessagesByChat(chatId: String): Single<List<ReadMessageEntity>>
The error:
e: error: There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (ambiguous column name: main.chat_user.chat_user_id)
Here is my join solution:
ChatsEntity
#Entity(tableName = "Chats",foreignKeys = [ForeignKey(entity = UserEntity::class,
parentColumns = ["id"], childColumns = ["userId"], onDelete = NO_ACTION),ForeignKey(entity = LastMessageEntity::class,
parentColumns = ["id"], childColumns = ["roomId"], onDelete = NO_ACTION)])
data class ChatsEntity(
#PrimaryKey(autoGenerate = true)
var id: Int? = null,
#ColumnInfo(name = "roomId") var roomId: String,
#ColumnInfo(name = "userId") var userId: String,
#ColumnInfo(name = "count") var count: Int
)
LastMessageEntity
#Entity(tableName = "LastMessages")
data class LastMessageEntity(
#PrimaryKey #ColumnInfo(name = "id") var id: String = "",
#ColumnInfo(name = "message") var message: String = "",
#ColumnInfo(name = "type") var type: String = ""
)
UserEntity
#Entity(tableName = "Users")
data class UserEntity(
#PrimaryKey #ColumnInfo(name = "id") var id: String = "",
#ColumnInfo(name = "username") var username: String = "",
#ColumnInfo(name = "token") var token: String = ""
)
1. using relation
class ChatUserMessage {
#Embedded
var chat : ChatsEntity? = null
#Relation(parentColumn = "userId", entityColumn = "id")
var user : UserEntity? = null
#Relation(parentColumn = "roomId", entityColumn = "id")
var lastMessage : LastMessageEntity? = null
}
SQL Query
#Query("SELECT * FROM Chats")
fun getAllChats(): List<ChatUserMessage?>?
2. without using relation
class ChatUserMessage
{
#Embedded
var chat: ChatsEntity? = null
#Embedded(prefix = "user_")
var user: UserEntity? = null
#Embedded(prefix = "message_")
var lastMessage: LastMessageEntity? = null
}
Query
#Query("SELECT Chats.*, LastMessages.id as message_id,LastMessages.message as message_message, LastMessages.type as message_type, Users.id as user_id, Users.username as user_username, Users.token as user_token FROM Chats INNER JOIN LastMessages ON LastMessages.id = Chats.roomId INNER JOIN Users ON Users.id = Chats.userId")
fun getAllChats(): List<ChatUserMessage?>?
In the query, you have to set an alias for the table chat_user as you are joining it twice in the same name which will confuse database engine so it tells you that the field is ambiguous.
EDIT:
I found something that may be similar to your issue: https://github.com/JetBrains/Exposed/issues/177
Also they referred to this code as an example to fix this issue:
object Users : Table() {
val id = varchar("id", 10).primaryKey()
val name = varchar("name", length = 50)
val residentialCityId = optReference("resid_city_id", Cities)
val bornCityId = optReference("born_city_id", Cities)
}
object Cities : IntIdTable() {
val name = varchar("name", 50) // Column
}
fun test() {
val userTable1 = Users.alias("u1")
val userTable2 = Users.alias("u2")
Cities
.innerJoin(userTable1, {Cities.id}, {userTable1[Users.residentialCityId]})
.innerJoin(userTable2, {Cities.id}, {userTable2[Users.bornCityId]})
.selectAll()
}
as mentioned Moayad .AlMoghrabi I should use table aliases. So I renamed LEFT JOIN chat_user to LEFT JOIN chat_user target_user. Note there is no AS between chat-user and target_user. (my mistake was that I tried to put it there). Behavior of prefixes is still a mystery to me as the prefix for chat_user works properly, but if I put the prefix to ChatUserRelationEntity then I receive unknown column error.
SELECT * FROM message_item
LEFT JOIN block_item
ON block_item.block_message_id = message_item.message_local_id
LEFT JOIN chat_user_relation
ON chat_user_relation.chat_user_relation_chat_user_id = message_item.message_user_id
AND chat_user_relation.chat_user_relation_chat_user_chat_id = :chatId
LEFT JOIN chat_user target_user
ON target_user.chat_user_id = message_item.messages_target_user
LEFT JOIN chat_user from_group_user
ON from_group_user.chat_user_id = message_item.messages_from_group_user
WHERE message_item.message_chat_id = :chatId
ORDER BY message_created_at ASC
Here is my setup. I have a WorkoutPlan object that can contain a list of Workout objects in it. The way I'm currently modeling it is by have a third table that handles mapping the two together. A WorkoutPlan can contain many workouts, and a Workout can be used by many WorkoutPlans.
#Entity(tableName = "workoutPlans")
data class DbWorkoutPlan(#ColumnInfo(name = "name")
val name: String,
#ColumnInfo(name = "date")
val date: Date) {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
var id: Int = 0
}
#Entity(tableName = "workouts")
data class DbWorkout(#ColumnInfo(name = "name")
val name: String,
#ColumnInfo(name = "date")
val data: Date) {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
var id: Int = 0
}
#Entity(tableName = "DbWorkoutPlanWorkoutJoin",
primaryKeys = arrayOf("workoutPlanId", "workoutId"),
foreignKeys = arrayOf(ForeignKey(entity = DbWorkoutPlan::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("workoutPlanId")),
ForeignKey(entity = DbWorkout::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("workoutId"))))
data class DbWorkoutPlanWorkoutJoin(#ColumnInfo(name = "workoutPlanId")
val workoutPlanId: Int,
#ColumnInfo(name = "workoutId")
val workoutId: Int)
So that is my data setup for the tables. I'm not sure if it's correct. On the returned data side I have this.
data class DbWorkoutPlanResult(#Embedded
val workoutPlan: WorkoutPlan,
#Relation(parentColumn = "id", entityColumn = "workoutId")
val workoutIds: List<DbWorkout>)
So I want to get back a DbWorkoutPlanResult containing one WorkoutPlan and a list of all the Workouts it has.
I know I'm not doing this right, and the complexity is increasing quickly. Does anyone know what I have done wrong in my setup? And what would I have for a query? My best attempt is this
#Query("SELECT * " +
"FROM DbWorkoutPlanWorkoutJoin " +
"INNER JOIN workoutPlans " +
"ON DbWorkoutPlanWorkoutJoin.workoutPlanId = workoutPlans.id " +
"INNER JOIN workouts " +
"ON DbWorkoutPlanWorkoutJoin.workoutId = workouts.id ")
fun getWorkoutPlans(): Flowable<List<DbWorkoutPlanResult>>
Thanks in advance.
Using #Relation annotation you can create a 1:N (one to many) relation. Like, in your case, a single plan can have multiple workouts, but each workout can belong to only a single plan. This is clearly not what you want!
For your needs, which I assume are like this: get a list of POJOs containing a planand list of associated workouts, you need to use a separate JOIN Table (which I guess you already are).
A simple way to get the results would be to divide the operations into two queries:
Get List<DbWorkoutPlan> of all plans
Query the Join Table and fetch all List<DbWorkout> for each DbWorkoutPlan
Example code
First define the models
#Entity(tableName="plans") class DbWorkoutPlan {
#PrimaryKey
private long id;
// ...
#Ignore private List<DbWorkout>; // do not persist this, also create getter/setter for this field
}
#Entity(tableName="workouts") class DbWorkout {
#PrimaryKey
private long id;
// ...
}
#Entity(
tableName="plan_workout_join"
primaryKeys = {"workoutPlanId", "workoutId"},
foreignKeys = {
#ForeignKey(entity = DbWorkoutPlan.class, parentColumns = "id", childColumns = "plan"),
#ForeignKey(entity = DbWorkout.class, parentColumns = "id", childColumns = "workout")
}
) class PlanWorkoutJoin {
private long plan;
private long workout;
}
Now in the DAO,
#Query("SELECT * FROM plans")
List<DbWorkoutPlan> getAllPlans();
#Query("SELECT * FROM workouts WHERE workouts.id IN (SELECT workout FROM plan_workout_join WHERE plan_workout_join.plan=:plan)")
List<DbWorkout> getWorkoutsForPlan(long plan);
Now you can query like,
List<DbWorkoutPlan> plans = dao.getAllPlans();
for(DbWorkoutPlan plan : plans){
List<DbWorkout> workouts = dao.getWorkoutsForPlan(plan.getId());
plan.setWorkouts(workouts);
}
// ... continue
P.S. You'll obviously need to modify this a bit if you are using RxJava, but the core idea remains the same
I am working with room persistence library in android, i would appreciate if someone can help me in using foreign key, how to get data by using foreign key.
Just to summarize the above posts for future readers:
The foreign key syntax in Kotlin is
#Entity(foreignKeys = arrayOf(ForeignKey(entity = ParentClass::class,
parentColumns = arrayOf("parentClassColumn"),
childColumns = arrayOf("childClassColumn"),
onDelete = ForeignKey.CASCADE)))
The foreign key syntax in Java is:
#Entity(foreignKeys = {#ForeignKey(entity = ParentClass.class,
parentColumns = "parentClassColumn",
childColumns = "childClassColumn",
onDelete = ForeignKey.CASCADE)
})
Note: foreignKeys is an array, so in Java enclose #ForeignKey elements in { and }
You can refer to the official documentation for more information.
https://developer.android.com/reference/androidx/room/ForeignKey
Here how you can define and access a One-to-many (Foreign Key) relationship in Android Jetpack Room. Here the Entities are Artist and Album and the foreign key is Album.artist
#Entity
data class Artist(
#PrimaryKey
val id: String,
val name: String
)
#Entity(
foreignKeys = [ForeignKey(
entity = Artist::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("artist"),
onDelete = ForeignKey.CASCADE
)]
)
data class Album(
#PrimaryKey
val albumId: String,
val name: String,
#ColumnInfo(index = true)
val artist: String
)
then the embedded object (read official Android documentation: Define relationships between objects)
data class ArtistAndAlbums(
#Embedded
val artist: Artist,
#Relation(
parentColumn = "id",
entityColumn = "artist"
)
val albums: List<Album>
)
And finally the DAO
#Dao
interface Library {
#Insert
suspend fun save(artist: Artist)
#Insert
suspend fun save(vararg album: Album)
#Transaction
#Query("SELECT * FROM artist")
suspend fun getAll(): List<ArtistAndAlbums>
#Transaction
#Query("SELECT * FROM artist WHERE id = :id")
suspend fun getByArtistId(id: String): ArtistAndAlbums
}
trying this out, (all these operations happens in a Coroutine)
// creating objects
val artist = Artist(id="hillsongunited", name="Hillsong United" )
val artist2 = Artist(id="planetshakers", name="Planet Shakers" )
val album1 = Album(albumId = "empires", name = "Empires", artist = artist.id)
val album2 = Album(albumId = "wonder", name = "Wonder", artist = artist.id)
val album3 = Album(albumId = "people", name = "People", artist = artist.id)
val album4 = Album(albumId = "rain", name = "Rain", artist = artist2.id)
val album5 = Album(albumId = "itschristmas", name = "Its Christmas", artist = artist2.id)
val album6 = Album(albumId = "overitall", name = "Over It All", artist = artist2.id)
// saving to database
SongDatabase.invoke(applicationContext).library().save(artist)
SongDatabase.invoke(applicationContext).library().save(artist2)
SongDatabase.invoke(applicationContext).library().save(album1, album2, album3, album4, album5, album6)
Logging out all Artists
val all = SongDatabase.invoke(applicationContext).library().getAll()
Log.d("debug", "All Artists $all ")
D/debug: All Artists [ArtistAndAlbums(artist=Artist(id=hillsongunited, name=Hillsong United), albums=[Album(albumId=empires, name=Empires, artist=hillsongunited), Album(albumId=wonder, name=Wonder, artist=hillsongunited), Album(albumId=people, name=People, artist=hillsongunited)]), ArtistAndAlbums(artist=Artist(id=planetshakers, name=Planet Shakers), albums=[Album(albumId=rain, name=Rain, artist=planetshakers), Album(albumId=itschristmas, name=Its Christmas, artist=planetshakers), Album(albumId=overitall, name=Over It All, artist=planetshakers)])]
Logging out albums by a specific artist,
val hillsongAlbums = SongDatabase.invoke(applicationContext).library().getByArtistId(artist.id)
Log.d("debug", "Albums by artist ID: $hillsongAlbums ")
D/debug: Albums by artist ID: ArtistAndAlbums(artist=Artist(id=hillsongunited, name=Hillsong United), albums=[Album(albumId=empires, name=Empires, artist=hillsongunited), Album(albumId=wonder, name=Wonder, artist=hillsongunited), Album(albumId=people, name=People, artist=hillsongunited)])
#ForeignKey annotations are not used to define relations when getting data but to define relations when modifying data. To get relational data from a Room databse, Google recommends the #Relation along with the #Embedded annotation.
You can check out my answer here for more explanation if you're interested.