前言
最近用到了 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 文档,也许构建一个自定义工具会更方便。