node.js에서 mongoose로 mongodb update, delete, join, group 정리한 글
문서 갱신 문서를 업데이트 하는 방법이 몇 개 잇다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 router.get('/update1' , (req, res ) => { userModel.findOne({ userName : '날강두' }, (err, User ) => { if (err) { res.json(err); } else if (User.length == 0 ) { res.json('데이터가 없습니다.' ); } else { User.position = '먹튀' ; User.save((err, UpdateUser ) => { if (err) { req.json(err); } else { res.json(UpdateUser); } }); } }); });
findOne에서 문서를 조회 후 콜백 받은 데이터에 바꿀 꺼 넣고 세이브 하면 갱신 된다.
1 2 3 4 5 6 7 8 9 10 { "createAt" : "2019-08-08T15:37:45.028Z" , "_id" : "5d4c41cc2a822207ecb315e8" , "userName" : "크리스티아누 날강두" , "userNum" : 7 , "position" : "먹튀" , "userMail" : "날강두@유벤투스.com" , "team" : "5d4c412caf60c707c225565d" , "__v" : 0 }
이전엔 update 하나 엿는데 updateOne, updateMany로 바뀌엇다 -_-
뭔가 sql의 update set … 느낌 나는 방법으로도 갱신 할 수 잇다.
1 2 3 4 5 6 7 8 9 10 11 12 13 router.get('/update2' , (req, res ) => { userModel.updateOne( { userName : '크리스티아누 날강두' }, { $set : { userMail : '먹튀@유벤투스.com' } }, (err) => { if (err) { res.json(err); } else { res.json('update success' ); } }); });
updateOne은 뭐 조건에 맞는 데이터 중 첫 번째 데이터만 갱신 한다는 것.
그리고 이거는 따로 갱신된 데이터를 콜백으로 못 받는다.
여러 문서를 갱신할려면 updateMany를 쓰면 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 / @TODO: 통상적인 update router.get('/update3' , (req, res ) => { userModel.updateMany( { position : '공격수' }, { $set : { userNum : 999 } }, (err) => { if (err) { res.json(err); } else { res.json('update success' ); } }); });
마찬가지로 콜백 받는 데이터가 업음.
어휴 많다. 마지막으로 업데이트 하는 거 하나 더 적음
1 2 3 4 5 6 7 8 9 10 11 12 13 router.get('/update4' , (req, res ) => { userModel.findOneAndUpdate( { userName : '크리스티아누 날강두' }, { $set : { userMail : '예전우리형@유벤투스.com' } }, { new : true }, (err, data) => { if (err) { res.json(err); } else { res.json(data); } }); });
조건에 맞는 데이터 중에 첫 번째 데이터를 가져와서 갱신을 함. 나름 편리하다.
결과도 콜백으로 받아 볼수도 잇다.
1 2 3 4 5 6 7 8 9 10 { "createAt" : "2019-08-08T15:37:45.028Z" , "_id" : "5d4c41cc2a822207ecb315e8" , "userName" : "크리스티아누 날강두" , "userNum" : 7 , "position" : "먹튀" , "userMail" : "예전우리형@유벤투스.com" , "team" : "5d4c412caf60c707c225565d" , "__v" : 0 }
근데 예전 버전에선 안 떳는데 아마 5.X부터 생겻나봄. deprecated 어쩌구가 뜬다.
해결 방법은 mongoose connect에서 하나 추가 해주면 안 뜸.
1 2 3 4 const mongodb = mongoose.connect('mongodb://localhost:27017/localtest' , { useNewUrlParser: true , useFindAndModify: false });
delete 이전엔 remove로 했는데 문서 보니 얘도 deleteOne, deleteMany가 생김ㅋ 컨셉인가?!
암튼 해보자.
1 2 3 4 5 6 7 8 9 10 router.get('/deleteOne' , (req, res ) => { userModel.deleteOne({ userName : '크리스티아누 날강두' }, (err ) => { if (err) { res.json(err); } else { res.json('삭제됨.' ); } }); });
뭐 조건에 맞는 첫 번째 데이터 삭제임. 로컬에 잇는 날강두 문서가 삭제되엇다.
deleteMany는 뭐 여러 개 삭제하는거겟지..
1 2 3 4 5 6 7 8 9 10 router.get('/deleteMany' , (req, res ) => { userModel.deleteMany({}, (err ) => { if (err) { res.json(err); } else { res.json('삭제됨.' ); } }); });
조건 빼고 지워봣더니 다 지워지네. ㅇㅋ
join mongodb는 nosql이라 일단 관계를 지양 하는데 그래도 필요? 할 때 쓰라고 만든거 같음.
populate랑 lookup 으로 이전에 조인을 햇엇는데 지금도 잘 되는지 확인 해보자.
조인을 하기 위해 팀 데이터를 저장할 컬렉션을 하나 미리 만드럿엇음.
1 2 3 4 5 6 7 8 9 10 11 12 13 'use strict' ;const mongoose = require ('mongoose' );const Schema = mongoose.Schema;const teamSchema = new Schema({ teamName: { type: String , required: true } }); module .exports = mongoose.model('teams' , teamSchema);
근데 조인을 할려면 뭔가 데이터가 잇어야 하니 미리 더미 데이터를 만들어 두엇고
저번에 유저 도큐먼트를 만들 때 위의 생성 값으로 넣어준거임.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 'use strict' const mongoose = require ('mongoose' );const Schema = mongoose.Schema;const userSchema = new Schema({ userName: { type: String , required: [true , '아이디는 필수입니다.' ] }, userNum: { type: Number , required: [true , '등번호는 필수입니다.' ] }, position: String , userMail: String , team: { type: Schema.Types.ObjectId, ref: 'teams' }, createAt: { type: Date , default : Date .now() } }); module .exports = mongoose.model('users' , userSchema);
저번 글에 적은 유저 스키마에 team이라는 프로퍼티가 rdb에서 fk 설정 하듯이
그런 느낌인데 type은 mongo의 고유 ID, 참조 테이블은 팀 테이블이다.
즉 teams 컬렉션의 고유 아디를 fk로.
후 이제 populate를 이용해 join이 된 결과를 보자.
1 2 3 4 5 6 7 8 9 router.get('/populate' , (req, res ) => { userModel.find({}).populate('team' ).exec((err, data ) => { if (err) { res.json(err); } else { res.json(data); } }); });
결과는 이러 하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 [ { "createAt" : "2019-08-08T15:42:53.213Z" , "_id" : "5d4c4428ebc6bc081f7a83c1" , "userName" : "크리스티아누 날강두" , "userNum" : 7 , "position" : "공격수" , "userMail" : "날강두@유벤투스.com" , "team" : { "_id" : "5d4c412caf60c707c225565d" , "teamName" : "유벤투스" , "__v" : 0 }, "__v" : 0 }, { "createAt" : "2019-08-08T15:42:53.213Z" , "_id" : "5d4c442debc6bc081f7a83c2" , "userName" : "모하메드 살라" , "userNum" : 11 , "position" : "공격수" , "team" : { "_id" : "5d4c412caf60c707c225565e" , "teamName" : "리버풀" , "__v" : 0 }, "userMail" : "이집트메시@리버풀.com" , "__v" : 0 }, { "createAt" : "2019-08-08T15:42:53.213Z" , "_id" : "5d4c442debc6bc081f7a83c3" , "userName" : "피에르에메릭 오바메양" , "userNum" : 17 , "position" : "공격수" , "team" : { "_id" : "5d4c412caf60c707c225565f" , "teamName" : "아스날" , "__v" : 0 }, "userMail" : "득점왕@아스날.com" , "__v" : 0 }, { "createAt" : "2019-08-08T15:42:53.213Z" , "_id" : "5d4c442debc6bc081f7a83c4" , "userName" : "메수트 외질" , "userNum" : 11 , "position" : "미드필더" , "team" : { "_id" : "5d4c412caf60c707c225565f" , "teamName" : "아스날" , "__v" : 0 }, "userMail" : "노랑머리@아스날.com" , "__v" : 0 } ]
선수 밑에 팀이 다 종속으로 들어가 있다.
암튼 populate를 쓰려면 미리 스키마에서 join 하기 전에 타입을 정해놔야 하고,
타입을 objectId 외에는 사용할 수가 없다.
다른 방법으로 좀 전의 조인을 반대로 해보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 router.get('/lookup' , (req, res ) => { teamModel.aggregate([ { "$sort" : { "_id" : 1 } }, { "$lookup" : { "localField" : "_id" , "from" : "users" , "foreignField" : "team" , "as" : "users" } } ]).exec((err, data ) => { if (err) { res.json(err); } else { res.json(data); } }); });
결과는 이러하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 [ { "_id" : "5d4c412caf60c707c225565d" , "teamName" : "유벤투스" , "__v" : 0 , "users" : [ { "_id" : "5d4c4428ebc6bc081f7a83c1" , "createAt" : "2019-08-08T15:42:53.213Z" , "userName" : "크리스티아누 날강두" , "userNum" : 7 , "position" : "공격수" , "userMail" : "날강두@유벤투스.com" , "team" : "5d4c412caf60c707c225565d" , "__v" : 0 } ] }, { "_id" : "5d4c412caf60c707c225565e" , "teamName" : "리버풀" , "__v" : 0 , "users" : [ { "_id" : "5d4c442debc6bc081f7a83c2" , "createAt" : "2019-08-08T15:42:53.213Z" , "userName" : "모하메드 살라" , "userNum" : 11 , "position" : "공격수" , "team" : "5d4c412caf60c707c225565e" , "userMail" : "이집트메시@리버풀.com" , "__v" : 0 } ] }, { "_id" : "5d4c412caf60c707c225565f" , "teamName" : "아스날" , "__v" : 0 , "users" : [ { "_id" : "5d4c442debc6bc081f7a83c3" , "createAt" : "2019-08-08T15:42:53.213Z" , "userName" : "피에르에메릭 오바메양" , "userNum" : 17 , "position" : "공격수" , "team" : "5d4c412caf60c707c225565f" , "userMail" : "득점왕@아스날.com" , "__v" : 0 }, { "_id" : "5d4c442debc6bc081f7a83c4" , "createAt" : "2019-08-08T15:42:53.213Z" , "userName" : "메수트 외질" , "userNum" : 11 , "position" : "미드필더" , "team" : "5d4c412caf60c707c225565f" , "userMail" : "노랑머리@아스날.com" , "__v" : 0 } ] } ]
lookup이 원래 관계를 맺으라고 나온 기능이 아니고 집계 관련으로 알고 잇는데
뭐 암튼 관계는 왠만 하면 쓰지 말자.
아 추가로 위에 $lookup 밑에
추가하면 아스날 처럼 배열에 잇던 애들의 결과가 분리된다. 뭐 쓸 일이 언젠가 잇을래나?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 [ { "_id" : "5d4c412caf60c707c225565d" , "teamName" : "유벤투스" , "__v" : 0 , "users" : { "_id" : "5d4c4428ebc6bc081f7a83c1" , "createAt" : "2019-08-08T15:42:53.213Z" , "userName" : "크리스티아누 날강두" , "userNum" : 7 , "position" : "공격수" , "userMail" : "날강두@유벤투스.com" , "team" : "5d4c412caf60c707c225565d" , "__v" : 0 } }, { "_id" : "5d4c412caf60c707c225565e" , "teamName" : "리버풀" , "__v" : 0 , "users" : { "_id" : "5d4c442debc6bc081f7a83c2" , "createAt" : "2019-08-08T15:42:53.213Z" , "userName" : "모하메드 살라" , "userNum" : 11 , "position" : "공격수" , "team" : "5d4c412caf60c707c225565e" , "userMail" : "이집트메시@리버풀.com" , "__v" : 0 } }, { "_id" : "5d4c412caf60c707c225565f" , "teamName" : "아스날" , "__v" : 0 , "users" : { "_id" : "5d4c442debc6bc081f7a83c3" , "createAt" : "2019-08-08T15:42:53.213Z" , "userName" : "피에르에메릭 오바메양" , "userNum" : 17 , "position" : "공격수" , "team" : "5d4c412caf60c707c225565f" , "userMail" : "득점왕@아스날.com" , "__v" : 0 } }, { "_id" : "5d4c412caf60c707c225565f" , "teamName" : "아스날" , "__v" : 0 , "users" : { "_id" : "5d4c442debc6bc081f7a83c4" , "createAt" : "2019-08-08T15:42:53.213Z" , "userName" : "메수트 외질" , "userNum" : 11 , "position" : "미드필더" , "team" : "5d4c412caf60c707c225565f" , "userMail" : "노랑머리@아스날.com" , "__v" : 0 } } ]
그룹 aggregate에 내장되어 있는 메소드 중에 group을 사용하면 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 router.get('/group' , (req, res ) => { userModel.aggregate([ { "$group" : { "_id" : { "position" : "$position" }, "count" : { "$sum" : 1 } } } ]).exec((err, data ) => { if (err) { res.json(err); } else { res.json(data); } }); });
결과는 이러하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 [ { "_id" : { "position" : "미드필더" }, "count" : 1 }, { "_id" : { "position" : "공격수" }, "count" : 3 } ]
여기서의 _id는 $group이 사용하는 고정된 형식. 바꾸면 에러 난다..
이 밖에도 aggregate는 다양한 함수들이 있으니 차차 필요 할 때마다
번역기를 돌려서 문서를 봐야겠다..
휴 끝.
참고