Android Room如何增加计数字段并更新特定行字段

luc*_*ter 6 java sqlite android kotlin android-room

我正在实现以下模式来计算用户的按钮点击次数。例如,如果用户单击苹果按钮,我想将类型存储为主键,这将是一个唯一的条目,后跟用户点击苹果按钮的次数的计数,对于每个按钮,我也会保存与这些点击相关的时间。因此,我正在尝试学习房间数据库,并想了解如何使用新的关联计数和时间戳来更新独特的水果类型行。我是否必须查询表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()
}
Run Code Online (Sandbox Code Playgroud)

Mik*_*keT 8

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

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

例如以下内容:-

    dao.insert(ClickEntity("Apple",0,0))
    dao.incrementClick("Apple",System.currentTimeMillis());
Run Code Online (Sandbox Code Playgroud)

结果是 :-

在此输入图像描述

即,在更新计数增加并且当前时间已相应设置后,插入计数和时间为 0 时。因此,无需获取数据然后应用(只要您知道它相当于按钮时应该使用的类型)。

补充评论

在更新和递增之前我仍然需要插入一次!是否有一个较短的版本可以同时插入或更新。

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

如果应用程序针对具有 SQLite 版本 3.24.0(Android API 30+ 版本 11+)的 Android 版本,那么您可以使用 UPSERT 带有 ON CONFLICT() DO UPDATE 子句的 INSERT)。

例如

@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)
Run Code Online (Sandbox Code Playgroud)
  • 请注意,上面的内容尚未经过测试,但显示语法错误(可能是因为目标 Android 版本)

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

@Insert(onConflictStategy = OnConflictStrategy.REPLACE)
fun insert(ClickEntity("the_type",the_new_count,the_current_timestamp)
Run Code Online (Sandbox Code Playgroud)

你必须检索计数。

然而,更复杂的解决方案可能是使用子查询获取类型的计数+1。这很复杂,因为第一次插入时没有计数。这可以使用 SQLitecoalesce函数来解决。这将需要@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)
Run Code Online (Sandbox Code Playgroud)

所以:-

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

示例(INSERT 或 REPLACE 选项):-

继上述代码之后,除了上述 Dao 的更改/添加之外,还添加了以下内容(不是 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")
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

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