I'm using Room on Android to query a POJO.
My POJO (Transaction) contains a value and an account_id. It further contains an account (instance of Account), which is fetched by a #Relation.
The transaction class:
#Parcelize
class Transaction(
#Embedded val entity: TransactionEntity,
#Relation(entity = Account::class, parentColumn = Database.Transactions.COL_ACCOUNT_ID, entityColumn = Database.Accounts.COL_ID) val account: Account? = null
) : Model
The accounts properties:
data class Account(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = Database.Accounts.COL_ID) val id: Long = 0,
#ColumnInfo(name = Database.Accounts.COL_NAME) val name: String = "",
#ColumnInfo(name = Database.Accounts.COL_BALANCE) val balance: Double = 0.0
)
The accounts.balance is not a value in the database, but the sum of all transactions.value belonging to that account.
So I need to sum every transaction.value where transaction.account_id = account.id.
It should work similar to this way, but I'm not getting it right:
#androidx.room.Transaction
#Query(
"""
SELECT transactions.* FROM transactions
LEFT OUTER JOIN (SELECT transactions.value, SUM(transactions.value) AS balance FROM accounts) ON transactions.account_id = accounts.id
GROUP BY transactions.id
"""
)
fun getTrs(): List<Transaction>
try this:
#androidx.room.Transaction
#Query(
"""
SELECT transactions.* FROM transactions
LEFT OUTER JOIN (SELECT transactions.value, SUM(transactions.value) AS balance FROM accounts GROUP BY transactions.id) ON transactions.account_id = accounts.id
"""
)
fun getTrs(): List<Transaction>
You have list all necessary columns of your table transactions in GROUP BY clause.
SELECT transactions.* SUM(transactions.value) AS balance
FROM transactions
LEFT JOIN accounts ON transactions.account_id = accounts.id
GROUP BY ...transactions.id, transactions.account_id.... and all necessary columns of transactions table...
I hope I helped you!
You could use subquery:
SELECT t.*,(SELECT SUM(transactions.value) FROM accounts a ON t.account_id = a.id) AS balance
FROM transactions t
Make it clean:
Create a balance view first
CREATE VIEW v_balance AS
SELECT account_id, SUM(value) as account_balance
FROM transactions
GROUP BY account_id
Then use the view in left join as desired:
SELECT account.*, IFNULL(v_balance.account_balance, 0.0)
FROM account
LEFT JOIN v_balance ON v_balance.account_id = account.id
It's probably meant alike this:
SELECT a.*, SUM(t.value) AS balance
FROM transactions t
LEFT JOIN accounts a
ON a.account_id = t.account_id
GROUP BY t.account_id
I've made it by creating a new POJO wrapping my original account object, but adding a field for the sum which is now queried.
#Query(
"""
SELECT accounts.*,
(SELECT SUM(CASE WHEN transactions.type = ${Transaction.TransactionType.EARNING} THEN transactions.value
ELSE -transactions.value END) FROM transactions WHERE transactions.account_id = accounts.id) AS balance
FROM accounts
LEFT JOIN transactions ON transactions.account_id = accounts.id
GROUP BY accounts.id
"""
)
fun getAccountsWithBalance(): LiveData<List<AccountWithBalance>>
Related
I have three tables in my Room database, my query should return a list of products that contains details about them.
All products are inserted based on a documentId so when the query is done I need to get in the list only the items for that documentId.
The DAO looks like this:
#Transaction
#Query("SELECT * FROM document_products dp LEFT JOIN products ON products.id = dp.productId LEFT JOIN products_barcodes ON products_barcodes.barcode = dp.productId WHERE dp.documentId = :documentId AND (products_barcodes.barcode = dp.productId OR products.id = dp.productId) ORDER BY timestamp ASC")
fun getProductsWithDetails(documentId: Long): Flow<List<DocumentProductWithDetails>>
And if I test the query in a table like this:
Where documentId is 5 the query returns the correct values:
But those values are incorrect in the application probably cause of #Relation in DocumentProductWithDetails but I'm unable to find the issue, in facts inside the application the data is shown as this:
So as the item with productId is saved three times it is showing the last item instead of the one related to documentId
The data class which contains #Relation annotation looks like this:
#JsonClass(generateAdapter = true)
data class DocumentProductWithDetails(
#Relation(
parentColumn = "id",
entityColumn = "productId"
)
var product: DocumentProduct,
#Embedded
var details: ProductWithBarcodes?
)
Where DocumentProduct and ProductWithBarcodes:
data class DocumentProduct(
#PrimaryKey(autoGenerate = true)
var id: Long,
var productId: String,
var quantity: Float,
var orderQuantity: Float,
var purchase: Float,
var documentId: Long,
var labelType: String?,
var timestamp: Long?
)
data class ProductWithBarcodes(
#Embedded
var product: Product,
#Relation(
parentColumn = "id",
entityColumn = "productId"
)
var barcodes: List<Barcode>
)
So as the item with productId is saved three times it is showing the last item instead of the one related to documentId
IF any columns of the query have the same column name, the values assigned may(will be) inconsistent in that the value of the last column, of the repeated name, will be the value assigned to all the other columns that have the same name (etc).
I would suggest that the fix is to use unique column names. e.g. instead of productId in the Barcode that you use a column name more indicative of the use of the value, it could be considered as a map to the product so perhaps barcode_productIdMap.
it is not the query that is at fault but how Room handles retrieving and assigning values.
Consider your second image (with some marking):-
The above is explained in more detail, with examples, in this answer
Which is the correct id, productId, quantity (Rhetorical).
How is Room supposed to know what goes where? (Rhetorical)
Consider that the following query extracts exactly the same data (but with the data columns in a different sequence):-
#Query("SELECT products.*,products_barcodes.*,document_products.* FROM document_products dp LEFT JOIN products ON products.id = ....
How is Room meant to cope with the different order (should the correct productId be the first or the second (Rhetorical)).
with unique column names the column order is irrelevant as Room then knows exactly what column is associated with what field, there is no ambiguity.
with unique columns tablename.columname can be reduced to just the column name in the SQL, so the SQL can be simplified.
I'm trying to get a count of a one-to-many ralationship in my query.
My data class:
data class CustomerWithCounts(
#Embedded val customer: Customer,
#Embedded val address: Address,
val orderCount: Int,
val paymentCount: Int
)
I'm struggling to figure out how I can get the counts.
My current Query:
SELECT *,
COUNT(SELECT * FROM tblOrder WHERE customerId = c.id) AS 'orderCount',
COUNT(SELECT * FROM tblPayment WHERE customerId = c.id) AS 'paymentCount'
FROM tblCustomer c
LEFT JOIN tblAddress a ON c.customerBillingAddressId = a.addressId
ORDER BY c.customerFirstName, c.customerLastName
How do I achieve this?
I have a table Messages (PrimaryKey messageId) and a table ReadUpdates. Each message has many ReadUpdates that are related with
I want to query the Room Database with the below sql, and get a Map with key=Message and value=List
#Query(
"""
Select * from messages
inner join readUpdates on messages.messages_message_id = readUpdates.read_upd_message_id
where messages.messages_message_id = :messageId
and messages.messages_server_id = :serverId
"""
)
fun getMessageRecipients(
serverId: Long,
messageId: String
): LiveData<Map<Message, List<ReadUpdateAccount>>>
ReadUpdateAcccount is the model joined ReadUpdate and Account
data class ReadUpdateAccount(
#Embedded
val readUpdate: ReadUpdate,
#Relation(entity = Account::class, parentColumn = "read_upd_connection_id", entityColumn = "accounts_account_id")
val account: Account?
)
After debugging the query i see that instead of example
<Message(AAA), [ReadUpdateAccount(111), ReadUpdateAccount(222), ReadUpdateAccount(333)]>
i have
<Message(AAA), [ReadUpdateAccount(111)]> <Message(AAA), [ReadUpdateAccount(222)]>, <Message(AAA), [ReadUpdateAccount(333)]>
Obviously, i need the First but i get the Second
Why is this happening and how can i solve this?
Say I have a one to many relationship between City and Person.
#Entity(tableName = "city")
data class City(
#PrimaryKey
val cityId: Int,
val name: String
)
#Entity(tableName = "person")
data class Person(
#PrimaryKey(autoGenerate = true)
val personId: Long = 0,
val name: String,
val cityId: Int
)
The goal is to get all person in the database and their corresponding city. According to the Jetpack documentation, the obvious way is to create a CityWithPersons class.
data class CityWithPersons(
#Embedded
val city: City,
#Relation(parentColumn = "cityId", entityColumn = "cityId")
val persons: List<Person>
)
Get all CityWithPersons, then combine persons from there.
But in my scenario, there could be less than 10 person and more than 1000 cities in the database. It seems ridiculous and very inefficient to do it this way.
Other potential approaches could be:
get a full list of person then query the city with cityId one by one
embed the City in Person entitiy instead of cityId
Do it as many to many relationship. PersonWithCity will just have a cities array with one entity
I wonder which would be the best way to do it? Or a better way I didn't think of?
I wonder which would be the best way to do it? Or a better way I didn't think of?
I don't believe that the many-many relationship would provide any performance advantage as you would still need to search through one of the tables. Nor do I believe that get a full list of person then query the city with cityId one by one would be of benefit (however do you need to? (rhetorical) See the PersonAndCity that effectively does this in one go)
the obvious way is to create a CityWithPersons class
Seeing that you are looking at the issue from the Person perspective, then why not PersonWithCity class?
embed the City in Person entitiy instead of cityId :-
data class PersonWithCity(
#Embedded
val person: Person,
#Relation(parentColumn = "cityId",entityColumn = "cityId")
val city: City
)
And a Dao such as :-
#Query("SELECT * FROM person")
fun getPersonWithCity(): List<PersonWithCity>
Do you need to build everything?
Another consideration I don't believe you've considered :-
data class PersonAndCity(
val personId: Long,
val name: String,
val cityId: Int,
val cityName: String,
)
And a Dao such as
#Query("SELECT *, city.name AS cityName FROM person JOIN city ON person.cityId = city.cityId")
fun getPersonAndCity(): List<PersonAndCity>
No #Relation
Running the above 2 and the original with 100000 Person and 10000 cities (I assume more Person rows) and Person randomly linked to a City extracting all with each of the 3 methods then the results are :-
690ms (extracts 10000 Cities with ? Persons) using CityWithPersons
1560ms (extracts all 100000 Persons with the City) using PersonWithCity
1475ms (extracts all 100000 Persons with the City information rather than a City object)
Changing to 10 Persons with 1000 Cities then
49ms (CityWithPersons (10000 extracted))
2ms (PersonWithCity (10) extracted)
5ms (PersonAndCity (10 extracted))
As such, the best way is dependant upon the what you are doing. As can be seen the ratio between Cities and Persons is a factor that should be considered.
In short you should undertake testing :-
For the results above I used :-
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val db = Room.databaseBuilder(applicationContext,MyDatabase::class.java,"Mydb")
.allowMainThreadQueries()
.build()
val dao = db.getAllDao()
val people = 10
val cities = 1000
for(i in 1..cities) {
dao.insertCity(City(null,"City00" + i))
}
for(i in 1..people) {
dao.insertPerson(Person(null,"Person" + i,(1..cities).random()))
}
val TAG = "CITYPERSONINFO"
Log.d(TAG,"Starting Test 1 - Using CityWithPerson")
val usingCityWithPerson = dao.getCityWithPerson()
Log.d(TAG,"Done Test 1. Rows Extracted = " + usingCityWithPerson.size)
Log.d(TAG,"Starting Test 2 - UsingPersonAndCity")
val usingPersonWithCity = dao.getPersonWithCity()
Log.d(TAG,"Done Test 2. Rows Extracted = " + usingPersonWithCity.size)
Log.d(TAG,"Starting Test 3 - UsingPersonAndCity (no #Relation just JOIN)")
val usingPersonAndCity = dao.getPersonAndCity()
Log.d(TAG,"Done Test 3. Rows Extracted = " + usingPersonAndCity.size)
}
}
Note that I uninstall the App between runs.
I'm aware Room lets us establish 1-N relations with the #Relation keyword. Although I'd like to know if it's possible to apply conditions to this relationship.
Let's say that I have the following POJO
class UserAndPets {
#Embedded
lateinit var user: User
#Relation(entityColumn = "ownerId", parentColumn = "userId")
lateinit var pets: List<Pets>
}
interface UserDao {
#Query("SELECT * FROM users WHERE userId = :userId LIMIT 1")
fun getUserAndPetsForUserId(userId: String): UserAndPets?
}
The above method lets me query a User and all his pets. Although is there a way for me to query the User and his last 10 pets for example? Or a User and all his Pets that would be a certain type?
Thanks
If the Id field in Pets is auto-incremented then this query should give you the result you are looking for:
#Query("SELECT * FROM pets WHERE ownerId = :userId ORDER BY Id DESC LIMIT 10")
By the way, the "LIMIT 1" in your Users query is unneccessary, the query would always return a single user.