MongoDB 02 mongoimport 数据导入工具

2,435 阅读3分钟

前言

最近用到了 MongoDB 的 mongoimport 来导入数据,故来此处总结下,以便日后查看。

MongoImport

参数

  • h,--host :代表远程连接的数据库地址,默认连接本地Mongo数据库;
  • --port:代表远程连接的数据库的端口,默认连接的远程端口27017;
  • -u,--username:代表连接远程数据库的账号,如果设置数据库的认证,需要指定用户账号;
  • -p,--password:代表连接数据库的账号对应的密码;
  • -d,--db:代表连接的数据库;
  • -c,--collection:代表连接数据库中的集合;
  • -f, --fields:代表导入集合中的字段;
  • --type:代表导入的文件类型,包括csv和json,tsv文件,默认json格式;
  • --file:导入的文件名称
  • --headerline:导入csv文件时,指明第一行是列名,不需要导入。

从参数 type 可以看出分别有三种导出文件格式: JSON 、 CSV 和 TSV

准备数据

student.json
{"id": 1,"name": "小明","sex": "男","scores": [{"course": "数学","score": 80,"exam_date": "20210205"},{"course": "语文","score": 100,"exam_date": "20210205"}]}
{"id": 2,"name": "小红","sex": "女","scores": [{"course": "数学","score": 90,"exam_date": "20210205"},{"course": "语文","score": 100,"exam_date": "20210205"}]}

JSON 文件格式

mongoimport -d studentManagement -c student --file student.json  --authenticationDatabase admin -u 用户名 -p 密码

执行后,数据展示

若仔细些,会发现导入的数据存在一个问题,即 scores.exam_date 的数据类型是字符串,它本身应该是 Date 。

为了展现的更准确些,可以使用 variety.js 脚本,其命令:

mongo studentManagement --eval "var collection = 'student'" variety.js --authenticationDatabase admin -u 用户名 -p 密码

分析结果如下:

可以看到 scores.exam_date 确实是 String 。

注:本文不介绍 variety.js ,请自行查看 github.com/variety/var…
下面若还需使用 variety.js 进行验证,请自行实现。

那倘若 mongoimport 导入数据后,某属性并非你所需要的数据类型,该如何呢?修改就好,哈哈~~

修改数据类型的语句如下:

db.student.find().forEach(function (doc) {
    for(var i=0; i < doc.scores.length; i++){
        doc.scores[i].exam_date = new ISODate(doc.scores[i].exam_date);
    }
    db.student.save(doc);
});

最终效果:

当然用 mongoexport 导出的JSON文件,再用 mongoimport 导入是不会存在这样的问题的!

CSV 文件格式

在讲 mongoexport 的时候,我们就数组类型做了特殊处理,即先拆分后导出。
那么 mongoimport 肯定就是反其道而行之了,即先导入再合并。

数据准备
student.csv
id,name,sex,course,score,exam_date
1,小明,男,数学,80,2021-02-05
1,小明,男,语文,100,2021-02-05
2,小红,女,数学,90,2021-02-05
2,小红,女,语文,100,2021-02-05
先将数据导入到 student_import 中
mongoimport -d studentManagement -c student_import --type=csv --headerline --file student.csv  --authenticationDatabase admin -u 用户名 -p 密码

执行后,查看数据:

exam_date 数据类型依旧是 String ,仍需先转换为 Date 。

db.student_import.find().forEach(function (doc) {
    doc.exam_date = new ISODate(doc.exam_date);
    db.student_import.save(doc);
});

执行后:

将数据按需合并属性到数组中

这里我们需要合并的是: course,score,exam_date 三个字段,合并为 scores 。

db.student_import.aggregate([
    // 先将 course,score,exam_date 分别赋值为 score 的属性
    {$project: {"id": "$id", "name": "$name", "sex": "$sex", 
        "score.course": "$course", "score.score": "$score", "score.exam_date": "$exam_date"}},
    // 再按照 id,name,sex 分组,将各组的 score 放入 scores 中
    { "$group": {"_id": { "id": "$id", "name": "$name", "sex": "$sex" }, 
        "scores": { "$push": "$score" }}},
    // 检索所需字段,并按所需格式显示
    {$project: {"id": "$_id.id", "name": "$_id.name", "sex": "$_id.sex","scores": "$scores", "_id":0}},
    // 将数据输入到 student 集合
    {$out:"student"}
]);

这里有个问题就是 $out 会直接覆盖 student 而不是插入。

事先在 student 中插入一条简单的数据:

db.student.insert({id: NumberInt(100),name: "小白"});

再执行 合并 操作,效果如下:

正如你所见,“小白”那条数据没了。

此时,我们只能先将合并的数据存入中间集合 student_tmp ,然后再将其插入 student 。

1)存入中间集合

db.student_import.aggregate([
    // 先将 course,score,exam_date 分别赋值为 score 的属性
    {$project: {"id": "$id", "name": "$name", "sex": "$sex", 
        "score.course": "$course", "score.score": "$score", "score.exam_date": "$exam_date"}},
    // 再按照 id,name,sex 分组,将各组的 score 放入 scores 中
    { "$group": {"_id": { "id": "$id", "name": "$name", "sex": "$sex" }, 
        "scores": { "$push": "$score" }}},
    // 检索所需字段,并按所需格式显示
    {$project: {"id": "$_id.id", "name": "$_id.name", "sex": "$_id.sex","scores": "$scores", "_id":0}},
    // 将数据输入到 student 集合
    {$out:"student_tmp"}
]);

2)插入 student

db.student_tmp.find().forEach(function (doc){
    db.student.insert(doc);
});

最后,记得删除 student_import 和 student_tmp

db.student_import.drop()
db.student_tmp.drop()

个人感觉,虽然直接用 mongoimport 和 mongo 能完成导入数据的工作,但是较为麻烦,不如用 python 或 kettle 处理数据并导入数据。

建议

  • 当处理的数据相对扁平时,使用 MongoDB 的导入导出工具。
  • 当需要将富文档导出到 CSV 或者从 CSV 导入一个富 MongoDB 文档,也许构建一个自定义工具会更方便。