Android 房间如何增加计数字段和更新特定行字段

Android room how to increment count field and updating specific row fields

我正在实施以下架构来计算用户的按钮点击次数。例如,如果用户点击苹果按钮,我想将类型存储为主键,这将是一个唯一的条目,后跟用户点击苹果按钮的次数计数,对于每一个我也会保存与这些点击相关的时间。因此,我正在尝试学习房间数据库,并想了解如何使用新的关联计数和时间戳更新唯一的水果类型行。我是否必须查询 table getByType(type) 以获取现有数据条目,然后每次都使用新的计数和时间戳值更新它,或者是否有更好的方法使用 room api的

 @Entity
 data class ClickEntity(
   @PrimaryKey
   val type: String,
   val clickCount: Int? = null,
   val clickTime: Long? = null
 )


 @Dao
internal interface ClickEntityDao {
@Query("SELECT * FROM ClickEntity ORDER BY type")
fun getAll(): List<ClickEntity>

@Query("SELECT * FROM ClickEntity WHERE type=:type")
fun getByType(entity: ClickEntity): ClickEntity

// QUESTION: type, count, time -> how do I increment counter and update time while updating an existing entry
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun update(entity: ClickEntity) 


@Query("DELETE FROM ClickEntity")
fun clear()
}

我相信以下内容可以满足您的需求:-

@Query("UPDATE ClickEntity SET clickCount = clickCount + 1, clickTime = :clickTime WHERE type=:type")
fun incrementClick(type: String, clickTime: Long)

例如:-

    dao.insert(ClickEntity("Apple",0,0))
    dao.incrementClick("Apple",System.currentTimeMillis());

结果:-

即当更新计数递增并且当前时间已相应设置后,插入计数和时间为 0。所以不需要获取数据然后应用(只要你知道你应该知道的类型,如果它等同于按钮)。

补充评论

I would still need to insert once before updating and incrmenting ! Is there a shorter version to insertorupdate at the same time.

是的,也许吧。但是,如果您有按钮,那么我建议您在知道应该有按钮时插入。

如果应用程序针对 android versions that have SQLite version 3.24.0 (Android API 30+ version 11+) then you could use an UPSERT(插入带有 ON CONFLICT() DO UPDATE 子句)。

例如

@Query("INSERT INTO clickEntity VALUES(:type,0,:clickTime) ON CONFLICT(type) DO UPDATE SET clickCount = clickCount +1, clickTime = :clickTime ")
fun insertOrUpdate(type: String, clickTime: Long)
  • 注意上面没有测试但是显示语法错误(可能是因为目标android版本).

另一种方法是使用 INSERT OR REPLACE,只需使用:-

@Insert(onConflictStategy = OnConflictStrategy.REPLACE)
fun insert(ClickEntity("the_type",the_new_count,the_current_timestamp)

但是 您必须检索计数。

HOWEVER 更复杂的解决方案可能是使用子查询为类型获取计数 + 1。这很复杂,因为第一次插入时没有计数。这可以使用 SQLite coalesce 函数解决。这将需要 @Query 因为 @Insert 需要一个 Entity 对象。因此,插入或替换 (更新但实际上删除并插入以执行更新) 的解决方案可能是:-

@Query("INSERT OR REPLACE INTO clickentity VALUES(:type,coalesce((SELECT clickCount FROM clickEntity WHERE type = :type),-1) + 1,strftime('%s','now') * 1000)")
fun insertOrReplace(type: String)

所以:-

  • 如果类型不存在,它将插入一个新行:-
    • 尝试获取该类型的 clickCount 列的当前值,但由于不存在此类行,因此返回 null,因此添加了合并函数 returns -1 和 1,因此计数为 0新行。
    • 使用 SQLite 的 strftime 函数获取当前时间(精确到秒)并将其乘以 1000 使其与 Android 时间戳(增加毫秒)内联。
  • 如果类型存在,它将用另一行替换该行:-
    • 从现有行中找到计数并加 1

示例(插入或替换选项):-

根据上述代码,除了上述 Dao 的 changes/additions(不是 UPSERT)之外,还添加了以下代码:-

    // Adds new
    dao.insert(ClickEntity("Grape",0, System.currentTimeMillis()))
    // Updates (cheated with clickCount knowing it would be 1)
    dao.insert(ClickEntity("Grape",1,System.currentTimeMillis()))
    // Adds new
    dao.insertOrReplace("Apricot")
    // Updates
    dao.insertOrReplace("Apricot")

数据库看起来像(所有 Android Studio Show):-

  • Banana 显示了 CURRENT_TIMESTAMP 的存储方式,这会导致问题。
  • Pear 仅使用 strftime('%s','now') 即没有毫秒
  • 如您所见,葡萄和杏都符合预期,即计数已增加。

您可以执行以下操作

@Entity
 data class ClickEntity(
   @PrimaryKey
   val type: String,
   val clickCount: Int = 0,
   val clickTime: Long = System.currentTimeMillis()
 )

然后递增

@Query("UPDATE ClickEntity SET clickCount = clickCount + 1, clickTime = CURRENT_TIMESTAMP WHERE type=:type")
fun incrementClick(type: String)