当前位置: 首页 > news >正文

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


实例:备忘录便签(Memo)|技术:置顶排序、部分字段更新、颜色筛选

一、两个核心查询的深入

便签数据层有两个「看似简单、实则讲究」的查询:置顶排序(双键 ORDER BY)和颜色筛选(条件 + 排序组合)。本篇文章深入实现细节,并重点展开部分字段更新——这是 RDB 相对 ORM 的优势所在。

二、置顶排序:双键 ORDER BY 的层级结构

便签墙的顺序是「置顶在前,普通在后;同组内最近编辑在前」:

staticasyncqueryAll(context:common.Context):Promise<Memo[]>{conststore=awaitMemoDao.getStore(context);constpredicates=newrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.orderByDesc('pinned').orderByDesc('updated_time');constresult=awaitstore.query(predicates);returnMemoDao.collect(result);}

生成的 SQL:

SELECT*FROMmemoORDERBYpinnedDESC,updated_timeDESC;

双键排序的执行逻辑:先按第一个键(pinned)排——1(置顶)在前、0(普通)在后,形成两大组;组内再按第二个键(updated_time)排——每组内部最新编辑的在前。排序键的优先级从左到右递减,这就是「分组 + 组内序」的 SQL 表达。

为什么置顶组内也要按时间排?置顶区有多张便签时(如 3 张置顶),它们的相对顺序应该反映「最近编辑」——用户刚改过的置顶便签浮到置顶区顶部。两层排序都有业务意义,不是随意堆叠。

pinned 用 DESC 而不是 ASC 的原因:pinned 存 0/1,DESC让 1 在前(置顶优先)。如果当初设计「1=普通、0=置顶」,排序方向就反了——枚举值与排序方向的配合是字段设计时就要想清楚的事。

三、部分字段更新:只改该改的列

便签的高频操作是「只改一个属性」——置顶、换颜色、改内容。三个场景对应三种更新形态(11-1 已展示代码),这里深入「为什么部分更新如此重要」:

场景一:置顶/取消置顶——只改 pinned:

staticasynctogglePin(context:common.Context,id:number,pinned:number):Promise<number>{conststore=awaitMemoDao.getStore(context);constvalues:relationalStore.ValuesBucket={pinned:pinned,updated_time:Date.now(),};constpredicates=newrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.equalTo('id',id);returnawaitstore.update(values,predicates);}

生成的 SQL:

UPDATEmemoSETpinned=1,updated_time=1750000000000WHEREid=5;

只更新两个列——标题、内容、颜色原封不动。RDB 的 UPDATE 天然支持部分更新(SET 子句写什么改什么),不需要「先 SELECT 全量 → 内存改字段 → UPDATE 全量」的 ORM 式冗余操作。这就是直接操作 SQL 的优势:最小写入、零冗余读

场景二:换颜色——只改 color(togglePin 同构)。

场景三:编辑内容——改标题 + 内容 + 颜色(全量更新,见 11-1)。

部分更新的共性:三种更新都顺带刷新 updated_time——因为便签墙按 updated_time 排序,任何修改都应让便签「上浮」到同组顶部。「修改 = 上浮」是内容型应用的时间语义,部分更新时不忘刷时间戳,保证排序正确。

一个容易忽略的细节:部分更新不更新 created_time——创建时间永不变更,只有 updated_time 随修改刷新。双时间戳的职责划分在实例 4 建立过,这里再次验证:created_time 是「出生证明」,updated_time 是「最近动态」。

四、颜色筛选:条件 + 排序的组合

按颜色筛选便签:

staticasyncqueryByColor(context:common.Context,color:string):Promise<Memo[]>{conststore=awaitMemoDao.getStore(context);constpredicates=newrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.equalTo('color',color).orderByDesc('pinned').orderByDesc('updated_time');constresult=awaitstore.query(predicates);returnMemoDao.collect(result);}

生成的 SQL:

SELECT*FROMmemoWHEREcolor='#FFE8A3'ORDERBYpinnedDESC,updated_timeDESC;

筛选结果仍保持置顶排序——颜色过滤不破坏置顶语义。「过滤条件 + 排序」的组合是列表查询的通用形态:WHERE 缩小范围,ORDER BY 确定顺序,两者独立组合。

页面分支:筛选「全部」时 queryAll(不过滤),选具体颜色时 queryByColor——页面 refresh 里按 colorFilter 分支(11-2 讲过 switchColor)。两个查询方法共享排序逻辑(都写双键排序),读者可抽公共方法减少重复,本实例保持直白。

五、更新的返回语义:受影响行数

store.update返回受影响行数——更新了几行。单条更新(WHERE id=?)正常返回 1;返回 0 说明「条件没匹配到」(id 不存在)。页面通常不检查返回值(乐观更新),但数据层可以据此判断:

constaffected=awaitMemoDao.togglePin(context,id,1);if(affected===0){hilog.warn(DOMAIN,TAG,`便签不存在: id=${id}`);}

受影响行数 = 更新成功的验证——生产环境的「更新后校验」常用它判断「条件是否命中」。

六、技术要点对照表

技术点实现方式生产价值
置顶排序orderByDesc(‘pinned’)分组结构
双键排序pinned + updated_time组内最近在前
部分更新ValuesBucket 只放目标列最小写入
更新上浮部分更新刷 updated_time修改即置顶组内上浮
颜色筛选equalTo(color) + 排序过滤不破坏序
受影响行数update 返回值更新校验

七、常见问题 FAQ

Q1:部分更新和全量更新怎么选?
A:只改 1~2 个字段用部分更新(togglePin/changeColor),改多个字段(编辑弹窗保存标题+内容+颜色)用全量更新。判断标准:改动字段数量——1 个用部分,多个用全量。全量更新的实现更简单(一个方法),部分更新胜在最小写。

Q2:为什么换颜色也要刷新 updated_time?
A:因为「换颜色」也是「修改」——修改后的便签应该上浮到同组顶部(用户刚操作过,大概率还想看它)。任何写操作都刷新 updated_time是本实例的约定,保证「最近动态」语义一致。

Q3:置顶便签能不能超过一张?
A:可以,pinned=1 的便签可以有任意多张,全部排在普通便签上面。如果要「唯一置顶」(只能钉一张),需要业务约束(置顶前先取消其他置顶)——本实例允许多张,更灵活。

Q4:颜色筛选和置顶排序会不会冲突?
A:不会——筛选是 WHERE(缩小范围),排序是 ORDER BY(确定顺序),两者正交。筛选出黄色便签后,它们内部仍按「置顶优先、最近编辑优先」排列。

Q5:UPDATE memo SET pinned=1不带 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):Promise<Memo[]>{conststore=awaitMemoDao.getStore(context);constpredicates=newrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.orderByDesc('pinned').orderByDesc('updated_time');constresult=awaitstore.query(predicates);returnMemoDao.collect(result);}
SELECT*FROMmemoORDERBYpinnedDESC,updated_timeDESC;

排序语义:pinned=1 的置顶组整体在前,pinned=0 的普通组在后;每组内部按 updated_time 倒序——最近编辑的浮到组顶。「先分组、组内再排」正是双键 ORDER BY 的直观读法,第一个键决定大组,第二个键决定组内次序。

9.2 按内容搜索:LIKE 模糊匹配

staticasyncqueryByKeyword(context:common.Context,keyword:string):Promise<Memo[]>{conststore=awaitMemoDao.getStore(context);constpredicates=newrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.like('content',`%${keyword}%`).orderByDesc('pinned').orderByDesc('updated_time');constresult=awaitstore.query(predicates);returnMemoDao.collect(result);}

生成的 SQL:

SELECT*FROMmemoWHEREcontentLIKE'%机票%'ORDERBYpinnedDESC,updated_timeDESC;

LIKE 的匹配规则%匹配任意多个字符(含 0 个),所以%机票%匹配「内容里任意位置含机票」的便签;开头%表示不限定起点,结尾%表示不限定终点。搜索条件同样叠加双键排序——搜索结果仍是「置顶优先、最近编辑在前」,用户从搜索回到便签墙时视觉顺序不跳变。

keyword 为空时的处理:页面层在调用前判空,空关键字直接走 queryAll,避免LIKE '%%'全表匹配这种无意义查询。

9.3 三种查询的关系

查询方法条件排序用途
queryAll双键便签墙默认展示
queryByKeywordcontent LIKE %kw%双键顶部搜索框
queryByColorcolor = ?双键颜色筛选条

三个方法共享同一排序尾巴,只是 WHERE 不同——这是「条件与排序正交」的又一体现:筛选/搜索决定「哪些行」,排序决定「什么顺序」,两者可自由组合。

十、置顶切换与新增编辑的完整流程

10.1 置顶切换:一条 UPDATE

staticasynctogglePin(context:common.Context,id:number,pinned:number):Promise<number>{conststore=awaitMemoDao.getStore(context);constvalues:relationalStore.ValuesBucket={pinned:pinned,updated_time:Date.now(),};constpredicates=newrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.equalTo('id',id);returnawaitstore.update(values,predicates);}
UPDATEmemoSETpinned=0,updated_time=1750000000000WHEREid=5;

注意 pinned 传的是目标值——页面调用togglePin(context, id, memo.pinned === 1 ? 0 : 1),把「要不要置顶」的决定权放在调用方,数据层只负责「把某行改成指定值」。顺带刷 updated_time:取消置顶也算修改,刷新时间戳后该便签在普通组内浮到顶部,符合「刚操作过就还看它」的直觉。

10.2 新增便签:INSERT 三步

staticasyncinsert(context:common.Context,title:string,content:string,color:string):Promise<number>{conststore=awaitMemoDao.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);

新增的三个细节:① 新便签默认不置顶(pinned=0);② created_time 与 updated_time 同时初始化——出生即「最近动态」;③ insert 返回rowId(自增主键),页面可用它定位新便签(如滚动到新签位置)。

10.3 编辑便签:全量 UPDATE

编辑弹窗保存时改标题、内容、颜色三个字段:

staticasyncupdateMemo(context:common.Context,memo:Memo):Promise<number>{conststore=awaitMemoDao.getStore(context);constvalues:relationalStore.ValuesBucket={title:memo.title,content:memo.content,color:memo.color,updated_time:Date.now(),};constpredicates=newrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.equalTo('id',memo.id);returnawaitstore.update(values,predicates);}
UPDATEmemoSETtitle='新标题',content='新内容',color='#4FC3F7',updated_time=1750000000000WHEREid=3;

编辑不碰 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):Promise<number>{conststore=awaitMemoDao.getStore(context);constpredicates=newrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.equalTo('id',id);returnawaitstore.delete(predicates);}
DELETEFROMmemoWHEREid=8;

删除语义:物理删除(行消失),不保留回收站。本实例便签无关联表(不涉及外键级联),删除就是一行 DELETE。同样必须带 WHERE——不带条件的 DELETE 会清空整张表,和 UPDATE 一样是危险操作。

11.2 颜色统计:分组聚合

统计六种颜色各有多少张,可在标题栏显示「黄色 2 张」这类角标:

staticasynccountByColor(context:common.Context):Promise<Map<string,number>>{conststore=awaitMemoDao.getStore(context);constpredicates=newrelationalStore.RdbPredicates(MemoDao.TABLE);predicates.groupBy(['color']);constresult=awaitstore.query(predicates,['color','COUNT(*) AS cnt']);// 遍历 result,把 color -> cnt 装进 Map}
SELECTcolor,COUNT(*)AScntFROMmemoGROUPBYcolor;
colorcnt
#FFE8A32
#FFB6C12
#A8E6CF2
#81D4FA2
#CE93D82
#FFB74D2

GROUP 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。本实例只搜内容(标题短、内容长,命中率高),读者可按需扩展。

Q2:LIKE 里的%_会被用户输入干扰吗?
A:会——用户搜「100%」时%会被当成通配符。含通配符的业务搜索需要转义(把输入中的%替换为\%并用 ESCAPE 子句),本实例的便签内容几乎不含这些字符,直接拼接可接受;生产环境建议做转义。

Q3:置顶切换后便签墙立即刷新吗?
A:是——togglePin 成功后页面调 refresh() 重新 queryAll。数据层只负责写库,UI 刷新由页面主动触发,两层职责清晰。

Q4:删除的返回值有什么用?
A:delete 返回受影响行数,0 表示该 id 已不存在(重复点击删除)。页面可以据此提示「便签不存在或已删除」。

Q5:颜色统计是每次刷新都查一次吗?
A:本实例在 refresh 时随 queryAll 一起查(两次查询,数据量小无压力)。统计值随便签增删改变化,不能缓存死值;若数据量大,可改为「增删改时只更新对应颜色计数」。

Q6:GROUP BY 的结果顺序稳定吗?
A:不稳定——SQL 不保证分组结果顺序。需要固定顺序就加 ORDER BY(如GROUP BY color ORDER BY cnt DESC按数量倒序)。颜色角标按固定色板渲染的话,更稳妥的做法是在代码里按 MEMO_COLORS 顺序取数。

十三、本文章节小结

本篇文章把便签数据层的「读、写、删、统计」补全:查询三件套(全量双键排序 / LIKE 搜索 / 颜色筛选)共享排序尾巴置顶与换色走部分更新而编辑走全量更新删除带 WHERE 保安全GROUP BY 支撑颜色统计。至此数据层全部能力就绪——下一篇(11-4)注入 12 张彩色种子数据,便签墙即刻五彩斑斓。

http://www.jsqmd.com/news/1401332/

相关文章:

  • 2026 年现阶段西安专业的玻璃钢化粪池供应厂家有哪些,小区楼下藏着这玩意儿,十年竟能省下近万元维修费? - 行业推荐官-2
  • 2026 年当下,泸西比较好的抗爆墙供应厂家哪个好,车间这玩意儿竟能扛住十级爆轰?看完才懂它的价值远不止安全!-道元乾抗爆墙泄爆墙 - 行业严选官
  • 2026 年 8 月新发布:永年靠谱的GEO推广公司选哪家,别再烧钱获客了,这招能让你的精准客户主动找上门 - 企业信息推荐-2
  • ‎2026‎年‎4‎月‎7‎日某分享会
  • amis6.13版本报错“Unhandled Rejection (Error): [mobx-state-tree] You are trying to ...”的解决方案
  • Hydro Hack 功能:手刃伪AC
  • 设备频繁掉线、数据悄悄丢失,嵌入式上 MQTT 通信怎么选?一个不到2000行的 C 语言库就够了
  • 十二张彩签点亮便签墙:鸿蒙备忘录种子数据与色彩效果
  • 2026 年新消息:鄱阳专业的陶粒供应商哪家靠谱,家里养花总烂根?这玩意儿竟然能把盆土透气性拉满,原来很多人都用错了!-良品陶粒 - 行业推荐官【认证】
  • 免费不限时长语音转文字:Whisper电脑端从零部署教程,附手机端、网页端平替方案 - 软件盘点管家
  • 傲梅分区助手技术员
  • 基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南
  • 2026 年当下,广州专业的交通护栏厂家电话,差点酿成事故的那堆铁疙瘩,竟藏着你忽略的保命细节-京标丝网 - 行业严选官
  • 2026年服务参考重庆市验光设备齐全办理流程:从需求确认到服务完成、预约准备与办理步骤、完整流程和资料清单,关键事项一文讲清 - 小校长
  • 大模型Prompt工程实操:5种提示词技巧与Python代码示例
  • 2026年广东建筑资质代办服务格局洞察与选型参考 - 卓企推荐
  • 2026 年新发布:宿城比较好的线上获客运营中心电话,别再傻傻发朋友圈了,这玩意儿能帮你精准挖到愿意掏钱的客户 - 行业推荐官【认证】
  • Day42-AI5-6期陪跑
  • 南京冷库租赁市场观察:从“一库难求”到专业化服务升级 - 装修教育财税推荐2026
  • 嵌入式基础三:ADC 和 DMA
  • 云安全左移:解析默认防火墙与API开放对运维的影响
  • 基于SpringBoot+Vue的露营探险系统的设计与实现
  • 2026 年新消息:昌乐口碑好的木头棺材供货商哪家专业,老家堂屋那堆旧木料,竟藏着比养老钱还重要的秘密? - 行业推荐官【认证】
  • 2507双相钢一站式采购中心:国标/美标齐全,性价比高,质保书齐全 - 2027品牌AI展
  • 为什么现在虚拟存储用得越来越少了?
  • 工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?
  • 上海房产纠纷律所怎么选?2026 年8月**指南与避坑攻略 - 孙青律师13681945561
  • 连通图与强连通图:从基础概念到算法实践与前沿计数
  • Chrome浏览器Cookie管理全攻略:从原理到实战,保护隐私与提升效率
  • 2026年寄重物哪家物流便宜?一文看懂计费规则+省钱攻略 - 快递物流资讯