room db update multiple rows with #query - android

I would like to update multiple rows in a room database. But I don't have the objects - I have only the ids.
If I update one row I write something like this to my DAO:
#Query("UPDATE items SET place = :new_place WHERE id = :id;")
fun updateItemPlace(id:Int, new_place:String)
With multiple rows I would need something like this:
#Query("UPDATE items SET place = :new_place WHERE id = :ids;")
fun updateItemPlaces(ids:List<Int>, new_place:String)
OR
#Query("UPDATE items SET place = :new_place WHERE id IN :ids;")
fun updateItemPlaces(ids:String, new_place:String)
where I write something like '(1,4,7,15)' to my ids-String
Can someone tell me a good way to make such an update
because something like
val ids = ListOf(1,4,7,15)
ids.forEach{
itemDao.updateItemPlace(it,'new place')
}
does not seem to be a good solution

#Query("UPDATE items SET place = :new_place WHERE id IN (:ids)")
fun updateItemPlaces(ids:List<Int>, new_place:String)
But keep in mind if your list of ids contains more than 999 items SQLite will throw an exception:
SQLiteException too many SQL variables (Sqlite code 1)

This is what you need for doing your work.
#Query("Update brand_table set name = :name where id In (:ids)")
void updateBrands(List<Integer> ids, String name);

Related

matching multiple title in single query using like keyword

matching multiple title in single query using like keyword
I am trying to get all records if that matches with given titles.
below is the structure of database please see
database screenshot
when i pass single like query it returns data
#Query("SELECT * FROM task WHERE task_tags LIKE '%\"title\":\"Priority\"%'")
when i try to generate query dynamically to search multiple match it return 0 data
val stringBuilder = StringBuilder()
for (i in 0 until tags.size) {
val firstQuery = "%\"title\":\"Priority\"%"
if (i == 0) {
stringBuilder.append(firstQuery)
} else stringBuilder.append(" OR '%\"title\":\"${tags[i].title}\"%'")
}
this is function I have made
#Query("SELECT * FROM task WHERE task_tags LIKE:tagQuery ")
fun getTaskByTag(stringBuilder.toString() : String): List<Task>
The single data is fine. However, you simply cannot use the second method.
First you are omitting the space after LIKE,
Then you are omitting the full test i.e. you have task_ tags LIKE ? OR ?? when it should be task_tags LIKE ? OR task_tags LIKE ?? ....
And even then, due to the way that a parameter is handled by room the entire parameter is wrapped/encased as a single string, so the OR/OR LIKE's all become part of what is being searched for as a single test.
The correct solution, as least from a database perspective, would be to not have a single column with a JSON representation of the list of the tags, but to have a table for the tags and then, as you want a many-many relationship (a task can have many tags and a single tag could be used by many tasks) an associative table and you could then do the test using a IN clause.
As a get around though, you could utilise a RawQuery where the SQL statement is built accordingly.
As an example:-
#RawQuery
fun rawQuery(qry: SimpleSQLiteQuery): Cursor
#SuppressLint("Range")
fun getTaskByManyTags(tags: List<String>): List<Task> {
val rv = ArrayList<Task>()
val sb=StringBuilder()
var afterFirst = false
for (tag in tags) {
if (afterFirst) {
sb.append(" OR task_tags ")
}
sb.append(" LIKE '%").append(tag).append("%'")
afterFirst = true
}
if (sb.isNotEmpty()) {
val csr: Cursor = rawQuery(SimpleSQLiteQuery("SELECT * FROM task WHERE task_tags $sb"))
while (csr.moveToNext()) {
rv.add(
Task(
csr.getLong(csr.getColumnIndex("tid")),
csr.getString(csr.getColumnIndex("task_title")),
csr.getString(csr.getColumnIndex("task_tags"))))
// other columns ....
}
csr.close()
}
return rv
}
Note that the complex string with the embedded double quotes is, in this example, passed rather than built into the function (relatively simple change to incorporate) e.g. could be called using
val tasks1 = taskDao.getTaskByManyTags(listOf()) would return no tasks (handling no passed tags something you would need to decide upon)
val tasks2 = taskDao.getTaskByManyTags(listOf("\"title\":\"Priority\""))
val tasks3 = taskDao.getTaskByManyTags(listOf("\"title\":\"Priority\"","\"title\":\"Priority\"","\"title\":\"Priority\"")) obviously the tags would change
Very limited testing has been undertaken (hence just the 3 columns) but the result of running all 3 (as per the above 3 invocations) against a very limited database (basically the same row) results in the expected (as per breakpoint):-
the first returns the empty list as there are no search arguments.
the second and third both return all 4 rows as "title":"Priority" is in all 4 rows
the main reason for the 3 search args was to check the syntax of multiple args, rather than whether or not the correct selections were made.
The resultant query of the last (3 passed tags) being (as extracted from the getTaskaByManyTags function):-
SELECT * FROM task WHERE task_tags LIKE '%"title":"Priority"%' OR task_tags LIKE '%"title":"Priority"%' OR task_tags LIKE '%"title":"Priority"%'

Android SQLite dynamically insert sorting (ASD or DESC), is it possible?

I'm trying to use one method for dao (Room) to get items by a specific sorting dynamically, but compiler gives an error for the next SQL query
So it's not possible? Do I have to create duplicate methods with different sorting?
you can not perform if and else logic in the sqlite query
You should use #RawQuery like this:
#Dao
interface RawDao {
#RawQuery
fun getTestItems(SupportSQLiteQuery query): DataSource.Factory
}
// Usage of RawDao
// for example set: restOfQuery = sortBy + "ASC"
val query = SimpleSQLiteQuery(
"SELECT * FROM Items ORDER BY ?",
new Object[]{ restOfQuery });
val result = rawDao.getTestItems(query);
Or another way is that you use multiple functions for multiple orderings.

How to count child entities with room efficiently?

I have those entities in my app:
#Entity(tableName = "parents")
data class Parent(
#PrimaryKey
val name: String = "",
val numberOfChildren: Int = 0
)
#Entity(tableName = "children")
data class Children(
#PrimaryKey
val parentName: String = "",
val name: String = ""
)
I want to update numberOfChildren to count how many children per parent.
Initially I thought about fetch the children, count and insert to parents and then again to the database but It seems to me very inefficient with a lots of db calls.
what's the best/efficient way to count number of children(based on parent name) and update numberOfChildren field?
i think best way is to create trigger, which will automatically update data in Parent table on insert/delete row in Children
example of how create trigger from sqlitetutorial.net
CREATE TRIGGER [IF NOT EXISTS] trigger_name
[BEFORE|AFTER|INSTEAD OF] [INSERT|UPDATE|DELETE]
ON table_name
[WHEN condition]
BEGIN
statements;
END;
so you create 2 triggers (one for insert and another for delete) will be something like this
create trigger if not exists CHILDREN_COUNT_TRIGGER
after insert on children
begin
update parents set numberOfChildren = (select count(*) from children where parentName = NEW.parentName) where name = NEW.parentName
end;
and same for delete, but change NEW to OLD

Kotlin / SQLite delete() in batch issue

I would like to delete mutiple items from SQLite in batch basing on their ID column.
What I have is a HashMap which contains objects which one of field is pID (unique ID in DB).
So, here's my code:
/*
Delete rows from DB
*/
val selection = "${BaseColumns._ID} = ?"
// Create a list of product ID's to delete
val dbDeletor = dbHelper.writableDatabase
// Temp array to store ID's in String format
val tempIDs = ArrayList<String>()
// Loop through array of items to be deleted
for(i in ProductsRecyclerAdapter.productsToDeleteArray)
tempIDs.add(i.value.pID.toString())
// Perform deletion in DB
val deletedRowsCount = dbDeletor.delete(ProductsEntry.TABLE_NAME, selection, tempIDs.toTypedArray())
// Show snackbar with count of deleted items
Snackbar.make(mainCoordinatorLayout, "Products deleted: $deletedRowsCount", Snackbar.LENGTH_SHORT).show()
Everything works great when I'm deleting only 1 item but if tempIDs array contains 2 or more I'm receiving following Exception:
Too many bind arguments. 3 arguments were provided but the statement needs 1 arguments.
Maybe the reason is that I'm converting pID of type Long into String in order to delete rows in batch? I did not find any other solution. Please take a look and comment.
Your query looks somewhat like that:
DELETE FROM ProductsEntry.TABLE_NAME WHERE BaseColumns._ID = ?
There is only 1 argument ? but you're passing 3 values (IDs). You want to use IN statement instead, and print your params separated by comma:
// IN instead of equal to compare multiple values
val selection = "${BaseColumns._ID} IN (?)"
// your code to obtain IDs here
// .....
// combine all values into single string, ie. 1, 2, 3, 4 and wrap it as an array
val selectionArg = arrayOf(tempIDs.joinToString())
// Perform deletion in DB
val deletedRowsCount = dbDeletor.delete(ProductsEntry.TABLE_NAME, selection, selectionArg)

Sorting Secondary Collection using Primary Sorted Collection's field

I have an unsorted List of Users and a sorted list of Users id. Id is a string.
I want to sort first list by second. How to do that in Kotlin?
data class User(val name : String, val id : String)
val unsorted = listOf<User>(
User("Max", "b12s11"),
User("Joe", "dj1232"),
User("Sam", "23d112"),
User("Tom", "k213i1")
)
val sorted = listOf<String>(
"dj1232",
"b12s11",
"k213i1",
"23d112"
)
// what I need
val result = listOf<User>(
User("Joe", "dj1232"),
User("Max", "b12s11"),
User("Tom", "k213i1"),
User("Sam", "23d112")
)
Shorter solution:
val result = unsorted.sortedBy { sorted.indexOf(it.id) }
Although the other answers show a solution to your problem, it seems to me that a Map<String, User> might better fit the purpose, e.g.:
val usersByid = unsorted.associateBy { it.id }
val result = sorted.mapNotNull {
usersById[it]
}
I assume that every id is only once in the list, therefore I used associateBy. Otherwise it wouldn't be an id for me ;-)
The main difference between this solution and others is that this solution only returns the entries that are also in the sorted-list. Note that if you have users for which you have no id in the sorted-list, this solution omits them, whereas other solutions put those entries at the front of the list. Depends on what you really want.
It could be that this solution is more efficient than the others. Accessing the Map should be much faster then reiterating all the entries over and over again (which both indexOf and first basically do).
I don't know of any Kotlin syntax for doing this, sorting one list by another, but this solution should work for you (the way I understood this question, was that you want to sort according to the Id's in sorted):
val correctList = arrayListOf<User>()
sorted.forEach { sortedId ->
correctList.add(unsorted.first {
it.id == sortedId
})
}
It iterates over your sorted list of Id's and takes the item in the first list (unsorted) which matches that ID and adds it to correctList
Edit: see answer from #Andrei Tanana for a better kotlin answer than mine : sort unsorted collection with another sorted collection's field it's pretty cool :D
Edit2: Thanks to #Roland for pointing out, I can simplify my answer even further with :
val correctList = sorted.map { sortedId -> unsorted.first { it.id == sortedId } }

Categories

Resources