Как запросить и отфильтровать объект отношения в базе данных комнат

#sql #android-room

Вопрос:

Например:

Пользователь:

 @Entity(tableName = "user")
data class UserEntity(
    @PrimaryKey
    @ColumnInfo(name = "id") val id: String,
    @ColumnInfo(name = "username") val username: String,
    @ColumnInfo(name = "name") val name: String,
 

Публикация:

 @Entity(
    tableName = "post",
    foreignKeys = [
        ForeignKey(
            entity = UserEntity::class,
            parentColumns = ["id"],
            childColumns = ["user_id"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        )
    ],
    indices = [Index(value = ["user_id"])]
)
data class PostEntity(
    @PrimaryKey
    @ColumnInfo(name = "id") var id: String, 
    @ColumnInfo(name = "user_id") var userId: String,
    @ColumnInfo(name = "body") val body: String,
    @ColumnInfo(name = "like") val like: Int,
    @ColumnType(name = "type") val type: String,
)
 

Данные

 data class Data(
    @Embedded
    val user: UserEntity,
    @Relation(parentColumn = "id", entityColumn = "user_id")
    val post: List<PostEntity> = emptyList(),
)
 

если я использую SELECT * FROM user , у меня есть данные о желании(пользователь и все сообщения), но как я могу отфильтровать сообщение для определенного типа, например WHERE post.type = 'sth' , возможно ли это?

Ответ №1:

но как я могу отфильтровать сообщение для определенного типа,

Это зависит от того, что именно вы хотите отфильтровать. Вам могут понадобиться объекты данных, соответствующие фильтру, но со всеми записями (независимо от типа), и в этом случае вы можете использовать:-

 @Transaction
@Query("SELECT * FROM user JOIN post ON user.id = user_id WHERE post.type = :type")
abstract fun getAllDataFiltered(type: String): List<Data>
 
  • где вы могли бы использовать что-то вроде var mylist = yourdao.getAllDataFiltered("sth")

Однако, поскольку столбцы идентификаторов post и user имеют имена id, возникает неопределенность (идентификатор пользователя становится идентификатором post, и, таким образом, базовые объекты post не извлекаются).

Если вы измените идентификатор сообщения на :-

 @Entity(
    tableName = "post",
    foreignKeys = [
        ForeignKey(
            entity = UserEntity::class,
            parentColumns = ["id"],
            childColumns = ["user_id"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        )
    ],
    indices = [Index(value = ["user_id"])]
)
data class PostEntity(
    @PrimaryKey
    @ColumnInfo(name = "postid") var id: String, //<<<<<<<<<< CHANGED
    @ColumnInfo(name = "user_id") var userId: String,
    @ColumnInfo(name = "body") val body: String,
    @ColumnInfo(name = "like") val like: Int,
    @ColumnInfo(name = "type") val type: String
)
 

Затем двусмысленность устраняется, и возвращаемые объекты данных включают ВСЕ записи для соответствующих пользователей, имеющих тип записи sth.

Если вы хотите, чтобы возвращались только объекты данных, в которых есть только отфильтрованные записи, вам придется обойти методику Комнаты по возвращению ПОЛНЫХ/ПОЛНЫХ связанных объектов.

Если вы сделаете @Dao класс абстрактным классом, а не интерфейсом, то у вас будет @Query такой :-

 @Query("SELECT * FROM post WHERE user_id=:userid AND type=:type")
abstract fun getPostsPerUserFiltered(userid: String, type: String): List<PostEntity>
 

наряду с такой функцией, как :-

 fun getFullyFiltered(type: String): List<Data> {
    var rv: ArrayList<Data> = arrayListOf()
    for(d: Data in getAllDataFiltered(type)) {
        rv.add(Data(d.user,post = getPostsPerUserFiltered(d.user.id,type)))
    }
    return rv
}
 

Это фильтрует возвращаемые объекты данных, но затем удаляет весь список записей (т. Е. каждую запись независимо от фильтра), а затем применяет отфильтрованные записи.

Если вам нужны все пользователи, но только с соответствующими сообщениями (и, следовательно, потенциально пустым списком сообщений), у вас может быть такая функция, как :-

 fun getAllFilteredData(type: String): List<Data> {
    var rv: ArrayList<Data> = arrayListOf()
    for(u: UserEntity in getUsers()) {
        rv.add(Data(user = u, getPostsPerUserFiltered(u.id,type)))
    }
    return rv
}
 

т. е. фильтрация не применяется к пользователям, а только к публикациям.

Используя вышесказанное (отмечая использование измененного имени столбца (postid вместо идентификатора)), затем рассмотрите следующее (был использован довольно стандартный класс @Database):-

 class MainActivity : AppCompatActivity() {
    lateinit var db: TheDatabase
    lateinit var dao: AllDao
    val TAG: String = "DBINFO"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        db = TheDatabase.getInstance(this)
        dao = db.getAllDao()

        dao.insert(UserEntity(id = "User1","User001","Mary"))
        dao.insert(UserEntity(id = "User2",username = "User002",name = "Sue"))
        dao.insert(UserEntity(id = "User3",username = "User003",name = "Tom"))

        dao.insert(PostEntity(id = "post1",userId = "User1",body = "post1 blah",type = "xxx",like = 0))
        dao.insert(PostEntity(id ="post2", userId = "User2",body ="post2 blah", type = "sth",like = 1))
        dao.insert(PostEntity(id = "post3", userId = "User1", body = "post3 blah", type = "sth", like = 3))

        /*
            No filtering applied
        */
        for(d: Data in dao.getAllData()) {
            logData(d,"ALL")
        }
        /*
            Return FULL Data objects (i.e. with ALL posts) but only those that
            have a post or posts that match the filter
        */
        for (d: Data in dao.getAllDataFiltered("sth")) {
            logData(d,"JOIN")
        }
        /*
            Return partial Data objects, but only for those that
            have a post that matches the type
         */
        for (d: Data in dao.getFullyFiltered("sth")) {
            logData(d,"FULL")
        }

        /*
            Return all partial Data Objects but with partial posts.

         */
        for (d: Data in dao.getAllFilteredData("sth")) {
            logData(d,"POST")
        }
    }

    private fun logData(data: Data,tagSuffix: String) {
        Log.d(TAG   tagSuffix,"Data for user ${data.user.id}, Name is ${data.user.name} etc")
        for (p: PostEntity in data.post) {
            Log.d(TAG   tagSuffix,"t Post is ${p.id} Type is ${p.type} body is:-ntt${p.body}")
        }
    }
}
 

Результаты, выводимые в журнал, являются:-

Никакой фильтрации вообще:-

 2021-09-17 11:22:00.722 D/DBINFOALL: Data for user User1, Name is Mary etc
2021-09-17 11:22:00.722 D/DBINFOALL:     Post is post1 Type is xxx body is:-
            post1 blah
2021-09-17 11:22:00.723 D/DBINFOALL:     Post is post3 Type is sth body is:-
            post3 blah
2021-09-17 11:22:00.723 D/DBINFOALL: Data for user User2, Name is Sue etc
2021-09-17 11:22:00.723 D/DBINFOALL:     Post is post2 Type is sth body is:-
            post2 blah
2021-09-17 11:22:00.723 D/DBINFOALL: Data for user User3, Name is Tom etc
 

Фильтруются только сообщения пользователей, так как Комната получает все сообщения для пользователей
:-

 2021-09-17 11:22:00.728 D/DBINFOJOIN: Data for user User2, Name is Sue etc
2021-09-17 11:22:00.728 D/DBINFOJOIN:    Post is post2 Type is sth body is:-
            post2 blah
2021-09-17 11:22:00.728 D/DBINFOJOIN: Data for user User1, Name is Mary etc
2021-09-17 11:22:00.728 D/DBINFOJOIN:    Post is post1 Type is xxx body is:-
            post1 blah
2021-09-17 11:22:00.728 D/DBINFOJOIN:    Post is post3 Type is sth body is:-
            post3 blah
 

Полностью отфильтрован :-

 2021-09-17 11:22:00.738 D/DBINFOFULL: Data for user User2, Name is Sue etc
2021-09-17 11:22:00.738 D/DBINFOFULL:    Post is post2 Type is sth body is:-
            post2 blah
2021-09-17 11:22:00.738 D/DBINFOFULL: Data for user User1, Name is Mary etc
2021-09-17 11:22:00.738 D/DBINFOFULL:    Post is post3 Type is sth body is:-
            post3 blah
 

Все пользователи, но с отфильтрованными сообщениями

 2021-09-17 11:22:00.744 D/DBINFOPOST: Data for user User1, Name is Mary etc
2021-09-17 11:22:00.744 D/DBINFOPOST:    Post is post3 Type is sth body is:-
            post3 blah
2021-09-17 11:22:00.744 D/DBINFOPOST: Data for user User2, Name is Sue etc
2021-09-17 11:22:00.744 D/DBINFOPOST:    Post is post2 Type is sth body is:-
            post2 blah
2021-09-17 11:22:00.744 D/DBINFOPOST: Data for user User3, Name is Tom etc
 

Если столбец PostEntity снова изменен на идентификатор, то результаты будут

:-

 2021-09-17 11:27:00.661 D/DBINFOALL: Data for user User1, Name is Mary etc
2021-09-17 11:27:00.661 D/DBINFOALL:     Post is post1 Type is xxx body is:-
            post1 blah
2021-09-17 11:27:00.661 D/DBINFOALL:     Post is post3 Type is sth body is:-
            post3 blah
2021-09-17 11:27:00.661 D/DBINFOALL: Data for user User2, Name is Sue etc
2021-09-17 11:27:00.662 D/DBINFOALL:     Post is post2 Type is sth body is:-
            post2 blah
2021-09-17 11:27:00.662 D/DBINFOALL: Data for user User3, Name is Tom etc
2021-09-17 11:27:00.664 D/DBINFOJOIN: Data for user post2, Name is Sue etc
2021-09-17 11:27:00.664 D/DBINFOJOIN: Data for user post3, Name is Mary etc
2021-09-17 11:27:00.672 D/DBINFOFULL: Data for user post2, Name is Sue etc
2021-09-17 11:27:00.672 D/DBINFOFULL: Data for user post3, Name is Mary etc
2021-09-17 11:27:00.676 D/DBINFOPOST: Data for user User1, Name is Mary etc
2021-09-17 11:27:00.676 D/DBINFOPOST:    Post is post3 Type is sth body is:-
            post3 blah
2021-09-17 11:27:00.676 D/DBINFOPOST: Data for user User2, Name is Sue etc
2021-09-17 11:27:00.676 D/DBINFOPOST:    Post is post2 Type is sth body is:-
            post2 blah
2021-09-17 11:27:00.677 D/DBINFOPOST: Data for user User3, Name is Tom etc
 
  • т. е. Обратите внимание, как пользователь является идентификатором сообщения, и, следовательно, никаких базовых сообщений.
  • тебе это может пригодиться @Embedded(prefix = "a_suitable_prefix") . Однако затем вам придется изменить имена столбцов Пользователя (таблица с префиксами) в запросе, используя AS гораздо более простое использование неоднозначного имени столбца.
  • 4-й, возвращающий всех пользователей, но только с отфильтрованными сообщениями, не затрагивается, поскольку он не использует POJO данных, в котором неоднозначность приводит к тому, что идентификатор пользователя является идентификатором записи.

Комментарии:

1. да, это сработало!, но есть ли какие-либо инструкции, которые выполняют эту работу, не делая это вручную? я имею в виду один вызов базы данных.

2. Если предположить, что полностью отфильтрованный работал, то нет (по крайней мере, на данный момент). Я полагаю, что причина в том, что Комната ориентирована на объекты и что база данных-это просто носитель информации, и получение неполных объектов (как в вашем случае не все сообщения) считается неправильным, возможно, под предлогом «сделайте это с объектами».