問題
假設您有多個花園,每個花園都有不同數量的植物。你的作業是不時檢查每個花園的每一株植物。每次訪問您都必須注釋植物的某些屬性,例如是否澆水充足和高度。該應用程式旨在在這些訪問期間提供幫助。
我的方法
我想使用房間資料庫。所以我創建了一個物體 GardenVisit,它具有唯一的 id 和訪問日期。然后我需要一個 GardenAnnotation 物體。該物體將為花園中的每株植物排成一行,并在訪問日標注其 id 和特征。我想為每個 GardenVisit 創建一個表并將它們與一對一的關系聯系起來,但我找不到辦法做到這一點。
為什么我要為每次 GardenVisit 創建一個 GardenAnnotation 表?
在應用程式中,您可以洗掉花園訪問。所以,在洗掉它時,它也應該洗掉它的 GardenAnnotation 表。這似乎是擁有此功能的最簡單方法。
結論
如何在 Room Database 中創建同一物體的多個表并將它們與另一個表鏈接?
如果你有更好的方法,如果你能分享它,我將不勝感激。實際上,創建許多相同物體的表感覺很奇怪。
uj5u.com熱心網友回復:
為了拆分基本相同的布局(架構)而使用多個表可能沒有多大意義,并且可能會使問題復雜化。
根據您的描述,您有一些共同點:-
- 花園。
- 植物。
- 訪問次數
- 性狀。
- 注釋(每次訪問的發現/特征)。
我會相應地建議表格。
一個花園表可能具有但不限于花園的人類識別符號(Kew,巴比倫空中花園......)和(因為它已經存在并且有效)識別符號(id)。
一個植物表(蒲公英,薔薇....)與列編號,名稱及有關植物也許還有其他資訊。
將植物映射/鏈接/關聯到花園的表(未提及),允許多對多關系(花園可以有許多植物,植物可以在許多花園中使用)。2 列一列用于地圖到花園,另一列用于植物。
一個訪問表,其中包含訪問的日期/時間,可能是開始/結束和地圖/鏈接......到花園。
一個特質表如水分充足,死(如果我照料廠)....列將ID和特征(具體要求)
一個注釋表,將鏈接到訪問(并因此鏈接到花園)和鏈接到花園內的植物以及指向要分配的特征的鏈接。
因此模式可以基于 SQLite(以演示資料庫/關系如何從 SQLite pov 作業):-
DROP TABLE IF EXISTS annotation;
DROP TABLE IF EXISTS trait;
DROP TABLE IF EXISTS visit;
DROP TABLE IF EXISTS garden_plant_map;
DROP TABLE IF EXISTS garden;
DROP TABLE IF EXISTS plant;
CREATE TABLE IF NOT EXISTS garden (garden_id INTEGER PRIMARY KEY, garden_name TEXT UNIQUE);
INSERT INTO garden (garden_name)
VALUES('Kew' /* id will be 1 */),('Hanging Gardens of Babylon' /* id will be 2 and so on (probably)*/)
;
CREATE TABLE IF NOT EXISTS plant(plant_id INTEGER PRIMARY KEY, plant_name TEXT UNIQUE);
INSERT INTO plant (plant_name)
VALUES('Rose' /* id will be 1 etc*/),('Dandelion'),('Poppy'),('Azelia'),('Oak'),('Beech')
;
CREATE TABLE IF NOT EXISTS garden_plant_map (
garden_map INTEGER,
plant_map INTEGER,
PRIMARY KEY (garden_map,plant_map)
FOREIGN KEY (garden_map) REFERENCES garden(garden_id) ON DELETE CASCADE ON UPDATE CASCADE
FOREIGN KEY (plant_map) REFERENCES plant(plant_id) ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO garden_plant_map
VALUES
(1 /* Kew */, 3 /* Poppy*/),
(1 /* Kew */, 1 /* Rose */),
(2 /* Babylon */, 2 /* Dandelion */),
(2,5),(2,6) /*Oak and Beech for Babylon */
;
CREATE TABLE IF NOT EXISTS trait (trait_id INTEGER PRIMARY KEY, trait_description UNIQUE);
INSERT INTO trait (trait_description)
VALUES ('Well watered'),('Dead'),('Stressed'),('Flourishing'),('under watered')
;
CREATE TABLE IF NOT EXISTS visit (
visit_id INTEGER PRIMARY KEY,
garden_map INTEGER,
start_of_visit TEXT /* will be date in yyyy-mm-dd hh:mm:ss format*/,
end_of_visit TEXT,
visit_done INTEGER, /* 0/false or 1 (or greater)/true */
FOREIGN KEY (garden_map) REFERENCES garden(garden_id) ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO visit (garden_map,start_of_visit,end_of_visit,visit_done)
VALUES
(1,'2020-01-01 08:00','2021-01-01 10:00',true)
,(1,'2021-01-01 08:00','2021-01-01 10:00',false)
,(2,'2021-02-01 08:00','2021-02-01 10:00',false)
,(1,'2021-03-01 08:00','2021-03-01 10:00',false)
,(2,'2021-04-01 08:00','2021-04-01 10:00',false)
;
CREATE TABLE IF NOT EXISTS annotation (
annotation_id INTEGER PRIMARY KEY,
visit_map INTEGER REFERENCES visit(visit_id) ON DELETE CASCADE ON UPDATE CASCADE,
trait_map INTEGER REFERENCES trait(trait_id) ON DELETE CASCADE ON UPDATE CASCADE,
garden_plant_map_garden_map INTEGER, garden_plant_map_plant_map INTEGER,
FOREIGN KEY (garden_plant_map_garden_map,garden_plant_map_plant_map) REFERENCES garden_plant_map(garden_map,plant_map)
);
INSERT INTO annotation (visit_map, trait_map, garden_plant_map_garden_map, garden_plant_map_plant_map )
VALUES
(1 /* visit on 1/1/20 */, 1 /* Well watered */, 1 /* Kew */, 3 /* Poppy */ )
, (1 /* visit on 1/1/20 */, 5 /* under watered */, 1 /* Kew */, 1 /* Rose */ )
, (3 /* visit on 1/2/21 */, 2 /* dead */, 2 /* Babylon */, 2 /* Dandelion */ )
, (3 /* visit on 1/2/21 */, 4 /* flourishing */, 2 /* babylon */, 6 /* Beech */ )
, (3 /* visit on 1/2/21 */, 3 /* stressed */, 2 /* babylon */, 5 /* Beech */ )
;
SELECT
garden_name,
start_of_visit,end_of_visit, visit_done,
plant.plant_name,
trait.trait_description,
CASE WHEN visit_done THEN 'Completed' ELSE 'ToDO' END AS status
FROM annotation
JOIN visit ON visit.visit_id = annotation.visit_map
JOIN garden ON visit.garden_map = garden.garden_id
JOIN plant ON garden_plant_map_plant_map = plant_id
JOIN trait ON trait_map = trait_id
;
查詢的結果是:-

假設 id 為 1 的訪問被洗掉(盡管您可能認為visit_done 是真的有效洗掉(因此您可以隨時回到過去)),例如使用:-
DELETE FROM visit WHERE visit_id = 3;
然后相同的查詢回傳:-

即訪問 3 的三個注釋已被洗掉
忽略洗掉,即訪問剩余的visit_id為3,然后表格看起來像:-
花園

植物

特征

訪問

Garden_plant_map

注解

uj5u.com熱心網友回復:
繼
物體。在 Kotlin 中,來自上述(正在進行中/未經測驗):-
花園
/* CREATE TABLE IF NOT EXISTS garden (garden_id INTEGER PRIMARY KEY, garden_name TEXT UNIQUE);*/
@Entity( indices = [Index(value = ["garden_name"], unique = true)])
data class Garden (
@PrimaryKey
@ColumnInfo(name = "garden_id")
val id: Long? = null, /* specifying null or not supplying value results in auto-generated id with overheads of autogenerate = true */
@ColumnInfo(name = "garden_name")
val name: String
)
植物
/* CREATE TABLE IF NOT EXISTS plant(plant_id INTEGER PRIMARY KEY, plant_name TEXT UNIQUE); */
@Entity(indices = [Index( value = ["plant_name"], unique = true)])
data class Plant (
@PrimaryKey
@ColumnInfo(name = "plant_id")
val id: Long? = null,
@ColumnInfo(name = "plant_name")
val name: String
)
特征
/* CREATE TABLE IF NOT EXISTS trait (trait_id INTEGER PRIMARY KEY, trait_description UNIQUE); */
@Entity(indices = [Index(value = ["trait_description"], unique = true)])
data class Trait(
@PrimaryKey
@ColumnInfo(name = "trait_id")
val id: Long? = null,
@ColumnInfo(name = "trait_description")
val description: String
)
花園植物地圖
/* CREATE TABLE IF NOT EXISTS garden_plant_map (
garden_map INTEGER,
plant_map INTEGER,
PRIMARY KEY (garden_map,plant_map)
FOREIGN KEY (garden_map) REFERENCES garden(garden_id) ON DELETE CASCADE ON UPDATE CASCADE
FOREIGN KEY (plant_map) REFERENCES plant(plant_id) ON DELETE CASCADE ON UPDATE CASCADE
);
*/
@Entity(
tableName = "garden_plant_map",
primaryKeys = ["garden_map","plant_map"],
foreignKeys = [
ForeignKey(
entity = Garden::class,
parentColumns = ["garden_id"],
childColumns = ["garden_map"],
onDelete = CASCADE,
onUpdate = CASCADE
),
ForeignKey(
entity = Plant::class,
parentColumns = ["plant_id"],
childColumns = ["plant_map"],
onDelete = CASCADE,
onUpdate = CASCADE
)
]
)
data class GardenPlantMap(
val garden_map: Long,
@ColumnInfo(index = true) /* indexed as will likely map via column */
val plant_map: Long
)
訪問
/*
CREATE TABLE IF NOT EXISTS visit (
visit_id INTEGER PRIMARY KEY,
garden_map INTEGER,
start_of_visit TEXT /* will be date in yyyy-mm-dd hh:mm:ss format*/,
end_of_visit TEXT,
visit_done INTEGER, /* 0/false or 1 (or greater)/true */
FOREIGN KEY (garden_map) REFERENCES garden(garden_id) ON DELETE CASCADE ON UPDATE CASCADE
);
*/
@Entity(
foreignKeys = [
ForeignKey(
entity = Garden::class,
parentColumns = ["garden_id"],
childColumns = ["garden_map"],
onDelete = CASCADE,
onUpdate = CASCADE
)
]
)
data class Visit(
@PrimaryKey
@ColumnInfo(name = "visit_id")
val id: Long? = null,
@ColumnInfo(index = true)
val garden_map: Long,
@ColumnInfo(name = "start_of_visit")
val visitStart: String, /* could be Long */
@ColumnInfo(name = "end_of_visit")
val visitEnd: String,
@ColumnInfo(name = "visit_done")
val visitDone: Boolean
)
注解
/*
CREATE TABLE IF NOT EXISTS annotation (
annotation_id INTEGER PRIMARY KEY,
visit_map INTEGER REFERENCES visit(visit_id) ON DELETE CASCADE ON UPDATE CASCADE,
trait_map INTEGER REFERENCES trait(trait_id) ON DELETE CASCADE ON UPDATE CASCADE,
garden_plant_map_garden_map INTEGER, garden_plant_map_plant_map INTEGER,
FOREIGN KEY (garden_plant_map_garden_map,garden_plant_map_plant_map) REFERENCES garden_plant_map(garden_map,plant_map)
);
*/
@Entity(
indices = [
Index(value = ["garden_plant_map_garden_map","garden_plant_map_plant_map"])
],
foreignKeys = [
ForeignKey(
entity = Visit::class,
parentColumns = ["visit_id"],
childColumns = ["visit_map"],
onDelete = CASCADE,
onUpdate = CASCADE
),
ForeignKey(
entity = Trait::class,
parentColumns = ["trait_id"],
childColumns = ["trait_map"],
onDelete = CASCADE,
onUpdate = CASCADE
),
ForeignKey(
entity = GardenPlantMap::class,
parentColumns = ["garden_map","plant_map"],
childColumns = ["garden_plant_map_garden_map","garden_plant_map_plant_map"] /*,
onDelete = CASCADE,
onUpdate = CASCADE
*/
)
]
)
data class Annotation(
@PrimaryKey
@ColumnInfo(name = "annotation_id")
val id: Long? = null,
@ColumnInfo(name = "visit_map", index = true)
val visitMap: Long,
@ColumnInfo(name = "trait_map", index = true)
val traitMap: Long,
@ColumnInfo( name ="garden_plant_map_garden_map")
val gardenPlantMap_garden_map: Long,
@ColumnInfo( name ="garden_plant_map_plant_map")
val gardenPlantMap_plant_map: Long
)
用于獲取最終查詢的兩個 POJO(替代方案),它具有額外的計算/派生列狀態 Example1POJO和Example2POJO :-
data class Example1POJO (
@Embedded
val garden: Garden,
@Embedded
val visit: Visit,
@Embedded
val plant: Plant,
@Embedded
val trait: Trait,
val status: String
)
和
data class Example2POJO(
val garden_id: Long,
val garden_name: String,
val visit_id: Long,
val visit_done: Boolean,
val start_of_visit: String,
val end_of_visit: String,
val plant_id: Long,
val plant_name: String,
val trait_id: Long,
val trait_description: String,
val status: String
)
單個 Dao 抽象類(可以是介面)GardenVisitDao
@Dao
abstract class GardenVisitDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(garden: Garden): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(plant: Plant): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(trait: Trait): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(gardenPlantMap: GardenPlantMap): Long /* not really of use */
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(visit: Visit): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(annotation: Annotation): Long
@Query("SELECT * FROM garden")
abstract fun getAllFromGarden(): List<Garden>
@Query("SELECT garden_id FROM garden WHERE garden_name=:gardenName")
abstract fun getGardenIdByGardenName(gardenName: String): Long
@Query("SELECT plant_id FROM plant WHERE plant_name=:plantName")
abstract fun getPlantIdByPlantName(plantName: String): Long
@Query("SELECT trait_id FROM trait WHERE trait_description=:traitDescription")
abstract fun getTraitIdByDescription(traitDescription: String): Long
@Query("SELECT visit_id FROM visit WHERE start_of_visit=:visitStartDateTime")
abstract fun getVisitIdByStartDateTime(visitStartDateTime: String): Long
/*
SELECT
garden_name,
start_of_visit,end_of_visit, visit_done,
plant.plant_name,
trait.trait_description,
CASE WHEN visit_done THEN 'Completed' ELSE 'ToDO' END AS status
FROM annotation
JOIN visit ON visit.visit_id = annotation.visit_map
JOIN garden ON visit.garden_map = garden.garden_id
JOIN plant ON garden_plant_map_plant_map = plant_id
JOIN trait ON trait_map = trait_id
;
*/
@Query("SELECT "
"garden.*,"
"visit.*,"
"plant.*,"
"trait.*,"
"CASE WHEN visit_done THEN 'Completed' ELSE 'ToDO' END AS status"
" FROM annotation "
"JOIN visit ON visit.visit_id = annotation.visit_map "
"JOIN garden ON visit.garden_map = garden.garden_id "
"JOIN plant ON annotation.garden_plant_map_plant_map = plant.plant_id "
"JOIN trait ON trait.trait_id = annotation.trait_map")
abstract fun getAllAnnotationsOverviewV1(): List<Example1POJO>
@Query("SELECT "
"garden.*,"
"visit.*,"
"plant.*,"
"trait.*,"
"CASE WHEN visit_done THEN 'Completed' ELSE 'ToDO' END AS status"
" FROM annotation "
"JOIN visit ON visit.visit_id = annotation.visit_map "
"JOIN garden ON visit.garden_map = garden.garden_id "
"JOIN plant ON annotation.garden_plant_map_plant_map = plant.plant_id "
"JOIN trait ON trait.trait_id = annotation.trait_map")
abstract fun getAllAnnotationsOverviewV2(): List<Example2POJO>
}
@Database 類GardenVisitDatabase
@Database(entities = [
Garden::class,
Plant::class,
Trait::class,
Visit::class,
Annotation::class,
GardenPlantMap::class],
version = 1
)
abstract class GardenVisitDatabase: RoomDatabase() {
abstract fun getGardenVisitDao(): GardenVisitDao
companion object {
@Volatile
private var instance: GardenVisitDatabase? = null
fun getInstance(context: Context): GardenVisitDatabase {
if (instance == null) {
instance = Room.databaseBuilder(
context,GardenVisitDatabase::class.java,
"gardenvisit.db"
)
.allowMainThreadQueries()
.build()
}
return instance as GardenVisitDatabase
}
}
}
- 注意 allowMainThreadQueries 允許所有(大多數)在主執行緒上運行(有利于測驗)。
最后從復制(接近)答案中的 SQL 的 Activity 加載和提取資料(上一個查詢的兩個版本)。
class MainActivity : AppCompatActivity() {
lateinit var db: GardenVisitDatabase
lateinit var dao: GardenVisitDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = GardenVisitDatabase.getInstance(this)
dao = db.getGardenVisitDao()
/*
INSERT INTO garden (garden_name)
VALUES('Kew' /* id will be 1 */),('Hanging Gardens of Babylon' /* id will be 2 and so on (probably)*/)
;
*/
val keygardens_id = dao.insert(Garden(name = "Kew"))
val babylon_id = dao.insert(Garden(name = "hanging Gardens of Babylon"))
/*
INSERT INTO plant (plant_name)
VALUES('Rose' /* id will be 1 etc*/),('Dandelion'),('Poppy'),('Azelia'),('Oak'),('Beech')
;
*/
val rose_id = dao.insert(Plant(name = "Rose"))
val dand_id = dao.insert(Plant(name = "Dandelion"))
val popp_id = dao.insert(Plant(name = "Poppy"))
val azel_id = dao.insert(Plant(name = "Azelia"))
val oak_id = dao.insert(Plant(name = "Oak"))
val ww_id = dao.insert(Trait(description = "Well Watered"))
val dead_id = dao.insert(Trait(description = "Dead"))
val uw_id = dao.insert(Trait(description = "Under Watered"))
val str_id = dao.insert(Trait(description = "Stressed"))
val flour_id = dao.insert(Trait(description = "Flourishing"))
dao.insert(GardenPlantMap( dao.getGardenIdByGardenName("Kew"),dao.getPlantIdByPlantName("Rose")));
dao.insert(GardenPlantMap(keygardens_id,popp_id))
dao.insert(GardenPlantMap(babylon_id,dand_id))
dao.insert(GardenPlantMap(babylon_id,oak_id))
dao.insert(GardenPlantMap(babylon_id,dao.insert(Plant(name = "Beech"))))
val vkew20210101 = dao.insert(Visit(garden_map = keygardens_id, visitStart = "2020-01-01 08:00", visitEnd = "2021-01-01 10:00", visitDone = true))
val vkew20220101 = dao.insert(Visit(garden_map = keygardens_id, visitStart = "2022-01-01 08:00", visitEnd = "2022-01-01 10:00", visitDone = false))
val vbab20220201 = dao.insert(Visit(garden_map = babylon_id, visitStart = "2022-02-01 08:00", visitEnd = "2022-02-01 10:00", visitDone = false))
val vkey20220301 = dao.insert(Visit(garden_map = keygardens_id, visitStart = "2022-03-01 08:00", visitEnd = "2022-03-01 10:00", visitDone = false))
val vbab20220401 = dao.insert(Visit(garden_map = babylon_id, visitStart = "2022-04-01 08:00", visitEnd = "2022-04-01 10:00", visitDone = false))
val vkey20220501 = dao.insert(Visit(garden_map = keygardens_id, visitStart = "2022-05-01 08:00", visitEnd = "2022-05-01 10:00", visitDone = false))
dao.insert(Annotation(visitMap = vkew20210101, traitMap = ww_id, gardenPlantMap_garden_map = keygardens_id, gardenPlantMap_plant_map = popp_id))
dao.insert(Annotation(visitMap = vkew20210101, traitMap = uw_id, gardenPlantMap_garden_map = keygardens_id, gardenPlantMap_plant_map = rose_id))
dao.insert(Annotation(visitMap = vbab20220201, traitMap = dead_id, gardenPlantMap_garden_map = babylon_id, gardenPlantMap_plant_map = dand_id))
dao.insert(Annotation(visitMap = vbab20220201, traitMap = flour_id, gardenPlantMap_garden_map = babylon_id, gardenPlantMap_plant_map = dao.getPlantIdByPlantName("Beech")))
dao.insert(Annotation(visitMap = vbab20220201, traitMap = str_id, gardenPlantMap_garden_map = babylon_id, gardenPlantMap_plant_map = oak_id))
for(ex1: Example1POJO in dao.getAllAnnotationsOverviewV1()) {
Log.d("GARDENDBINFO","Garden is ${ex1.garden.name} starts: ${ex1.visit.visitStart} ends: ${ex1.visit.visitEnd}. Plant is ${ex1.plant.name}. Trait is ${ex1.trait.description}. Status is ${ex1.status} ")
}
for(ex2: Example2POJO in dao.getAllAnnotationsOverviewV2()) {
Log.d("GARDENDBINFO","Garden is ${ex2.garden_name} starts: ${ex2.start_of_visit} ends: ${ex2.end_of_visit}. Plant is ${ex2.plant_name}. Trait is ${ex2.trait_description}. Status is ${ex2.status} ")
}
}
}
結果輸出到日志
40.959D/GARDENDBINFO: Garden is Kew starts: 2020-01-01 08:00 ends: 2021-01-01 10:00. Plant is Poppy. Trait is Well Watered. Status is Completed
40.959D/GARDENDBINFO: Garden is Kew starts: 2020-01-01 08:00 ends: 2021-01-01 10:00. Plant is Rose. Trait is Under Watered. Status is Completed
40.960D/GARDENDBINFO: Garden is hanging Gardens of Babylon starts: 2022-02-01 08:00 ends: 2022-02-01 10:00. Plant is Dandelion. Trait is Dead. Status is ToDO
40.960D/GARDENDBINFO: Garden is hanging Gardens of Babylon starts: 2022-02-01 08:00 ends: 2022-02-01 10:00. Plant is Beech. Trait is Flourishing. Status is ToDO
40.960D/GARDENDBINFO: Garden is hanging Gardens of Babylon starts: 2022-02-01 08:00 ends: 2022-02-01 10:00. Plant is Oak. Trait is Stressed. Status is ToDO
40.962D/GARDENDBINFO: Garden is Kew starts: 2020-01-01 08:00 ends: 2021-01-01 10:00. Plant is Poppy. Trait is Well Watered. Status is Completed
40.962D/GARDENDBINFO: Garden is Kew starts: 2020-01-01 08:00 ends: 2021-01-01 10:00. Plant is Rose. Trait is Under Watered. Status is Completed
40.962D/GARDENDBINFO: Garden is hanging Gardens of Babylon starts: 2022-02-01 08:00 ends: 2022-02-01 10:00. Plant is Dandelion. Trait is Dead. Status is ToDO
40.962D/GARDENDBINFO: Garden is hanging Gardens of Babylon starts: 2022-02-01 08:00 ends: 2022-02-01 10:00. Plant is Beech. Trait is Flourishing. Status is ToDO
40.962D/GARDENDBINFO: Garden is hanging Gardens of Babylon starts: 2022-02-01 08:00 ends: 2022-02-01 10:00. Plant is Oak. Trait is Stressed. Status is ToDO
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/398934.html
