美文网首页
thinkphp6的mongodb聚合操作使用笔记!

thinkphp6的mongodb聚合操作使用笔记!

作者: DragonersLi | 来源:发表于2021-09-29 16:22 被阅读0次

宝塔安装mongodb后,在使用的php版本开启mongodb扩展。composer安装topthink/think-mongo发现没这个包,README说请使用最新版本的topthink/think-orm本仓库不再维护更新,发现项目composer.json已经有这个包了。

[InvalidArgumentException]                                                                                                                         
  Could not find package topthink/think-mongo. 
It was however found via repository search, which indicates a consistency issue with the repository. 

mysql表结构:team_award团队奖励

CREATE TABLE `pjh_user_team_award` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT ' ID',
  `uid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT ' 用户ID',
  `pid` text COLLATE utf8mb4_unicode_ci COMMENT '递归上级ID,形如:,508,',
  `upid` int(11) unsigned DEFAULT '0' COMMENT ' 直推上级ID',
  `tpid` int(11) unsigned DEFAULT '0' COMMENT ' 顶级上级ID',
  `mobile` varchar(11) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '用户手机号',
  `nickname` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '用户微信昵称',
  `vip` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否会员',
  `award` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '奖励',
  `money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '金额',
  `cycle` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '周期(年月例如:202012)',
  `team_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '本周期团队总金额',
  `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT ' 状态【0:未发奖励;1:已发奖励】',
  `create_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT ' 创建时间',
  `update_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT ' 更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uni_uid_cycle` (`uid`,`cycle`) USING BTREE, 
  KEY `upid` (`upid`) USING BTREE,
  KEY `tpid` (`tpid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPACT

mongodb第一版设计文档结构:team_award团队奖励
把mysql数据导入mongodb后,代码从mysql连接改成mongodb,然后加索引

字段参考mysql表结构
uid  int型  用户ID
pid  string型  递归存储用户上级ID
upid int型 上级ID
tpid int型 顶级ID
mine_money  float型 个人金额
team_money float型  团队金额(包含个人金额)
team_award  float型  团队奖励
cycle  int型  当前年月(每月统计一次)
status  int型  奖励发放状态【0:未发放;1:已发放】
create_time int型 创建时间
update_time int型 更新时间

mongodb第二版设计文档结构:team_award团队奖励 但是考虑用户在增加,每个月一条用户信息,积累下来也是很大数据量。mongodb可以存储数组形式的子文档,改写成每个用户每年只有一条记录,把12个月份的数据变化的字段存储成子文档形式。thinkphp的mongodb文档资料太少,做个笔记,方便下次遇到同样问题,快速回忆。
mysql中递归用户所有上级ID组成字符串 ,1403,1308,542,518,508,
mongodb查询的时候字段类型严格区分。
mongodb用正则匹配查询很慢,存数组建索引
每条记录如:【id=>每一条记录,upid=>如果是第一个则存储,tpid=>如果是最后一个则存储

字段参考mysql表结构
uid  int型  用户ID
pid  array型  【存储3个字段,递归上级ID,首尾各一个字段,再加上所有的ID分割成单独一条记录】
      id int型  每个ID
      upid int型 上级ID
      tpid int型 顶级ID
cycle  array型 【存储8个字段,】    
      mine_money  float型 个人金额
      team_money float型  团队金额(包含个人金额)
      team_award  float型  团队奖励
      team_level  int型 团队等级
      cycle  int型  当前年月(每月统计一次)
      status  int型  奖励发放状态【0:未发放;1:已发放】
      create_time int型 创建时间
      update_time int型 更新时间
create_time int型 创建时间
update_time int型 更新时间

mongodb聚合查询后台数据列表

config/database.php配置mongodb数据库。然后mysql数据转存mongodb,用脚本循环读取数据批量插入mongodb

    // 数据库连接配置信息
    'connections'     => [
        'mysql' => [... ],

        // 更多的数据库配置信息
        'mongo' => [
            // 数据库类型
            'type'              => env('mongo.type', 'mongo'),
            // 服务器地址
            'hostname'          => env('mongo.hostname', '127.0.0.1'),
            // 数据库名
            'database'          => env('mongo.database', 'test'),
            // 用户名
            'username'          => env('mongo.username', 'root'), #
            // 密码
            'password'          => env('mongo.password', 'root'),#
            // 端口
            'hostport'          =>  env('mongo.hostport', 27017),

            // 数据库编码默认采用utf8
            'charset'           => env('mongo.charset', 'utf8mb4'),
            // 数据库表前缀
            'prefix'            => env('mongo.prefix', ''),
            //是否开启debug
            'debug'            => env('mongo.debug', true),
        ],

连接操作:新建模型UserTeamAwardMongo指定连接名称和数据表名,mongodb可以插入数据时自动创建文档集合。所以表名不能固定,动态生成。

<?php
namespace app\common\model;
use think\facade\{Db};
class UserTeamAwardMongo extends BaseModel
{

    protected $connection = 'mongo';//连接名称
    #protected $name = 'user_team_award';//连接数据表 

    /**
     * 动态表名每年一张表
     * 实例化的时候可以传递表名方便开发和测试
     */
    public function __construct($name = '',$cycle = 0){
        $this->name  = $name ? $name : 'user_team_award_'.date('Y');
        $this->cycle = intval($cycle ? $cycle : date('Ym',strtotime(' -1 month')));
    }

    /**
     * 集合文档名称
     *  Db::connect('mongo')->table(表全名)
     * @return mixed
     */
    public function  collection(){ 
        return Db::connect($this->connection)->name($this->name);
    }

    /**
     * 聚合操作
     * @param array $pipeline
     * @param false $bool【为真统计用,只返回一条记录;为假时返回全部记录】
     * @return int|mixed
     */
    public  function mongo($pipeline = [],$bool = false){
         $data = $this->collection()->cmd([
                    'aggregate'=>$this->name,
                    'pipeline'=>$pipeline,
                    'explain'=>false,
                ]);
         return $bool ?  (!empty($data[0]) ?$data[0]: 0) : $data;
    }

}

thinkphp6下mongodb聚合操作使用:
设计的文档主要是每月统计一次用户的团队奖励,奖励规则是月初统计上个月一整月的用户佣金,该用户的团队(无限子集包含自己)佣金,根据该团队佣金等级规则计算出团队奖励,并发放!

aggregate带where条件的sum统计求和

#原生写法   
> db.award.aggregate([ { $match: { cycle: 202108,"pid.id":508 } }, { $group : { _id : null,  total : {$sum : "$money"} } } ])
{ "_id" : null, "total" : 57185.11 }
> 
#tp框架写法
$this->model->where(['pid.id'=>508,'cycle'=>202108]) ->aggregate('sum','money');//统计求和金额#dd($this->model->getLastSql()); //打印sql 

#打印sql
db.runCommand({
"aggregate":"team_award",
"allowDiskUse":true,
"pipeline":[{
"$match":{"$and":[{"cycle":202108},{"pid.id":517}]}},
{"$group":{"_id":null,"aggregate":{"$sum":"$money"}}
}],
"cursor":{}
});

查询某个用户的历史记录

$this->model->collection()->withoutField('_id')->field('uid,pid,cycle')->where(['uid'=>508])->find();
$this->model->mongo([
    [
        '$match'=>['uid' => 508],
    ],
    [
        '$unwind'=>'$cycle',
    ],
    [
        '$project'=>[
                '_id'=>0,
                'my_money'=>'$cycle.mine_money',
                'team_money'=>'$cycle.team_money',
                'team_award'=>'$cycle.team_award',
                'team_level'=>'$cycle.team_level',
                'cycle'=>'$cycle.cycle',
                'status'=>'$cycle.status',
                'create_time'=>'$cycle.create_time',
                'update_time'=>'$cycle.update_time'
        ]
    ],
    [
        '$sort'=>['cycle'=>-1],
    ],

]);

统计金额和记录数

//聚合统计
$this->model->mongo([
    [
        '$match'=>["uid"=>508], //自己 
        #'$match'=>["pid.upid"=>508], //直推下级 
        #'$match'=>["pid:id"=>508], //团队 
               # new \MongoDB\BSON\Regex(",$uid,",'i');//团队,//where
    ],
    [
         //拆分子文档 
         '$unwind'=>'$cycle',
    ],
    [
        //子文档的字段值where匹配,当期自己佣金大于0的记录
        '$match'=>['cycle.cycle'=>202108,'cycle.mine_money'=>['$gt'=>0]],
    ],
    [
        //group分组:_id为null匹配所有记录
        //total为新起名的字段:$sum值为子文档字段表示求和该字段符合条件的记录,类似sum
        //count为新起名的字段:$sum值为1表示统计复合条件的记录总数,类似count
        '$group'=>['_id'=>null,'total'=>['$sum'=>'$cycle.mine_money'],'count'=>['$sum'=>1]],
    ],
    [
        //$project类似关系型数据库的字段过滤和起别名,字段值为0不显示为1显示;别名=>'$字段'
        '$project'=>['_id'=>0,'total'=>1,'count'=>1] //返回总金额和复合记录的总数
    ],

],1);
 



#打印sql
db.cmd({"aggregate":"team_award","pipeline":[{"$match":{"uid":508}},{"$unwind":"$cycle"},{"$project":{"_id":0,"mine_money":"$cycle.mine_money","team_money":"$cycle.team_money","team_award":"$cycle.team_award","team_level":"$cycle.team_level","cycle":"$cycle.cycle","status":"$cycle.status","create_time":"$cycle.create_time","update_time":"$cycle.update_time"}},{"$sort":{"cycle":-1}}],"explain":false});
#原生sql查询
db.team_award.aggregate( 
{'$match':{'cycle.cycle':{'$eq':202108},'cycle.team_award':{"$gt":0}}},
{'$unwind':'$cycle'},
{'$match':{'cycle.cycle':{'$eq':202108},'cycle.team_award':{"$gt":0}}},
{'$project':{"_id":'$cycle.cycle','uid':1,'team_award':'$cycle.team_award'}},
{"$sort":{'cycle':-1}}
)

统计某年所有月份发放金额和人数

$this->model->mongo([
    [
        '$match'=>['cycle.team_award'=>['$gt'=>0]],
    ],
    [
         '$unwind'=>'$cycle',
    ],
    [
        '$group'=>['_id'=>'$cycle.cycle','total'=>['$sum'=>'$cycle.team_award'],'count'=>['$sum'=>1]],
    ],
    [
        '$project'=>['_id'=>0,'cycle'=>'$_id','total'=>1,'count'=>1]
    ],
    [
        '$sort'=>['cycle'=>-1]
    ],
]);

查看统计某期发放金额和人数总数和列表

//某期符合条件的总数
$match['cycle.team_award'] = ['$gt'=>0];
$match['cycle.cycle'] = 202108;
$count = $this->model->mongo([
    [
        '$match'=>$match,
    ],
    [
         '$unwind'=>'$cycle',
    ],
    [
        '$match'=>$match,
    ],
    [
        '$group'=>['_id'=>null,'total'=>['$sum'=>'$cycle.team_award'],'count'=>['$sum'=>1]],
    ],
    [
        '$project'=>['_id'=>0,'total'=>1,'count'=>1]
    ]
],1);

//某期复合条件的列表
$this->model->mongo([
    [
        '$match'=>$match,
    ],
    [
         '$unwind'=>'$cycle',
    ],
    [
        '$match'=>$match,
    ],
    [
        '$project'=>[
            '_id'=>0,
            'uid'=>'$uid',
            'cycle'=>'$cycle.cycle',
            'mine_money'=>'$cycle.mine_money',
            'team_money'=>'$cycle.team_money',
            'team_level'=>'$cycle.team_level',
            'team_award'=>'$cycle.team_award',
            'status'=>'$cycle.status',
            'create_time'=>'$cycle.create_time'
        ]
    ],
    [
        '$sort'=>['uid'=>1]
    ],
    [
        '$skip'=>($page-1)*10,
    ],
    [
        '$limit'=>10,
    ],
]);

更新子文档数组字段

//更新子文档一条记录
$this->model->collection()
->where(['uid'=>508,'cycle.cycle'=>202108,'cycle.status'=>0])
->update(['cycle.$.status'=>1,'cycle.$.update_time'=>time()]);


//更新子文档所有记录
$this->model->collection()
->where(['uid'=>508,'cycle.cycle'=>202108,'cycle.status'=>0])
->update(['cycle.$[].status'=>1,'cycle.$[].update_time'=>time()]);

相关文章

网友评论

      本文标题:thinkphp6的mongodb聚合操作使用笔记!

      本文链接:https://www.haomeiwen.com/subject/htonyxtx.html