使用 Room 和 Flows 从 SQLite table 获取祖先树

Get tree of ancestors from a SQLite table with Room and Flows

这对我来说非常棘手,因为我不知道它是否可以通过 SQLite 查询或递归调用某个函数来完成。它变得复杂,因为我还使用 FlowLiveData 以及 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 解释。