保质期管理 app, 基于 SQLite 的过期时间排序

更新日期: 2023-06-11 阅读次数: 689 字数: 760 分类: Android

在基于 Android XML View 重写 Jetpack Compose 版保质期管理 app 时, 用 SQLDelight 替代了 Room 来做 SQLite 数据库管理。

但是在实现过期时间排序功能时,引入了一个 bug。

原来的数据库中的过期时间字段有两种值,NULL 或者时间对应的秒数。 现在又引入了零值。导致排序混乱。

正确的排序效果

保质期管理 app 中,已过期的物品,或者快过期的应该排列在前面,而不会过期的应该排在后面,如图所示:

保质期管理 app

零值是否合理

例如录入一个物品时,可以不填写过期时间,那么数据库中应该存储 null 还是 0。

这里确实应该存储 NULL,而不是 0。因为 0 也对应一个日期时间,只有 NULL 才代表未填写。

确保默认值是 NULL 而不是 0

简单粗暴的做法,在保存时,加了一层判断:

var expirationDate: Long?
expirationDate = viewModel.expiredDateMilliSeconds / 1000
if (expirationDate == 0L) {
	expirationDate = null
}

NULL 值排序,方法一

如果想将 NULL 值排在后面。之前用 Room 的写法是:

// 过期时间为空的排在最后
@Query("SELECT * from items order by CASE WHEN expirationDate IS NULL THEN 1 ELSE 0 END, expirationDate")
fun getAll(): LiveData<List<Item>>

但是使用 CASE WHEN 这个语法,在 SQLDelight 中会报错

compound operator real, join operator real, GROUP, INDEXED, LIMIT, NOT, ORDER or WHERE expected, got 'by'

NULL 值排序,方法二 (最终使用) 🍎

SELECT * FROM items ORDER BY COALESCE(expirationDate, 4102444800) ASC, name ASC
  • 时间戳格式示例:1685232000.
  • 日期 2999-12-31 对应的时间戳为:4102444800

可以使用 COALESCE 函数代替 CASE WHEN 语句,来实现相同的排序效果。

COALESCE 函数返回列表中第一个非空值,因此可以用它来判断 expirationDate 是否为 NULL,如果为 NULL 则返回 1(表示未过期),否则返回 0(表示已过期)。

在这个查询语句中,使用了 COALESCE 函数将 NULL 值替换为一个足够大的日期值(例如 '2999-12-31'),以使其在排序时排在未过期的记录之后。

COALESCE 示例:

The COALESCE function accepts two or more arguments and returns the first non-null argument.

SELECT COALESCE(10,20); -- return 10
SELECT COALESCE(NULL,20,10); -- returns 20

最终我是选择了使用 COALESCE 来实现

NULL 值排序,方法三

ORDER BY IFNULL(SOMECOL, 4102444800)

The ifnull() function returns a copy of its first non-NULL argument, or NULL if both arguments are NULL. Ifnull() must have exactly 2 arguments. The ifnull() function is equivalent to coalesce() with two arguments.

注意 IFNULL 是 SQLite 的特有函数,COALESCE 是标准 SQL。

NULL 值排序,方法四

需要 SQLite 3.30.0+ 版本以上:

ORDER BY SOMECOL ASC NULLS LAST;
  • SQLDelight 1.x 版本支持 SQLite 3.7.11 至 3.28.0 版本。
  • SQLDelight 1.4.2 版本支持 SQLite 3.32.0 及以上版本。
  • SQLDelight 1.5.0-beta1 版本开始支持 SQLite 3.34.0 及以上版本。

再看 ROOM

看来 Room 这类 ORM 还是有优势的

  • 可以规避一些我的低级错误。
  • 似乎对 SQL 支持的更好,至少不会出现 SQLDelight 出现语法不支持的情况

参考

  • https://stackoverflow.com/questions/12503120/how-to-do-nulls-last-in-sqlite

tags: sqldelight android room sqlite

关于作者 🌱

我是来自山东烟台的一名开发者,有敢兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊, 查看更多联系方式