リレーションを使用したAndroidRoomの検索とフィルタリング、複数のテーブルのクエリ

Aug 17 2020

プルリクエストを発行する場合は、以下のレポにコメントを含めてください。

ポケモンアプリ用の部屋データベースを作成しました。ポケモン名とポケモンタイプに基づいてデータベースをフィルタリングおよび検索できるようにしたいです。

ポケモンエンティティ用のテーブル、ポケモンタイプエンティティ用のテーブル、およびポケモンタイプジョインエンティティ用のジャンクションテーブルがあります。また、ポケモンエンティティを埋め込み、これとポケモンタイプエンティティのリストとの関係を定義するデータクラスPokemonWithTypesもあります。

ポケモンエンティティ:

    @TypeConverters(RoomStringListConverter::class)
    @Entity
    data class Pokemon(
        @NotNull
        @PrimaryKey
        @ColumnInfo(name = POKEMON_ID)
        var id: Int,
    
        @ColumnInfo(name = POKEMON_NAME)
        var name: String,
    
        @ColumnInfo(name = POKEMON_URL)
        var url: String,
    
        @ColumnInfo(name = POKEMON_WEIGHT)
        val weight: Int,
    
        @ColumnInfo(name = POKEMON_HEIGHT)
        val height: Int,
    
        @ColumnInfo(name = POKEMON_SPECIES)
        var species: String,
    
        @ColumnInfo(name = POKEMON_MOVES)
        val moves: List<String>
    
    ) 

    const val POKEMON_ID: String = "pokemon_id"
    const val POKEMON_NAME: String = "pokemon_name"
    const val POKEMON_URL: String = "pokemon_url"
    const val POKEMON_HEIGHT: String = "pokemon_height"
    const val POKEMON_WEIGHT: String = "pokemon_weight"
    const val POKEMON_MOVES: String = "pokemon_moves"
    const val POKEMON_SPECIES: String = "pokemon_species"

PokemonTypeエンティティ:

    @Entity
    data class PokemonType (
    
        @NotNull
        @PrimaryKey
        @ColumnInfo(name = POKEMON_TYPE_ID)
        var id: Int,
    
        @ColumnInfo(name = POKEMON_TYPE_NAME)
        var name: String,
    
        @ColumnInfo(name = POKEMON_TYPE_SLOT)
        var slot: Int
    
    )

    const val POKEMON_TYPE_ID: String = "type_id"
    const val POKEMON_TYPE_NAME: String = "type_name"
    const val POKEMON_TYPE_SLOT: String = "type_slot"

PokemonTypesJoinエンティティ:

    @Entity(primaryKeys = [POKEMON_ID, POKEMON_TYPE_ID])
    class PokemonTypesJoin(
        @NotNull
        @ColumnInfo(name = POKEMON_ID, index = true)
        val pokemon_id: Int,
    
        @NotNull
        @ColumnInfo(name = POKEMON_TYPE_ID, index = true)
        val pokemon_type_id: Int
    
    )
    
    const val POKEMON_ID: String = "id"
    const val POKEMON_TYPE_ID: String = "type_id"

PokemonWithTypesクラス

    data class PokemonWithTypes(
        @Embedded
        val pokemon: Pokemon,
        @Relation(
            parentColumn = Pokemon.POKEMON_ID,
            entity = PokemonType::class,
            entityColumn = PokemonType.POKEMON_TYPE_ID,
            associateBy = Junction(
                value = PokemonTypesJoin::class,
                parentColumn = PokemonTypesJoin.POKEMON_ID,
                entityColumn = PokemonTypesJoin.POKEMON_TYPE_ID
            )
        )
        val types: List<PokemonType>
    )

この構造が与えられると、次のクエリを使用してすべてのPokemonWithTypesをpokemon_nameで取得および検索できます。

@Transaction
@Query("SELECT * FROM pokemon WHERE pokemon_name LIKE :search ORDER BY pokemon_id ASC")
fun getPokemonWithTypes(search: String?): LiveData<List<PokemonWithTypes>>

しかし、PokemonWithTypes.typesのいずれかがフィルターリスト内の特定のタイプと一致する場合にのみPokemonWithTypesを返すフィルター(文字列のリスト)を追加するにはどうすればよいですか?

したがって、3つのポケモンが与えられます(簡潔にするために一部のデータは削除されています)

PokemonWithTypes(pokemon=Pokemon(id=1, name=bulbasaur, types=[PokemonType(id=4, name=poison, slot=2), PokemonType(id=12, name=grass, slot=1)])
PokemonWithTypes(pokemon=Pokemon(id=4, name=charmander, types=[PokemonType(id=10, name=fire, slot=2), PokemonType(id=12, name=grass, slot=1)])
PokemonWithTypes(pokemon=Pokemon(id=7, name=squirtle, types=[PokemonType(id=11, name=water, slot=2), PokemonType(id=12, name=grass, slot=1)])

私は現在すべてのポケモンを入手しており、pokemon_nameで検索できますが、水タイプまたは草タイプだけを表示できるようにしたいと思います。

このようなクエリを使用して、文字列のリストではなく文字列をフィルタリングしてみました

@Transaction
@Query("SELECT * FROM pokemon, pokemonType WHERE type_name LIKE :filter AND pokemon_name LIKE :search ORDER BY pokemon_id ASC")
fun getPokemonWithTypes(search: String?, filter: String): LiveData<List<PokemonWithTypes>>

しかし、それは機能しませんでした

あなたはここで完全なものをチェックすることができます https://github.com/martipello/PokeApp/tree/add_filters

回答

1 CarsonHolzheimer Aug 17 2020 at 08:05

@Relationアノテーションはそのユースケース向けに設計されていないと思います。フィルタリングされたサブセットではなく、関連するすべてのタイプを返すように設計されています。私はあなたが3つのオプションがあると思います:

  1. Kotlinでフィルタリングするだけです:pokemonWithTypes.filter { it.types.contains("GRASS") }。ポケモンのレコードが10000を超えていないので、パフォーマンスは問題ではないと思います。
  2. 結合クエリを記述します。パフォーマンスの向上をごくわずかにするための努力はそれだけだと思います。
  3. 次のようにデータベースビューを使用します。 https://issuetracker.google.com/issues/65509934。これはより静的であり、すべてのタイプのビューを作成する必要があります。