置顶排序与部分更新:ArkTS 实现鸿蒙便签的轻量 SQL 更新

发布时间:2026/8/16 0:15:09
置顶排序与部分更新:ArkTS 实现鸿蒙便签的轻量 SQL 更新 实例备忘录便签Memo技术置顶排序、部分字段更新、颜色筛选一、两个核心查询的深入便签数据层有两个「看似简单、实则讲究」的查询置顶排序双键 ORDER BY和颜色筛选条件 排序组合。本篇文章深入实现细节并重点展开部分字段更新——这是 RDB 相对 ORM 的优势所在。二、置顶排序双键 ORDER BY 的层级结构便签墙的顺序是「置顶在前普通在后同组内最近编辑在前」staticasyncqueryAll(context:common.Context):PromiseMemo[]{conststoreawaitMemoDao.getStore(context);constpredicatesnewrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.orderByDesc(pinned).orderByDesc(updated_time);constresultawaitstore.query(predicates);returnMemoDao.collect(result);}生成的 SQLSELECT*FROMmemoORDERBYpinnedDESC,updated_timeDESC;双键排序的执行逻辑先按第一个键pinned排——1置顶在前、0普通在后形成两大组组内再按第二个键updated_time排——每组内部最新编辑的在前。排序键的优先级从左到右递减这就是「分组 组内序」的 SQL 表达。为什么置顶组内也要按时间排置顶区有多张便签时如 3 张置顶它们的相对顺序应该反映「最近编辑」——用户刚改过的置顶便签浮到置顶区顶部。两层排序都有业务意义不是随意堆叠。pinned 用 DESC 而不是 ASC 的原因pinned 存 0/1DESC让 1 在前置顶优先。如果当初设计「1普通、0置顶」排序方向就反了——枚举值与排序方向的配合是字段设计时就要想清楚的事。三、部分字段更新只改该改的列便签的高频操作是「只改一个属性」——置顶、换颜色、改内容。三个场景对应三种更新形态11-1 已展示代码这里深入「为什么部分更新如此重要」场景一置顶/取消置顶——只改 pinnedstaticasynctogglePin(context:common.Context,id:number,pinned:number):Promisenumber{conststoreawaitMemoDao.getStore(context);constvalues:relationalStore.ValuesBucket{pinned:pinned,updated_time:Date.now(),};constpredicatesnewrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.equalTo(id,id);returnawaitstore.update(values,predicates);}生成的 SQLUPDATEmemoSETpinned1,updated_time1750000000000WHEREid5;只更新两个列——标题、内容、颜色原封不动。RDB 的 UPDATE 天然支持部分更新SET 子句写什么改什么不需要「先 SELECT 全量 → 内存改字段 → UPDATE 全量」的 ORM 式冗余操作。这就是直接操作 SQL 的优势最小写入、零冗余读。场景二换颜色——只改 colortogglePin 同构。场景三编辑内容——改标题 内容 颜色全量更新见 11-1。部分更新的共性三种更新都顺带刷新 updated_time——因为便签墙按 updated_time 排序任何修改都应让便签「上浮」到同组顶部。「修改 上浮」是内容型应用的时间语义部分更新时不忘刷时间戳保证排序正确。一个容易忽略的细节部分更新不更新 created_time——创建时间永不变更只有 updated_time 随修改刷新。双时间戳的职责划分在实例 4 建立过这里再次验证created_time 是「出生证明」updated_time 是「最近动态」。四、颜色筛选条件 排序的组合按颜色筛选便签staticasyncqueryByColor(context:common.Context,color:string):PromiseMemo[]{conststoreawaitMemoDao.getStore(context);constpredicatesnewrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.equalTo(color,color).orderByDesc(pinned).orderByDesc(updated_time);constresultawaitstore.query(predicates);returnMemoDao.collect(result);}生成的 SQLSELECT*FROMmemoWHEREcolor#FFE8A3ORDERBYpinnedDESC,updated_timeDESC;筛选结果仍保持置顶排序——颜色过滤不破坏置顶语义。「过滤条件 排序」的组合是列表查询的通用形态WHERE 缩小范围ORDER BY 确定顺序两者独立组合。页面分支筛选「全部」时 queryAll不过滤选具体颜色时 queryByColor——页面 refresh 里按 colorFilter 分支11-2 讲过 switchColor。两个查询方法共享排序逻辑都写双键排序读者可抽公共方法减少重复本实例保持直白。五、更新的返回语义受影响行数store.update返回受影响行数——更新了几行。单条更新WHERE id正常返回 1返回 0 说明「条件没匹配到」id 不存在。页面通常不检查返回值乐观更新但数据层可以据此判断constaffectedawaitMemoDao.togglePin(context,id,1);if(affected0){hilog.warn(DOMAIN,TAG,便签不存在: id${id});}受影响行数 更新成功的验证——生产环境的「更新后校验」常用它判断「条件是否命中」。六、技术要点对照表技术点实现方式生产价值置顶排序orderByDesc(‘pinned’)分组结构双键排序pinned updated_time组内最近在前部分更新ValuesBucket 只放目标列最小写入更新上浮部分更新刷 updated_time修改即置顶组内上浮颜色筛选equalTo(color) 排序过滤不破坏序受影响行数update 返回值更新校验七、常见问题 FAQQ1部分更新和全量更新怎么选A只改 1~2 个字段用部分更新togglePin/changeColor改多个字段编辑弹窗保存标题内容颜色用全量更新。判断标准改动字段数量——1 个用部分多个用全量。全量更新的实现更简单一个方法部分更新胜在最小写。Q2为什么换颜色也要刷新 updated_timeA因为「换颜色」也是「修改」——修改后的便签应该上浮到同组顶部用户刚操作过大概率还想看它。任何写操作都刷新 updated_time是本实例的约定保证「最近动态」语义一致。Q3置顶便签能不能超过一张A可以pinned1 的便签可以有任意多张全部排在普通便签上面。如果要「唯一置顶」只能钉一张需要业务约束置顶前先取消其他置顶——本实例允许多张更灵活。Q4颜色筛选和置顶排序会不会冲突A不会——筛选是 WHERE缩小范围排序是 ORDER BY确定顺序两者正交。筛选出黄色便签后它们内部仍按「置顶优先、最近编辑优先」排列。Q5UPDATE memo SET pinned1不带 WHERE 会怎样A会把所有行都置顶——这是 SQL 的危险语义UPDATE 无条件更新全表。本实例所有 update 都带equalTo(id, ...)条件这是安全底线。写 UPDATE 永远记得 WHERE。Q6便签颜色能自定义任意值吗A数据层可以color 是 TEXT存什么渲染什么页面只提供六色色板。要支持自定义加一个颜色选择器取色盘即可——数据层无需改动。八、文章小结本篇文章深入讲解了便签数据层的核心双键置顶排序pinned DESC updated_time DESC 的分组结构、三种更新形态全量 / 部分置顶 / 部分颜色、颜色筛选与排序的组合。核心心法是「部分更新 RDB 的最小写入优势」——只改该改的列顺带刷新 updated_time 保持「修改即上浮」的时间语义。下一篇11-4展示 12 张彩色便签种子数据让便签墙一开屏就五彩斑斓。九、便签查询三件套全量 / 搜索 / 筛选便签页的数据展示由三个查询支撑全部便签默认墙、按内容搜索找关键字、按颜色筛选看某一色系。三者共用双键排序差异只在 WHERE 条件。9.1 全部便签置顶优先 时间倒序staticasyncqueryAll(context:common.Context):PromiseMemo[]{conststoreawaitMemoDao.getStore(context);constpredicatesnewrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.orderByDesc(pinned).orderByDesc(updated_time);constresultawaitstore.query(predicates);returnMemoDao.collect(result);}SELECT*FROMmemoORDERBYpinnedDESC,updated_timeDESC;排序语义pinned1 的置顶组整体在前pinned0 的普通组在后每组内部按 updated_time 倒序——最近编辑的浮到组顶。「先分组、组内再排」正是双键 ORDER BY 的直观读法第一个键决定大组第二个键决定组内次序。9.2 按内容搜索LIKE 模糊匹配staticasyncqueryByKeyword(context:common.Context,keyword:string):PromiseMemo[]{conststoreawaitMemoDao.getStore(context);constpredicatesnewrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.like(content,%${keyword}%).orderByDesc(pinned).orderByDesc(updated_time);constresultawaitstore.query(predicates);returnMemoDao.collect(result);}生成的 SQLSELECT*FROMmemoWHEREcontentLIKE%机票%ORDERBYpinnedDESC,updated_timeDESC;LIKE 的匹配规则%匹配任意多个字符含 0 个所以%机票%匹配「内容里任意位置含机票」的便签开头%表示不限定起点结尾%表示不限定终点。搜索条件同样叠加双键排序——搜索结果仍是「置顶优先、最近编辑在前」用户从搜索回到便签墙时视觉顺序不跳变。keyword 为空时的处理页面层在调用前判空空关键字直接走 queryAll避免LIKE %%全表匹配这种无意义查询。9.3 三种查询的关系查询方法条件排序用途queryAll无双键便签墙默认展示queryByKeywordcontent LIKE %kw%双键顶部搜索框queryByColorcolor ?双键颜色筛选条三个方法共享同一排序尾巴只是 WHERE 不同——这是「条件与排序正交」的又一体现筛选/搜索决定「哪些行」排序决定「什么顺序」两者可自由组合。十、置顶切换与新增编辑的完整流程10.1 置顶切换一条 UPDATEstaticasynctogglePin(context:common.Context,id:number,pinned:number):Promisenumber{conststoreawaitMemoDao.getStore(context);constvalues:relationalStore.ValuesBucket{pinned:pinned,updated_time:Date.now(),};constpredicatesnewrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.equalTo(id,id);returnawaitstore.update(values,predicates);}UPDATEmemoSETpinned0,updated_time1750000000000WHEREid5;注意 pinned 传的是目标值——页面调用togglePin(context, id, memo.pinned 1 ? 0 : 1)把「要不要置顶」的决定权放在调用方数据层只负责「把某行改成指定值」。顺带刷 updated_time取消置顶也算修改刷新时间戳后该便签在普通组内浮到顶部符合「刚操作过就还看它」的直觉。10.2 新增便签INSERT 三步staticasyncinsert(context:common.Context,title:string,content:string,color:string):Promisenumber{conststoreawaitMemoDao.getStore(context);constvalues:relationalStore.ValuesBucket{title:title,content:content,color:color,pinned:0,created_time:Date.now(),updated_time:Date.now(),};returnawaitstore.insert(MemoDao.TABLE,values);// 返回新行的 rowId}INSERTINTOmemo(title,content,color,pinned,created_time,updated_time)VALUES(回家机票,国庆机票已订…,#FFB74D,0,1750000000000,1750000000000);新增的三个细节① 新便签默认不置顶pinned0② created_time 与 updated_time 同时初始化——出生即「最近动态」③ insert 返回rowId自增主键页面可用它定位新便签如滚动到新签位置。10.3 编辑便签全量 UPDATE编辑弹窗保存时改标题、内容、颜色三个字段staticasyncupdateMemo(context:common.Context,memo:Memo):Promisenumber{conststoreawaitMemoDao.getStore(context);constvalues:relationalStore.ValuesBucket{title:memo.title,content:memo.content,color:memo.color,updated_time:Date.now(),};constpredicatesnewrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.equalTo(id,memo.id);returnawaitstore.update(values,predicates);}UPDATEmemoSETtitle新标题,content新内容,color#4FC3F7,updated_time1750000000000WHEREid3;编辑不碰 pinned——置顶状态由置顶操作单独管理编辑弹窗不提供置顶开关避免两个入口改同一个字段。全量 vs 部分的边界编辑改 3 个字段接近全量置顶/换色只改 1 个字段——字段多就全量字段少就部分。10.4 页面操作流程总览操作数据层方法SQL 形态是否刷 updated_time新建insertINSERT✅初始化编辑保存updateMemoUPDATE 3 列✅置顶/取消togglePinUPDATE 1 列✅换颜色changeColorUPDATE 1 列✅删除deleteDELETE—十一、删除便签与颜色统计11.1 删除DELETE 一条staticasyncdelete(context:common.Context,id:number):Promisenumber{conststoreawaitMemoDao.getStore(context);constpredicatesnewrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.equalTo(id,id);returnawaitstore.delete(predicates);}DELETEFROMmemoWHEREid8;删除语义物理删除行消失不保留回收站。本实例便签无关联表不涉及外键级联删除就是一行 DELETE。同样必须带 WHERE——不带条件的 DELETE 会清空整张表和 UPDATE 一样是危险操作。11.2 颜色统计分组聚合统计六种颜色各有多少张可在标题栏显示「黄色 2 张」这类角标staticasynccountByColor(context:common.Context):PromiseMapstring,number{conststoreawaitMemoDao.getStore(context);constpredicatesnewrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.groupBy([color]);constresultawaitstore.query(predicates,[color,COUNT(*) AS cnt]);// 遍历 result把 color - cnt 装进 Map}SELECTcolor,COUNT(*)AScntFROMmemoGROUPBYcolor;colorcnt#FFE8A32#FFB6C12#A8E6CF2#81D4FA2#CE93D82#FFB74D2GROUP BY 的读法按 color 分组每组算 COUNT(*)。投影列必须是分组键或聚合函数——SELECT color, COUNT(*)合法SELECT id, color, COUNT(*)不合法id 既非分组键也非聚合。统计结果可以驱动「颜色筛选条上的数字角标」让用户知道每个色点下有几张便签。十二、FAQ查询与操作补充问答Q1搜索只在 content 里找标题搜不到怎么办A把搜索条件扩成 OR 组合predicates.like(content, kw).or().like(title, kw)——RdbPredicates 支持链式 OR。本实例只搜内容标题短、内容长命中率高读者可按需扩展。Q2LIKE 里的%和_会被用户输入干扰吗A会——用户搜「100%」时%会被当成通配符。含通配符的业务搜索需要转义把输入中的%替换为\%并用 ESCAPE 子句本实例的便签内容几乎不含这些字符直接拼接可接受生产环境建议做转义。Q3置顶切换后便签墙立即刷新吗A是——togglePin 成功后页面调 refresh() 重新 queryAll。数据层只负责写库UI 刷新由页面主动触发两层职责清晰。Q4删除的返回值有什么用Adelete 返回受影响行数0 表示该 id 已不存在重复点击删除。页面可以据此提示「便签不存在或已删除」。Q5颜色统计是每次刷新都查一次吗A本实例在 refresh 时随 queryAll 一起查两次查询数据量小无压力。统计值随便签增删改变化不能缓存死值若数据量大可改为「增删改时只更新对应颜色计数」。Q6GROUP BY 的结果顺序稳定吗A不稳定——SQL 不保证分组结果顺序。需要固定顺序就加 ORDER BY如GROUP BY color ORDER BY cnt DESC按数量倒序。颜色角标按固定色板渲染的话更稳妥的做法是在代码里按 MEMO_COLORS 顺序取数。十三、本文章节小结本篇文章把便签数据层的「读、写、删、统计」补全查询三件套全量双键排序 / LIKE 搜索 / 颜色筛选共享排序尾巴、置顶与换色走部分更新而编辑走全量更新、删除带 WHERE 保安全、GROUP BY 支撑颜色统计。至此数据层全部能力就绪——下一篇11-4注入 12 张彩色种子数据便签墙即刻五彩斑斓。