Flutter——数据库Drift开发详细教程(四)
目录
- 参考
- 正文表达式
- 1.比较
- 2.布尔代数
- 3.算术
- BigIn
- 4.空值检查
- 6.日期和时间
- 7.IN和NOT IN
- 8.聚合函数(例如 count 和 sum)
- 8.1比较
- 8.2算术
- 8.4计数
- 8.5group_concat
- 8.9窗口函数
- 9.数学函数和正则表达式
- 10.子查询
- 10.1 标量子查询
- 10.2 isInQuery
- 10.3 存在
- 10.4完整子查询
- 11.自定义表达式
参考
- https://drift.simonbinder.eu/dart_api/expressions/
正文表达式
表达式是 SQL 的片段,数据库解释它们时会返回一个值。drift 的 dart_api 允许您在 Dart 中编写大多数表达式,然后将其转换为 SQL。表达式适用于各种情况。例如,where 期望一个返回布尔值的表达式。
大多数情况下,你编写的表达式会将其他表达式组合起来。任何列名都是有效的表达式,因此对于大多数where子句,你会编写一个将列名包装在某种比较中的表达式。
1.比较
每个表达式都可以使用
与值进行比较equals。如果要将一个表达式与另一个表达式进行比较,可以使用。对于数值和日期时间表达式,您还可以使用等equalsExpr各种方法
来比较它们:isSmallerThan 和 isSmallerOrEqual
// 查询小于 5 的 所有对象
(select(animals)..where((a) => a.amountOfLegs.isSmallerThanValue(5))).get();// find all animals who's average livespan is shorter than their amount of legs (poor flies)
(select(animals)..where((a) => a.averageLivespan.isSmallerThan(a.amountOfLegs)));Future<List<Animal>> findAnimalsByLegs(int legCount) {return (select(animals)..where((a) => a.amountOfLegs.equals(legCount))).get();
}
注意:根据下面图圈红标记,可以看到对应的参数的用途,具体效果可以自行测试
2.布尔代数
您可以使用&、|运算符和not漂移公开的方法来嵌套布尔表达式:
// find all animals that aren't mammals and have 4 legs
select(animals)..where((a) => a.isMammal.not() & a.amountOfLegs.equals(4));// find all animals that are mammals or have 2 legs
select(animals)..where((a) => a.isMammal | a.amountOfLegs.equals(2));
如果您有一个谓词列表,其中一个或全部需要匹配,则可以分别使用 Expression.or和Expression.and:
Expression.and([a.isMammal,a.amountOfLegs.equals(4),
]);
3.算术
对于int与double表达式,可以使用+、-、*和/运算符。要在 SQL 表达式和 Dart 值之间运行计算,请将其包装在Variable:
Future<List<Product>> canBeBought(int amount, int budget) {return (select(products)..where((p) {final totalPrice = p.price * Variable(amount);return totalPrice.isSmallerOrEqualValue(budget);})).get();
}
字符串表达式+也定义了一个运算符。正如你所期望的,它在 SQL 中执行连接操作。
对于整数值,可以使用~、bitwiseAnd和bitwiseOr执行按位运算:
Expression<int> bitwiseMagic(Expression<int> a, Expression<int> b) {// Generates `~(a & b)` in SQL.return ~(a.bitwiseAnd(b));
}
BigIn
虽然 SQLite 和 Dart VM 使用 64 位整数,但编译为 JavaScript 的 Dart 应用程序却不使用。因此,为了在编译到 Web 时表示大整数结果,您可能需要将表达式转换为BigInt。
使用dartCast()将确保结果被解释为BigInt漂移。这不会改变生成的 SQL,漂移对所有数据库都使用 64 位整数类型。
示例: 对于表达式,即使和被定义为常规整数,(table.columnA * table.columnB).dartCast()drift 也会将结果值报告为。BigIntcolumnAcolumnB
4.空值检查
要检查 SQL 中表达式的计算结果是否为NULL,可以使用isNull扩展:
final withoutCategories = select(todoItems)..where((row) => row.category.isNull());
true如果内部表达式解析为 null,则返回的表达式将解析为null,false否则解析为 null。正如你所料,isNotNull反之亦然。
要在表达式计算结果为 时使用后备值null,可以使用该coalesce 函数。它接受一个表达式列表,并计算第一个不为 的表达式null:
final category = coalesce([todoItems.category, const Constant(1)]);
这对应于??Dart 中的运算符。
6.日期和时间
对于返回的列和表达式,DateTime您可以使用 year、、、和getter 从该日期month中提取各个字段:dayhourminutesecond
select(users).where((u) => u.birthDate.year.isSmallerThanValue(1950));
year诸如、month等各个字段本身就是表达式。这意味着您可以对它们使用运算符和比较操作。要获取当前日期或当前时间作为表达式,请使用Drift 提供的currentDate 和常量。currentDateAndTime
您还可以使用+and-运算符在时间列中添加或减去持续时间:
Future<void> increaseDueDates() async {final change = TodoItemsCompanion.custom(dueDate: todoItems.dueDate + Duration(days: 1));await update(todoItems).write(change);
}
对于更复杂的日期时间转换,modifyandmodifyAll函数很有用。例如,这会将dueDate待办事项的每个值递增到星期一的同一时间:
Future<void> moveDueDateToNextMonday() async {final change = TodoItemsCompanion.custom(dueDate: todoItems.dueDate.modify(DateTimeModifier.weekday(DateTime.monday)));await update(todoItems).write(change);
}
7.IN和NOT IN
isIn您可以使用和方法检查表达式是否在值列表中isNotIn :
select(animals)..where((a) => a.amountOfLegs.isIn([3, 7, 4, 2]));
再次,该isNotIn函数以相反的方式工作。
8.聚合函数(例如 count 和 sum)
Dart API 提供了聚合函数。与常规函数不同,聚合函数可以同时对多行进行操作。默认情况下,它们会将 select
语句返回的所有行合并为一个值。你还可以使用 group by让它们对结果集中的不同组进行操作。
8.1比较
您可以在数值和日期时间表达式上使用min和max方法。它们分别返回结果集中的最小值或最大值。
8.2算术
、和方法可用。例如,你可以使用以下查询avg来查看待办事项的平均长度:sumtotal
Stream<double> averageItemLength() {final avgLength = todoItems.content.length.avg();final query = selectOnly(todoItems)..addColumns([avgLength]);return query.map((row) => row.read(avgLength)!).watchSingle();
}
注意:我们使用selectOnly而不是 ,select因为我们对任何提供的列都不感兴趣 todos——我们只关心平均长度。更多详情请见 此处。
8.4计数
有时,计算一个组中有多少行很有用。通过使用 示例中的表格布局,此查询将报告每个类别关联的待办事项数量:
final amountOfTodos = todoItems.id.count();final query = db.select(categories).join([innerJoin(todoItems,todoItems.category.equalsExp(categories.id),useColumns: false,)
]);
query..addColumns([amountOfTodos])..groupBy([categories.id]);
如果不想计算重复值,可以使用count(distinct: true)。有时,您只需要计算符合条件的值。为此,您可以使用filter参数 on count。要计算所有行(而不是单个值),可以使用顶级countAll() 函数。
有关如何使用 Drift 的 Dart API 编写聚合查询的更多信息,请参见 此处
8.5group_concat
该groupConcat函数可用于将多个值连接成一个字符串:
Stream<String> allTodoContent() {final allContent = todoItems.content.groupConcat();final query = selectOnly(todoItems)..addColumns([allContent]);return query.map((row) => row.read(allContent)!).watchSingle();
}
分隔符默认为逗号,周围没有空格,但可以使用separator参数 on进行更改groupConcat。
8.9窗口函数
除了聚合表达式和 之外groupBy,Drift 还支持窗口函数。与将一组行折叠为单个值的常规聚合不同,窗口函数允许对与当前行相关的行子集运行聚合。例如,您可以使用它来跟踪值的累计总数:
/// Returns all todo items, associating each item with the total length of all
/// titles up until (and including) each todo item.
Selectable<(TodoItem, int)> todosWithRunningLength() {final runningTitleLength = WindowFunctionExpression(todoItems.title.length.sum(),orderBy: [OrderingTerm.asc(todoItems.id)],);final query = select(todoItems).addColumns([runningTitleLength]);query.orderBy([OrderingTerm.asc(todoItems.id)]);return query.map((row) {return (row.readTable(todoItems)!, row.read(runningTitleLength)!);});
}
窗口函数的一个有趣用途是确定如果行按某列排序(实际上并不返回所有行,也不按该列排序),该行的排名。此排名可以附加到每一行:
/// Returns all todo items, also reporting the index (counting from 1) each
/// todo item would have if all items were sorted by descending content
/// length.
Selectable<(TodoItem, int)> todosWithLengthRanking() {final lengthRanking = WindowFunctionExpression(todoItems.id.count(),orderBy: [OrderingTerm.desc(todoItems.content.length)],);final query = select(todoItems).addColumns([lengthRanking]);return query.map((row) {return (row.readTable(todoItems)!, row.read(lengthRanking)!);});
}
9.数学函数和正则表达式
使用 时NativeDatabase,将提供一组基本的三角函数。它还定义了REGEXP函数,允许您a REGEXP b在 SQL 查询中使用。有关更多信息,请参阅此处的函数列表。
10.子查询
Drift 对表达式中的子查询有基本的支持。
10.1 标量子查询
标量子查询是一个只返回一行且只有一列的选择语句。由于它只返回一个值,因此它可以在另一个查询中使用:
Future<List<TodoItem>> findTodosInCategory(String category) async {final groupId = selectOnly(categories)..addColumns([categories.id])..where(categories.name.equals(category));final query = select(todoItems)..where((row) => row.category.equalsExp(subqueryExpression(groupId)));return await query.get();
}
这groupId是一个常规的 select 语句。默认情况下,drift 会选择所有列,因此我们使用它 selectOnly来仅加载我们关注的类别的 ID。然后,我们可以使用subqueryExpression该查询嵌入到我们用作过滤器的表达式中。
10.2 isInQuery
isIn与和isNotIn函数类似,您可以使用它isInQuery来传递子查询而不是直接传递一组值。
子查询必须返回一列,但允许返回多行。 isInQuery如果查询中存在该值,则返回 true。
10.3 存在
andexistsQuery函数notExistsQuery可用于检查子查询是否包含任何行。例如,我们可以用它来查找空类别:
Future<List<Category>> emptyCategories() {final hasNoTodo = notExistsQuery(select(todoItems)..where((row) => row.category.equalsExp(categories.id)));return (select(categories)..where((row) => hasNoTodo)).get();
}
10.4完整子查询
Drift 还支持出现在 s 中的子查询,这些子查询在joins 的文档JOIN中有描述 。
11.自定义表达式
如果要将自定义 SQL 内联到 Dart 查询中,可以使用CustomExpression类。它接受一个sql参数,允许您编写自定义表达式:
const inactive =CustomExpression<bool>("julianday('now') - julianday(last_login) > 60");
select(users)..where((u) => inactive);
注意:使用过多查询很容易导致无效查询CustomExpressions。如果您觉得需要使用它们,因为您使用的功能在 Drift 中不可用,请考虑创建问题告知我们。如果您只是喜欢 SQL,也可以查看 类型安全的编译型 SQL 。
尤其是在自定义表达式需要嵌入子表达式时,CustomExpression会有些限制。一种更复杂的替代方案是直接实现,它可以让你完全控制代码片段如何写入 SQL 。例如,这是一个使用 Drift 查询构建器实现行值的Expression表达式:
/// Writes row values (`(1, 2, 3)`) into SQL. We use [Never] as a bound because
/// this expression cannot be evaluated, it's only useful as a subexpression.
final class RowValues extends Expression<Never> {final List<Expression> expressions;RowValues(this.expressions); Precedence get precedence => Precedence.primary;void writeInto(GenerationContext context) {context.buffer.write('(');for (final (i, expr) in expressions.indexed) {if (i != 0) context.buffer.write(', ');expr.writeInto(context);}context.buffer.write(')');}
}
然后可以像这样使用它:
void rowValuesUsage() {select(animals).where((row) {// Generates (amount_of_legs, average_livespan) < (?, ?)return RowValues([row.amountOfLegs, row.averageLivespan]).isSmallerThan(RowValues([Variable(2), Variable(10)]));});
}