使用 Room 和 Flows 从 SQLite table 获取祖先树
Get tree of ancestors from a SQLite table with Room and Flows
这对我来说非常棘手,因为我不知道它是否可以通过 SQLite 查询或递归调用某个函数来完成。它变得复杂,因为我还使用 Flow
和 LiveData
以及 ViewModel
基本上,我的实体看起来像这样
@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
)
现在,假设我有 5 个项目,每个都是下一个的父项
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")
所以,我想要的是获取 Item 5 及其所有祖先,即 Item 4 因为它是它的父项, 项目 3 因为它是 项目 4 的父级,依此类推到第一个(没有父级,因此它是我们停止的地方)
目前,我的 DAO 中出现了混乱,我有点卡住了。您认为如何实现?
@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)
}
}
}
}
这是行不通的(我什么都不懂),很可能是因为错误地使用了流程。我需要一个替代方案或一点帮助来理解这一点并摆脱困境
如果我没理解错的话(我想你想要5的前辈(parents),而不是祖先(children)).
所以 5 会得到 4,4 会得到 3 .... 直到 1 没有 parent。那么我相信以下会起作用:-
@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>>
- 请注意,以上内容尚未在项目中编码,因此未经测试,因此可能包含一些错误。
- 这样做的好处是所有操作都在单个查询/数据库访问中完成(也许您的问题之一是递归返回流程)
从 SQLite 的角度(即忽略 Room)作为上述示例,然后考虑以下带有注释的演示:-
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 */
结果(3 个输出)是:-
- 包含您的数据的项目 table
- 项目 5 及其前身
- 项目 3 及其前身
您可能希望参考 https://sqlite.org/lang_with.html 以获得关于 CTE 和 WITH 子句和递归的更多 in-depth 解释。
这对我来说非常棘手,因为我不知道它是否可以通过 SQLite 查询或递归调用某个函数来完成。它变得复杂,因为我还使用 Flow
和 LiveData
以及 ViewModel
基本上,我的实体看起来像这样
@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
)
现在,假设我有 5 个项目,每个都是下一个的父项
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")
所以,我想要的是获取 Item 5 及其所有祖先,即 Item 4 因为它是它的父项, 项目 3 因为它是 项目 4 的父级,依此类推到第一个(没有父级,因此它是我们停止的地方)
目前,我的 DAO 中出现了混乱,我有点卡住了。您认为如何实现?
@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)
}
}
}
}
这是行不通的(我什么都不懂),很可能是因为错误地使用了流程。我需要一个替代方案或一点帮助来理解这一点并摆脱困境
如果我没理解错的话(我想你想要5的前辈(parents),而不是祖先(children)).
所以 5 会得到 4,4 会得到 3 .... 直到 1 没有 parent。那么我相信以下会起作用:-
@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>>
- 请注意,以上内容尚未在项目中编码,因此未经测试,因此可能包含一些错误。
- 这样做的好处是所有操作都在单个查询/数据库访问中完成(也许您的问题之一是递归返回流程)
从 SQLite 的角度(即忽略 Room)作为上述示例,然后考虑以下带有注释的演示:-
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 */
结果(3 个输出)是:-
- 包含您的数据的项目 table
- 项目 5 及其前身
- 项目 3 及其前身
您可能希望参考 https://sqlite.org/lang_with.html 以获得关于 CTE 和 WITH 子句和递归的更多 in-depth 解释。