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

发布时间: 2023-06-11 17:41:57 作者: 大象笔记

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

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

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

正确的排序效果

保质期管理 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

可以使用 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;

再看 ROOM

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

参考

我是一名山东烟台的开发者,联系作者