【问题标题】:Query one to many relationship Android Room查询一对多关系Android Room
【发布时间】:2021-08-01 11:47:03
【问题描述】:

我有一对多的关系。一个Parent 可以容纳许多Child 实体。 孩子们拥有date 的属性,它的类型是Instant

我创建了一个结合这两个实体的类:

data class Combined(
    @Embedded val parent: Parent,
    @Relation(
        parentColumn = "elementId",
        entityColumn = "Id"
    )
    val children: List<Child>
)

我知道我可以像这样检索所有 Combined 元素:

@Transaction
@Query("SELECT * FROM Parent")
fun getCombined(): Flow<List<Combined>>

有什么方法可以检索一个List&lt;Combined&gt;,其中只包括特定日期范围内的孩子?

【问题讨论】:

    标签: android-sqlite android-room android-jetpack android-database android-room-relation


    【解决方案1】:

    有什么方法可以检索只包含特定日期范围内的孩子的列表?

    没那么容易。 @Relation 的工作原理是根据第二个查询获取父级的所有子级(因此推荐使用 @Transaction)。过滤仅适用于父级的选择。

    问题是,如果你使用类似的东西:-

    SELECT * FROM parent JOIN child ON child.Id = parent.elementId WHERE date BETWEEN fromDate and toDate;
    

    返回的是一个列表,其中每行包含父项和单个子项。要提取父级和所有过滤后的子级,需要处理列表以挑出父级及其子级。

    根据可用的代码,这里有一个示例。

    • 一个例外是 Id 列,它通常是子项的 ID,因此添加了一个 parentId 列来保存父项的 ID。

    • 另一个例外是,为了方便/简洁,日期只是一个字符串 (TEXT),而不是需要 @TypeConverter 的 Instant。

    • 第三个例外是使用了伴生对象(将在适当的时候显示)。

    先新建一个POJO,Filter用于提取过滤后的列表(父子各一行)

    data class Filter(
        @Embedded
        val parent: Parent,
        @Embedded
        val child: Child
    )
    

    下一个合适的 Dao getFiltered 用于查询(和上面差不多):-

    @Query("SELECT * FROM parent JOIN child ON child.parentId = parent.elementId WHERE date BETWEEN :fromDate AND :toDate")
        abstract fun getFiltered(fromDate: String, toDate:String): List<Filter>
    
    • 请注意,这将返回 Filter 的列表而不是 组合s
    • 的列表

    Child 与伴随对象放在一起,以便从 Filters 列表中构建 Combined 列表:-

    @Entity
    data class Child(
        @PrimaryKey
        var id: Long? = null,
        var parentId: Long,
        @NotNull
        var childName: String,
        var date: String
    ) {
    
        companion object {
    
            fun buildParentsWithChildren(filter: List<Filter>): List<Combined> {
                var rv: ArrayList<Combined> = arrayListOf()
                if (filter.size < 1) return rv
                for (f: Filter in filter) {
                    addChild(rv, getOrAddParent(rv, f), f)
                }
                return rv
            }
    
            private fun getOrAddParent(built: ArrayList<Combined>, f: Filter): Int {
                for (i in 0..(built.size-1)) {
                    if (built.get(i).parent.parentName == f.parent.parentName) {
                        return i
                    }
                }
                var newCombined: Combined = Combined(f.parent, emptyList())
                built.add(newCombined)
                return built.size - 1
            }
    
            private fun addChild(built: ArrayList<Combined>, parentIx: Int, f: Filter) {
                var currentChildren: ArrayList<Child> = arrayListOf<Child>()
                currentChildren.addAll(built.get(parentIx).children)
                currentChildren.add(f.child)
                built[parentIx] = Combined(parent = built.get(parentIx).parent, currentChildren)
            }
        }
    }
    

    示例

    这是使用上述方法的示例。

    首先它构建一些数据,3 个父母 5 个孩子(3 个到第一个父母,2 个到第二个父母,0 个到第三个父母):-

    然后它使用查询来提取一些数据并将其转换为列表。然后遍历列表输出到Log。

    这是来自 Activity 的代码:-

        db = TheDatabase.getInstance(this)
        dao = db.getAllDao()
    
        var p1 = dao.insert(Parent(parentName = "Parent1"))
        var p2 = dao.insert(Parent(parentName = "Parent2"))
        var p3 = dao.insert(Parent(parentName = "Parent3"))
    
        dao.insert(Child(parentId = p1,childName = "Child1",date = "2000-01-01"))
        dao.insert(Child(parentId = p1,childName = "Child2",date = "2003-01-01"))
        dao.insert(Child(parentId = p1,childName = "Child3",date = "2005-01-01"))
        dao.insert(Child(parentId = p2,childName = "Child4",date = "2006-01-01"))
        dao.insert(Child(parentId = p2,childName = "Child5",date = "2007-01-01"))
    
    
        for(cmbnd: Combined in Child.buildParentsWithChildren(dao.getFiltered("2004-12-31","2010-01-01"))) {
            Log.d("DBINFO","Parent is ${cmbnd.parent.parentName}")
            for(c: Child in cmbnd.children)
                Log.d("DBINFO","Child is ${c.childName} date is ${c.date}")
        }
    

    以及结果:-

    2021-08-02 08:38:50.426 D/DBINFO: Parent is Parent1
    2021-08-02 08:38:50.426 D/DBINFO: Child is Child3 date is 2005-01-01
    2021-08-02 08:38:50.426 D/DBINFO: Parent is Parent2
    2021-08-02 08:38:50.427 D/DBINFO: Child is Child4 date is 2006-01-01
    2021-08-02 08:38:50.427 D/DBINFO: Child is Child5 date is 2007-01-01
    

    即第一个父母的 3 个孩子中只有 1 个的日期在 2005 年到 2009 年之间。但是,第二个父母的两个孩子都适合日期范围。第三个父母什么都没有。

    【讨论】:

    • 感谢您的全面回答!我实际上更愿意让 Parent 3 有一个空列表,而不是不返回它。您能否修改您的答案以包含此内容?
    • @shko,那将不是一个 1-many 的查询,因为没有关系。您必须将其基于提取父母的列表,然后根据过滤器为每个父母提取每个父母的孩子,如果没有与过滤器匹配,则为空列表。确实这应该是一个不同的问题,但我可能会更新答案。
    • @shko 我已经添加了补充答案。
    【解决方案2】:

    附加

    关于评论:-

    我实际上更愿意让 Parent 3 带有一个空列表,而不是不返回它。

    实际上,就数据库而言,这相对困难/复杂,因为您要求考虑“非关系”(因为需要更好的术语)。

    但是,使用不同的方法,即获取所有父级,然后为每个父级获取过滤后的子级(如果适用,则无)。

    因此修改上述内容以包括:-

    在@Dao AllDao

    1. 添加一个查询以获取所有父母(您可能有这个)作为一个列表。
    2. 添加查询以将过滤后的子项作为列表获取

    例如:-

    @Query("SELECT * FROM parent")
    abstract fun getAllParents(): List<Parent>
    
    @Query("SELECT * FROM child WHERE parentId = :parentId AND date BETWEEN :fromDate AND :toDate")
    abstract  fun getFilteredChildrenForAParent(parentId: Long, fromDate: String, toDate: String): List<Child>
    

    如果 @Dao AllDao抽象类 而不是接口。 3.添加一个提取所有父母的函数,通过他们循环获得过滤的孩子,例如:-

    fun getAllParentsWithFilteredChildren(fromDate: String, toDate: String): List<Combined> {
        var rv: ArrayList<Combined> = arrayListOf()
        for(p: Parent in this.getAllParents()) {
            rv.add(Combined(parent = p,this.getFilteredChildrenForAParent(p.elementId!!,fromDate, toDate)))
        }
        return rv
    }
    

    否则(如果您不希望 @Dao 成为抽象类并因此成为接口)在其他地方(在子数据类中)包含一个函数,例如:-

        fun getAllParentsWithFilteredChildren(dao: AllDao, fromDate: String, toDate: String): List<Combined> {
            var rv: ArrayList<Combined> = arrayListOf()
            for (p: Parent in dao.getAllParents()) {
                rv.add(Combined(p,dao.getFilteredChildrenForAParent(p.elementId!!,fromDate,toDate)))
            }
            return rv
        }
    
    • 注意细微差别,将 dao 传递给函数

    结果

    修改第一个示例中的活动代码以包括:-

        for(cmbnd: Combined in dao.getAllParentsWithFilteredChildren("2004-12-31","2010-01-01")) {
            Log.d("DBINFOV2","Parent is ${cmbnd.parent.parentName}")
            for(c: Child in cmbnd.children)
                Log.d("DBINFOV2","Child is ${c.childName} date is ${c.date}")
        }
    
        for (cmbnd: Combined in Child.getAllParentsWithFilteredChildren(dao,"2004-12-31","2010-01-01")) {
            Log.d("DBINFOV3","Parent is ${cmbnd.parent.parentName}")
            for(c: Child in cmbnd.children)
                Log.d("DBINFOV3","Child is ${c.childName} date is ${c.date}")
        }
    

    然后是结果(忽略第一个示例的结果)然后 logcat 包括:-

    2021-08-03 08:33:30.812 D/DBINFOV2: Parent is Parent1
    2021-08-03 08:33:30.812 D/DBINFOV2: Child is Child3 date is 2005-01-01
    2021-08-03 08:33:30.812 D/DBINFOV2: Parent is Parent2
    2021-08-03 08:33:30.812 D/DBINFOV2: Child is Child4 date is 2006-01-01
    2021-08-03 08:33:30.812 D/DBINFOV2: Child is Child5 date is 2007-01-01
    2021-08-03 08:33:30.812 D/DBINFOV2: Parent is Parent3
    
    
    2021-08-03 08:33:30.817 D/DBINFOV3: Parent is Parent1
    2021-08-03 08:33:30.817 D/DBINFOV3: Child is Child3 date is 2005-01-01
    2021-08-03 08:33:30.817 D/DBINFOV3: Parent is Parent2
    2021-08-03 08:33:30.817 D/DBINFOV3: Child is Child4 date is 2006-01-01
    2021-08-03 08:33:30.817 D/DBINFOV3: Child is Child5 date is 2007-01-01
    2021-08-03 08:33:30.817 D/DBINFOV3: Parent is Parent3
    
    • 即包括没有孩子的 Parent3

    【讨论】:

    • 谢谢迈克!我想知道是否可以让 getAllParentsWithFilteredChildren 返回 Flow&lt;List&lt;Combined&gt;&gt; 。最后,我想使用 LiveData 来获得有关更改的通知
    • @shko 当然会/应该。但是,我从未涉足 LiveData 领域,因此我无法在该领域提供帮助,但我猜这只是更改签名并返回 Flow 的问题。
    猜你喜欢
    • 1970-01-01
    • 2022-01-13
    • 1970-01-01
    • 1970-01-01
    • 2021-02-23
    • 2020-07-27
    • 2011-08-08
    • 2018-06-23
    • 2018-07-04
    相关资源
    最近更新 更多