I'm trying to get data using Room library with SQLite and kotlin with this query but I have 2 nullable parameters with the joined tables and I don't know how to check if they are null or not.
I want to see if they are null, then returns null and if they are not null returns the parameter but I am using this query RN and the problem is the query returns data only if all parameters (even nullable ones ) are not null.
this is my query
#Query(
"SELECT " +
"f.word AS from_word, " +
"f.word_id as from_word_id, " +
"f2.word AS from_word2, " +
"f2.word_id as from_word_id2, " +
"fl.language AS from_language, " +
"fl.language_id AS from_language_id, " +
"t.word AS to_word, " +
"t.word_id AS to_word_id, " +
"t2.word AS to_word2, " +
"t2.word_id AS to_word_id2, " +
"tl.language AS to_language, " +
"tl.language_id AS to_language_id, " +
"time, " +
"date, " +
"day_of_week AS dayOfWeek, " +
"country, " +
"city, " +
"bookmarked " +
"FROM word_translate_map " +
"JOIN word AS f ON from_word_map = f.word_id " +
"JOIN word AS t ON to_word_map = t.word_id " +
"JOIN word AS f2 ON from_word_map2 = f2.word_id " +
"JOIN word AS t2 ON to_word_map2 = t2.word_id " +
"JOIN language AS fl ON f.language_map = fl.language_id " +
"JOIN language AS tl ON t.language_map = tl.language_id "
)
abstract fun getAllTranslatedWords(): LiveData<List<TranslatedWord>>
and this is my table that I have a problem with it.
#Entity(
tableName = "word_translate_map",
primaryKeys = ["from_word_map","to_word_map"],
foreignKeys = [
ForeignKey(
entity = Word::class,
parentColumns = ["word_id"],
childColumns = ["from_word_map"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
ForeignKey(
entity = Word::class,
parentColumns = ["word_id"],
childColumns = ["to_word_map"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
],
indices = [
Index("to_word_map")
]
)
data class WordTranslateMap(
#ColumnInfo(name = "from_word_map")
val fromWord: Long,
#ColumnInfo(name = "to_word_map")
val toWord: Long,
#ColumnInfo(name = "from_word_map2")
val fromWord2: Long?,
#ColumnInfo(name = "to_word_map2")
val toWord2: Long?,
#ColumnInfo(name = "time")
val time: String,
#ColumnInfo(name = "date")
val date: String,
#ColumnInfo(name = "country")
val country: String,
#ColumnInfo(name = "city")
val city: String,
#ColumnInfo(name = "day_of_week")
val dayOfWeek: String,
#ColumnInfo(name = "bookmarked")
val bookmarked: Boolean
)
and also this is my model (if you need this)
data class TranslatedWord(
val from_word: String,
val from_word2: String?,
val from_language: String,
val to_word: String,
val to_word2: String?,
val to_language: String,
val time: String,
val date: String,
val country: String,
val city: String,
val dayOfWeek: String,
val bookmarked: Boolean
)
I think you can do a dual validation on the joins, is it possible on a where to select all fields that are either null or match a condition, like:
WHERE (:t2 IS NULL OR :t2 = :t1)
My guess is you can achieve the same doing that on the join, something like:
JOIN word AS f ON (from_word_map = f.word_id OR from_word_map IS NULL)
You can perform further validations (like checking if both fields are null to indeed return a null) nesting and's.
Related
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
Getting error on API level 23 & 24, Same Query is working fine on API level 29 & 30 and getting results.
Here are the entities(Removed the variables which are not used in Query for posting here)
#Entity
data class Activity(#PrimaryKey #ColumnInfo(name = "activity_id") val activityId: Int)
#Entity(tableName = "JobActivities", primaryKeys = ["activity_id", "job_id"])
data class JobActivity(
#ColumnInfo(name = "job_id")
val jobId: Int,
#ColumnInfo(name = "activity_id")
val activityId: Int,
)
#Entity(tableName = "LocationActivities")
data class LocationActivity(
#PrimaryKey
#ColumnInfo(name = "loc_act_id")
val locActId: String,
val active: Int?,
#ColumnInfo(name = "activity_id")
val activityId: Int?,
)
Query that i'm using
#Query("SELECT 'job' AS entity, l.activity_id , a.activity_name FROM LocationActivities l " +
"JOIN Activity a ON a.activity_id = l.activity_id WHERE loc_id = :locationId " +
"UNION SELECT 'loc' AS entity , j.activity_id , a.activity_name FROM JobActivities j " +
"JOIN Activity a ON a.activity_id = j.activity_id WHERE j.job_id = :jobId")
fun getActivities(locationId: String, jobId: Int): Flow<MutableList<ClassName>>
Suposse that i have an android studio App where i can add companies, and inside those companies i can also add employees. Now, i'm trying to get the employees list showing only the elements of the company I selected before (In an unique activity for showing employees).
I created two classes:
data class Company(val id:Int, val name:String, val telephone:String)
data class Employee (val id:Int, val name:String, val telephone:String, val idCompany:Int)
And in my SQLiteHelper I created the tables for each one, with a foreign key in Employees to make a relation
private val TABLE_COMPANY = "CompanyTable"
private val TABLE_EMPLOYEE = "EmployeeTable"
//Company table
private val COMPANY_ID = "_id"
private val COMPANY_NAME = "name"
private val COMPANY_TL = "telephone"
//Employee table
private val EMPLOYEE_ID = "id"
private val EMPLOYEE_NAME = "name"
private val EMPLOYEE_TL = "telephone"
private val EMPLOYEE_COMPANY_ID = "id"
}
override fun onCreate(db: SQLiteDatabase?) {
val CREATE_COMPANY_TABLE = ("CREATE TABLE " + TABLE_COMPANY + "("
+ COMPANY_ID + " INTEGER PRIMARY KEY,"
+ COMPANY_NAME + " TEXT,"
+ COMPANY_TL + " TEXT" + ")")
val CREATE_EMPLOYEE_TABLE = ("CREATE TABLE " + TABLE_EMPLOYEE + "("
+ EMPLOYEE_ID + " INTEGER PRIMARY KEY,"
+ EMPLOYEE_NAME + " TEXT,"
+ EMPLOYEE_TL + " INTEGER,"
+ EMPLOYEE_COMPANY_ID + " INTEGER,"
+ " FOREIGN KEY ("+ EMPLOYEE_COMPANY_ID+") REFERENCES "+TABLE_COMPANY+"("+ COMPANY_ID+"))")
db?.execSQL(CREATE_EMPLOYEE_TABLE)
db?.execSQL(CREATE_COMPANY_TABLE)
}
So, I made two activities with recyclerviews, one for the Companies and the other for the employees.
When i click a company, the employees activity opens and it shows a list of them.
But it shows all the employees i have, so i'm trying to figure out how to show only the ones that i saved with the same id of the Company that i clicked in the previous activity.
But i don't know how to proceed now
Here is the DAO function that shows the employees:
fun viewEmployee(): ArrayList<Employee> {
val empList: ArrayList<Employee> = ArrayList<Employee>()
// Query to select the records
val selectQuery = "SELECT L.$EMPLOYEE_NAME, L.$EMPLOYEE_TL, L.$EMPLOYEE_ID, L.$EMPLOYEE_COMPANY_ID, C.$COMPANY_ID" +
"FROM $TABLE_EMPLOYEE as L, $TABLE_COMPANY as C" +
"WHERE L.$EMPLOYEE_COMPANY_ID = C.$COMPANY_ID"
val db = this.readableDatabase
var cursor: Cursor? = null
try {
cursor = db.rawQuery(selectQuery, null)
} catch (e: SQLiteException) {
db.execSQL(selectQuery)
return ArrayList()
}
var id: Int
var name: String
var telephone: String
var idCompany: Int
if (cursor.moveToFirst()) {
do {
id = cursor.getInt(cursor.getColumnIndex(EMPLOYEE_ID))
name = cursor.getString(cursor.getColumnIndex(EMPLOYEE_NAME))
telephone = cursor.getString(cursor.getColumnIndex(EMPLOYEE_TL))
idCompany = cursor.getInt(cursor.getColumnIndex(EMPLOYEE_COMPANY_ID))
val employee = Employee(id = id, name = name, telephone = telephone, idCompany = idCompany)
empList.add(employee)
} while (cursor.moveToNext())
}
return empList
}
And here is the activity that shows the employees
class ManagerEmp : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_manager_emp)
setList()
createEmp.setOnClickListener{
val intent = Intent(this, CreateEmp::class.java)
startActivity(intent)
}
}
/**
* Gets DB employee list
*/
private fun getItemsList(): ArrayList<Employee> {
//creating the instance of DatabaseHandler class
val databaseHandler = DataBaseHelper(this)
//calling the viewEmployee method of DatabaseHandler class to read the records
val empList: ArrayList<Employee> = databaseHandler.viewEmployee()
return empList
}
/**
* Generates the list
*/
private fun setList() {
recyclerEmp.layoutManager = LinearLayoutManager(this)
val itemAdapter = empAdapter(this, getItemsList())
recyclerEmp.adapter = itemAdapter
}
}
It sounds simple but it isn't (At least for me)
I thought of getting the id value of the company and pass it to the employee list activity, so i can use to compare it, but idk how, i'm pretty new in kotlin (And programming in general)
If you can give an answer, you would be saving my life.
H E L P
First you have to obtain companyId you want to filter by. It can be done for example by adding onClickListener in RecyclerView.
Then you will need additional query which will be used for filtering by companyId.
For example if companyId is stored in filteredCompanyId variable you can add additional filter condition (WHERE ... AND C.$COMPANY_ID == $filteredCompanyId):
val filteredCompanyId = 5
val query = """
SELECT L.$EMPLOYEE_NAME,
L.$EMPLOYEE_TL,
L.$EMPLOYEE_ID,
L.$EMPLOYEE_COMPANY_ID,
C.$COMPANY_ID
FROM $TABLE_EMPLOYEE AS L,
$TABLE_COMPANY AS C
WHERE L.$EMPLOYEE_COMPANY_ID = C.$COMPANY_ID
AND C.$COMPANY_ID == $filteredCompanyId
"""
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
I started using Room database and went through several docs to create room entities.
These are my relations. A Chat Channel can have Many Conversations. So this goes as one-to-many relationship. Hence i created entities as below.
Channel Entity
#Entity(primaryKeys = ["channelId"])
#TypeConverters(TypeConverters::class)
data class Channel(
#field:SerializedName("channelId")
val channelId: String,
#field:SerializedName("channelName")
val channelName: String,
#field:SerializedName("createdBy")
val creationTs: String,
#field:SerializedName("creationTs")
val createdBy: String,
#field:SerializedName("members")
val members: List<String>,
#field:SerializedName("favMembers")
val favMembers: List<String>
) {
// Does not show up in the response but set in post processing.
var isOneToOneChat: Boolean = false
var isChatBot: Boolean = false
}
Conversation Entity
#Entity(primaryKeys = ["msgId"],
foreignKeys = [
ForeignKey(entity = Channel::class,
parentColumns = arrayOf("channelId"),
childColumns = arrayOf("msgId"),
onUpdate = CASCADE,
onDelete = CASCADE
)
])
#TypeConverters(TypeConverters::class)
data class Conversation(
#field:SerializedName("msgId")
val msgId: String,
#field:SerializedName("employeeID")
val employeeID: String,
#field:SerializedName("channelId")
val channelId: String,
#field:SerializedName("channelName")
val channelName: String,
#field:SerializedName("sender")
val sender: String,
#field:SerializedName("sentAt")
val sentAt: String,
#field:SerializedName("senderName")
val senderName: String,
#field:SerializedName("status")
val status: String,
#field:SerializedName("msgType")
val msgType: String,
#field:SerializedName("type")
val panicType: String?,
#field:SerializedName("message")
val message: List<Message>,
#field:SerializedName("deliveredTo")
val delivered: List<Delivered>?,
#field:SerializedName("readBy")
val read: List<Read>?
) {
data class Message(
#field:SerializedName("txt")
val txt: String,
#field:SerializedName("lang")
val lang: String,
#field:SerializedName("trans")
val trans: String
)
data class Delivered(
#field:SerializedName("employeeID")
val employeeID: String,
#field:SerializedName("date")
val date: String
)
data class Read(
#field:SerializedName("employeeID")
val employeeID: String,
#field:SerializedName("date")
val date: String
)
// Does not show up in the response but set in post processing.
var isHeaderView: Boolean = false
}
Now as you can see Conversation belongs to a Channel. When user sees a list of channels, i need to display several attributes of last Conversation in the list item. My question is, is it enough if i just declare relation like above or should i contain Converstion object in Channel class? What are the other ways in which i can handle it? Because UI needs to get most recent conversation that happened along with time, status etc. in each item of the channel list when user scrolls. So there should not be any lag in UI because of this when i query.
And how can i have recent Converstaion object in Channel object?
I suggest create another class (not in DB, just for show in UI) like this:
data class LastConversationInChannel(
val channelId: String,
val channelName: String,
val creationTs: String,
val createdBy: String,
val msgId: String,
val employeeID: String,
val sender: String,
val sentAt: String,
val senderName: String
.
.
.
)
Get last Conversation in each Channel by this query:
SELECT Channel.*
,IFNULL(LastConversation.msgId,'') msgId
,IFNULL(LastConversation.sender,'') sender
,IFNULL(LastConversation.employeeID,'') employeeID
,IFNULL(LastConversation.sentAt,'') sentAt
,IFNULL(LastConversation.senderName,'') senderName
from Channel left join
(SELECT * from Conversation a
WHERE a.msgId IN ( SELECT b.msgId FROM Conversation AS b
WHERE a.channelId = b.channelId
ORDER BY b.sentAt DESC LIMIT 1 )) as LastConversation
on Channel.channelId = LastConversation.channelId
then use it in your dao like this:
#Query(" SELECT Channel.*\n" +
" ,IFNULL(LastConversation.msgId,'') msgId\n" +
" ,IFNULL(LastConversation.sender,'') sender\n" +
" ,IFNULL(LastConversation.employeeID,'') employeeID\n" +
" ,IFNULL(LastConversation.sentAt,'') sentAt\n" +
" ,IFNULL(LastConversation.senderName,'') senderName\n" +
" from Channel left join \n" +
" (SELECT * from Conversation a \n" +
" WHERE a.msgId IN ( SELECT b.msgId FROM Conversation AS b \n" +
" WHERE a.channelId = b.channelId \n" +
" ORDER BY b.sentAt DESC LIMIT 1 )) as LastConversation\n" +
" on Channel.channelId = LastConversation.channelId")
fun getLastConversationInChannel(): LiveData<List<LastConversationInChannel>>
is it enough if i just declare relation like above or should i contain Converstion object in Channel class?
You should not contain Conversation in Channel class, because Room will create some columns for it in Conversation table.
You can have an LastConversation which is an Conversation object inside the Chanel. You have to update this every time the lastConversation is updated by modify the table Chanel from Room layer. (Not take so much performance for update db). By implement sorting for the Chanel list (Comparable). Your UI update will be cool. And your logic from UI or ViewModel is simpler. I did it this way too.