- 最佳实践丨云数据库实现联表+聚合查询
聚合是云开发 CloudBase 数据库中非常重要的一种数据批处理操作方式。聚合操作可以将数据分组(或者不分组,即只有一组/每个记录都是一组),然后对每组数据执行多种批处理操作,最后返回结果。 有了聚合能力,可以方便的解决很多没有聚合能力时无法实现或只能低效实现的场景,包括分组查询、只取某些字段的统计值或变换值返回、流水线式分阶段批处理、获取唯一值(去重)等。 本文就以一个简单的实例解释如何在云数据库中,实现十分常用的联表+聚合查询操作。 场景说明假设数据库内存在两个集合:[代码]class[代码] 与 [代码]student[代码],存在以下数据: class(班级信息): [图片] student(学生信息): [图片] 现在需要查询徐老师所带的班级里面所有学生的平均成绩。 代码示例1、lookup 联表查询首先我们需要把 student 内的所有数据,按照 class_id 进行分组,这里我们使用云数据库的 lookup 操作符: lookup({ from: "student", //要关联的表student localField: "id", //class表中的关联字段 foreignField: "class_id", //student表中关联字段 as: "stu" //定义输出数组的别名 }).end(); 这个语句会查出来下面的结果,会查出班级的信息以及该班级所对应的所有学生的信息: {"list": [{ "id":1, "teacher":"王老师", "cname":"一班", "stu":[ { "sname":"宁一", "class_id":1, "score":90 } ] }, { "id":2, "teacher":"徐老师", "cname":"二班", "stu":[ { "class_id":2, "sname":"张二", "score":100 }, { "class_id":2, "sname":"李二", "score":80 } ] }] } 但是我们只需要徐老师所在班级学生的数据,所以需要进一步过滤。 2、match 条件匹配现在就只是返回徐老师所在班级的学生数据了,学生数据在 stu 对应的数组里面: .lookup({ from: 'student', localField: 'id', foreignField: 'class_id', as: 'stu' }) .match({ teacher:"徐老师" }) .end() 现在就只是返回徐老师所在班级的学生数据了,学生数据在 stu 对应的数组里面: { "list": [ { "_id": "5e847ab25eb9428600a512352fa6c7c4", "id": 2, "teacher": "徐老师", "cname": "二班", //学生数据 "stu": [ { "_id": "37e26adb5eb945a70084351e57f6d717", "class_id": 2, "sname": "张二", "score": 100 }, { "_id": "5e847ab25eb945cf00a5884204297ed8", "class_id": 2, "sname": "李二", "score": 80 } ] } ] } 接下来我们继续优化代码,直接返回学生的平均分数。 3、直接返回学生成绩平均值如果想要在被连接的表格中(本课程中的 student)做聚合操作,就用 pipeline 方法: .lookup({ from: 'student', pipeline: $.pipeline() .group({ _id: null, score: $.avg('$score') }) .done(), as: 'stu' }) .match({ teacher:"徐老师" }) .end() 现在输出的数据是这样的: { "list": [ { "_id": "5e847ab25eb9428600a512352fa6c7c4", "id": 2, "teacher": "徐老师", "cname": "二班", "stu": [{ "_id": null, "score": 90 }] } ] } 但是现在输出的数据有点复杂,如果只想显示 teacher 和 score 这两个值,我们再进行下面的操作。 4. 只显示 teacher 和 score 这两个值我们使用 replaceRoot、mergeObjects 和 project 进行最后的处理: .lookup({ from: 'student', pipeline: $.pipeline() .group({ _id: null, score: $.avg('$score') }) .done(), as: 'stu' }) .match({ teacher:"徐老师" }) .replaceRoot({ newRoot: $.mergeObjects([$.arrayElemAt(['$stu', 0]), '$$ROOT']) }) .project({ _id:0, teacher:1, score:1 }) .end() 现在输出的数据是这样的: { "list": [{ "score": 90, "teacher": "徐老师" }] } 相关文档:云开发聚合搜索:https://docs.cloudbase.net/database/aggregate.html 产品介绍云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为开发者提供高可用、自动弹性扩缩的后端云服务,包含计算、存储、托管等serverless化能力,可用于云端一体化开发多种端应用(小程序,公众号,Web 应用,Flutter 客户端等),帮助开发者统一构建和管理后端服务和云资源,避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。 开通云开发:https://console.cloud.tencent.com/tcb?tdl_anchor=techsite 产品文档:https://cloud.tencent.com/product/tcb?from=12763 技术文档:https://cloudbase.net?from=10004 技术交流加Q群:601134960 最新资讯关注微信公众号【腾讯云云开发】
2021-04-08 - 小程序隐私政策已经更新了手机号的手机内容,而且也配置了隐私协议弹窗,但是使用手机号快速验证组件报错?
1.已经在小程序的隐私政策钟配置了手机号 [图片] 2.配置了隐私保护指引弹窗(这里就有一个问题,这个handleAgreePrivacyAuthorization总是不能正常回调,后面附带了具体的代码,当然这个代码我抄的别人的,因为不能回调,所以我自己加了一个tap来主动触发这个操作,不加的话点击没有任何反应) [图片][图片] 点击同意,OK,那么这一步我认为是可以了(先忽略重复不重复的问题,起码效果到位了,当然我也不知道是不是真的到位了)~ 3.使用手机号快速验证组件的时候,仍然提示 [图片] 那么,问题出在了哪里呢,我实在是找不到嘞~ <template> <view class="privacy" v-if="showPrivacy"> <view class="content"> <view class="title">隐私保护指引</view> <view class="des"> 在使用当前小程序服务之前,请仔细阅读<text class="link" @tap="openPrivacyContract">{{ privacyContractName }}</text >。如你同意{{ privacyContractName }},请点击“同意”开始使用。 </view> <view class="btns"> <button class="item reject" @tap="exitMiniProgram">拒绝</button> <button id="agree-btn" class="item agree" open-type="agreePrivacyAuthorization" @agreeprivacyauthorization="handleAgreePrivacyAuthorization" @tap="handle">同意</button> </view> </view> </view> </template> <script> export default { data() { return { privacyContractName: '《隐私保护引导》', showPrivacy: false, } }, created() { this.checkPrivacySetting() }, methods: { checkPrivacySetting() { wx.getPrivacySetting({ success: res => { console.log('getPrivacySetting', res) this.showPrivacy = true // needAuthorization是否需要用户授权隐私协议 // 返回结果为: res = { needAuthorization: true/false, privacyContractName: '《xxx隐私保护指引》' } if (res.needAuthorization) { // 需要弹出隐私协议 this.showPrivacy = true } else { this.showPrivacy = false // 用户已经同意过隐私协议,所以不需要再弹出隐私协议,也能调用已声明过的隐私接口 // wx.getUserProfile() // wx.chooseMedia() // wx.getClipboardData() // wx.startRecord() } }, fail: () => {}, complete: () => {}, }) }, // 打开隐私协议 openPrivacyContract() { wx.openPrivacyContract({ fail: () => { wx.showToast({ title: '遇到错误', icon: 'error', }) }, }) }, // 拒绝隐私协议 exitMiniProgram() { console.log('拒绝隐私协议') const that = this // 直接退出小程序 // wx.exitMiniProgram() wx.showModal({ // 如果拒绝,我们将无法获取您的信息, 包括手机号、位置信息、相册等该小程序十分重要的功能,您确定要拒绝吗? content: '您确定要拒绝吗?', success: res => { if (res.confirm) { that.showPrivacy = false wx.exitMiniProgram({ success: () => { console.log('退出小程序成功') }, }) } }, }) }, handle() { console.log('handle') this.handleAgreePrivacyAuthorization() }, // 同意隐私协议 handleAgreePrivacyAuthorization() { console.log('agree privacy') var that = this wx.requirePrivacyAuthorize({ success: () => { // 用户同意授权 // 继续小程序逻辑 that.showPrivacy = false console.log('agree handle') }, fail: () => {}, // 用户拒绝授权 complete: () => {}, }) }, }, } </script> <style> .privacy { position: fixed; top: 0; right: 0; bottom: 0; left: 0; background: rgba(0, 0, 0, 0.5); z-index: 9999999; display: flex; align-items: center; justify-content: center; } .content { width: 632rpx; padding: 48rpx; box-sizing: border-box; background: #fff; border-radius: 16rpx; } .content .title { text-align: center; color: #333; font-weight: bold; font-size: 32rpx; } .content .des { font-size: 26rpx; color: #666; margin-top: 40rpx; text-align: justify; line-height: 1.6; } .content .des .link { color: #07c160; text-decoration: underline; } .btns { margin-top: 48rpx; display: flex; } .btns .item { justify-content: space-between; width: 244rpx; height: 80rpx; display: flex; align-items: center; justify-content: center; border-radius: 16rpx; box-sizing: border-box; border: none; } .btns .reject { background: #f4f4f5; color: #909399; } .btns .agree { background: #07c160; color: #fff; } </style>
2023-11-21 - 小程序云开发where条件怎么根据时间筛选
小程序云开发过程中,碰到需要查询大于或小于某个时间的数据,该如何写where条件?db.collection(...).where(???)where里现在只会用条件 key:val 即key=val
2019-07-24 - 一个云函数五行代码搞定云调用openapi
云调用接口如下: https://developers.weixin.qq.com/miniprogram/dev/api-backend/ 1、该文档中的几十个接口,全部可由下面5行代码实现: 2、同时支持共享环境下的云调用 云函数名:openapi index.js代码: const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) exports.main = async event => { let appid = cloud.getWXContext().FROM_APPID || cloud.getWXContext().APPID return await cloud.openapi({appid})[event.action](event.body) } 小程序端调用代码: onOpenapi: function () { wx.cloud.callFunction({ name: 'openapi', data: { action: 'urlscheme.generate', body: {} } }).then(res => { console.log(res) }) }, 将云调用相关的云函数合并成一个。 而且,极简。。。
2022-09-29 - 只有三行代码的神奇云函数的功能之四:获取电话号码
这是一个神奇的网站,哦不,神奇的云函数,它只有三行代码:(真的只有三行哦) 云函数:login index.js: const cloud = require('wx-server-sdk') cloud.init() exports.main = async (event) => { return { ...event, ...cloud.getWXContext() } } 神奇功能之四:获取电话号码: 还是这三行代码,获取用户的电话号码。 wxml: <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber" >{{mobile||"获得电话号码"}}</button> js: getPhoneNumber: function (e) { wx.cloud.callFunction({ name: 'login', data: {weRunData: wx.cloud.CloudID(e.detail.cloudID)} }).then(res => { this.setData({ mobile: res.result.weRunData.data.phoneNumber }) }) } 其他功能: 神奇功能之一:获取openid: https://developers.weixin.qq.com/community/develop/article/doc/00080c6e3746d8a940f9b43e55fc13 神奇功能之二:不用授权获取unionid: https://developers.weixin.qq.com/community/develop/article/doc/000a0c6b580338e947f9db0c65b813 神奇功能之三:100%成功获取unionid: https://developers.weixin.qq.com/community/develop/article/doc/00066a967c4e384949f93fe1151413 神奇功能之五:获取群id: 将小程序分享到某群里,可获得该群的群id, https://developers.weixin.qq.com/community/develop/article/doc/000ea802c00f70894cf9fe72556013 [图片]
2020-12-16 - 刚收到通知获取手机号收费开始了?
[图片] https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getRealtimePhoneNumber.html https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/paymentManage.html 未来:旧版本接口依然可以使用,只是需要收费而已,不想做改动的交钱就行了。如果想用新API方法就去改吧,多花一分钱。 可能支持的省钱办法: 授权手机号后,服务端将openId、手机号进行绑定。用户onLaunch打开小程序的时候通过wx.login获取code去解密openId,同时由于服务端已经绑定过手机号,所以可以使用该手机号进行登录,并同步返回token、jwtToken等登录态。这样可以做到用户冷启动小程序时自动登录上,减少使用授权的逻辑。业务按钮点击后 先调用wx.login,如果返回token则进行后续业务,如果没返回则弹出自定义弹窗,弹窗内点击按钮再进行手机号授权。(也可以在部分页面onLoad里wx.login),这个场景因为会延长流程,所以产品说不考虑,先直接打开页面就登录上,你们的各自看各自的业务场景吧。然后有四个疑问: 充值购买次数后会,如果小程序被封禁了,充值的金额是否可退款。购买数量是否支持按量付费?如果次数用完了,未购买新的次数,用户端的表现是什么?如果次数用完了,之前文档说的余量20%、10%、5%时会发模板消息提醒,文档相关现在已经删除了,是否还会发?[图片] ———————————————————————————————————————————————— 今天看了下文档做了改动: 退款规则:若购买有误,且未正式开始使用资源包前,可以在支付成功后的7天内申请退款。款项将在3-5个工作日内从原支付路径返回;若资源包已经开始使用(使用1次及以上),则不能申请退款;若支付成功后超过7天,未发起退款申请,亦不能再申请退款。 那么小程序被封了应该是不退的。不确定,等官方回复次数用完了,用户授权不会弹出授权弹窗,会返回一个errNo:1400001,用户判断等于这个errNo的时候跳转到自己的账密登录页面。不确定,等官方回复———————————————————————————————————————————————— 据了解老版本的快速验证组件(获取手机号),180天才会发送短信验证一次,为啥能每次授权都收费0.03元。 社区搜了一张图,180天没验证的应该会弹这个,不是说是短信运营成本么?为啥不是第180天验证那次费用让我们付,而是每次授权都付? [图片] 手机号授权改造后的效果: 打开职位详情页:优先调用接口判断openId是否绑定过。 如果未绑定:使用button的open-type=“getPhoneNumber”,点击报名弹出手机号授权,授权成功后与openId进行绑定落库。 如果已绑定,页面通过变量判断使用wx.login静默授权,同时服务端拿到绑定的手机号后进行登录操作,同步返回登录态(token/jwtToken)。 退出登录页面增加解绑操作(服务端解除openId与手机号的绑定),此时用户再次点击报名,就会弹出手机号授权,方便用户切换手机号。 [视频]
2023-07-27 - 更换手机号后并且修改了微信号,对应小程序的openid会改变么。
大家都知道,更换手机等设备,一个微信号是在同一个微信小程序中openid是不变的。如果对方还没有设置微信号呢?然后某天他设置了微信号,再去打开小程序,openid还是原来的那个她吗
2019-07-24 - 用户更换手机号,如何及时获取最新的手机号?
因为正常是只有小程序登录失效后,才会去通过getPhoneNumber获取并验证手机号,不会频繁获取。用户更换手机号后,小程序会登录失效吗?如果不会,如何及时获取更换后的手机号呢?
2020-07-02