This is very tricky for me because I don't know if it can be done with a SQLite query or by recursively calling some function. It gets complicated because I also use Flow
and LiveData
with ViewModel
Basically, my entity looks like this
@Entity(
foreignKeys = [
ForeignKey(
entity = Item::class,
parentColumns = [ "id" ],
childColumns = [ "parentId" ],
onDelete = ForeignKey.CASCADE
),
],
indices = [
Index(value = [ "parentId" ]),
Index(value = [ "sectionId" ])
],
)
class Item(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
var parentId: Long? = null,
val name: String
)
Now, imagine I have 5 items, each one parent of the next one
Item (id = 1, parentId = null, name = "Root item")
Item (id = 2, parentId = 1, name = "Item 2")
Item (id = 3, parentId = 2, name = "Item 3")
Item (id = 4, parentId = 3, name = "Item 4")
Item (id = 5, parentId = 4, name = "Item 5")
So, what I want is to get Item 5 and all its ancestors, that is Item 4 because it is its parent, Item 3 because it's the parent of Item 4 and so on to the first one (which has no parent, therefore it is where we stop)
At the moment I have this mess in my DAO and I am kinda stuck. How do you think this can be achieved?
@Dao
interface ItemDao {
@Query("SELECT * FROM Item WHERE itemId = :itemId")
fun getById(itemId: Long): Flow<Item>
fun getAncestors(itemId: Long): Flow<List<Item>> = flow {
val item = getById(itemId)
item.collect { it ->
if (it.parentId != null) {
val parent = getAncestors(it.parentId!!)
val items = combine(item, parent ) { i, p ->
val allItems: ArrayList<Item> = arrayListOf()
allItems.add(i)
allItems.add(p)
allItems
}
emitAll(items)
} else {
emitAll(item)
}
}
}
}
This does not work (I don't get everything) and it's most likely because of the wrong use of flows. I need an alternative or a little help to understand this and get unstuck
If I understand correctly (I think you want predecessors(parents) of 5, rather than ancestors (children)).
So 5 will get 4, 4 will get 3 .... until 1 which has no parent. Then I believe the following will work:-
@Query("WITH cte1(id,parentId,name) AS (SELECT * FROM item WHERE id=:itemId UNION ALL SELECT parentId,(SELECT parentId FROM item WHERE item.id = cte1.parentId),(SELECT name FROM item WHERE item.id = cte1.parentId) FROM cte1 WHERE parentId IS NOT NULL LIMIT 10 /*<<<<< just in case limit to 10 iterations */) SELECT * FROM cte1;")
fun getPredecessorsOfId(itemId): Flow<List<Item>>
As an example of the above from an SQLite perspective (i.e. ignoring Room) then consider the following demo with comments :-
DROP TABLE IF EXISTS item;
/* Create the demo table */
CREATE TABLE IF NOT EXISTS item(id INTEGER PRIMARY KEY, parentId INTEGER, name TEXT);
INSERT INTO item VALUES (1,null,'Root Item'),(2,1,'item2'),(3,2,'item3'),(4,3, 'item4'),(5,4,'item5');
SELECT * FROM item; /* Output 1 the item table in full */
/* Uses a Common Table Expression which is recursive due to the UNION with itself */
/* and will loop forever unless ended.*/
/* Common Table Expression can be considered as a temporary table that exists only for the duration of the query */
/* In this case the end is when the parentId is null due to WHERE parentId IS NOT NULL */
/* However, as a precaution, LIMIT 10 will limit to 10 iterations */
/* Output 2 example 1 */
WITH cte1(id,parentId,name) AS (
SELECT * FROM item WHERE id = 5
UNION ALL SELECT
parentId, /* the id is the parentId of the previous row */
(SELECT parentId FROM item WHERE item.id = cte1.parentId), /* parentId is obtained from the item table using the parentId of the previous row */
(SELECT name FROM item WHERE item.id = cte1.parentId) /* likewise for the name */
FROM cte1
WHERE parentId IS NOT NULL
LIMIT 10 /*<<<<< just in case limit to 10 iterations */
)
SELECT * FROM cte1;
/* Output 3 using an id mid-list */
WITH cte1(id,parentId,name) AS (
SELECT * FROM item WHERE id = 3
UNION ALL SELECT
parentId,
(SELECT parentId FROM item WHERE item.id = cte1.parentId),
(SELECT name FROM item WHERE item.id = cte1.parentId)
FROM cte1
WHERE parentId IS NOT NULL
LIMIT 10
)
SELECT * FROM cte1;
DROP TABLE IF EXISTS item; /* Cleanup demo environment */
The results (3 outputs) are :-
You may wish to refer to https://sqlite.org/lang_with.html for a more in-depth explanation of CTE's and the WITH clause and recursion.