Mongoose에 입력 후 쿼리 중
저는 Mongoose와 MongoDB에 대해 전반적으로 잘 모르기 때문에 다음과 같은 일이 가능한지 알아보는데 어려움을 겪고 있습니다.
Item = new Schema({
id: Schema.ObjectId,
dateCreated: { type: Date, default: Date.now },
title: { type: String, default: 'No Title' },
description: { type: String, default: 'No Description' },
tags: [ { type: Schema.ObjectId, ref: 'ItemTag' }]
});
ItemTag = new Schema({
id: Schema.ObjectId,
tagId: { type: Schema.ObjectId, ref: 'Tag' },
tagName: { type: String }
});
var query = Models.Item.find({});
query
.desc('dateCreated')
.populate('tags')
.where('tags.tagName').in(['funny', 'politics'])
.run(function(err, docs){
// docs is always empty
});
더 좋은 방법이 있을까요?
편집
혼란을 드려 죄송합니다.제가 하려는 것은 웃긴 태그나 정치 태그가 있는 모든 아이템을 얻는 것입니다.
편집
where 절이 없는 문서:
[{
_id: 4fe90264e5caa33f04000012,
dislikes: 0,
likes: 0,
source: '/uploads/loldog.jpg',
comments: [],
tags: [{
itemId: 4fe90264e5caa33f04000012,
tagName: 'movies',
tagId: 4fe64219007e20e644000007,
_id: 4fe90270e5caa33f04000015,
dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
rating: 0,
dislikes: 0,
likes: 0
},
{
itemId: 4fe90264e5caa33f04000012,
tagName: 'funny',
tagId: 4fe64219007e20e644000002,
_id: 4fe90270e5caa33f04000017,
dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
rating: 0,
dislikes: 0,
likes: 0
}],
viewCount: 0,
rating: 0,
type: 'image',
description: null,
title: 'dogggg',
dateCreated: Tue, 26 Jun 2012 00:29:24 GMT
}, ... ]
where 절을 사용하면 빈 배열이 나타납니다.
3.2 이상의 최신 MongoDB를 사용하면 다음 기능을 대신 사용할 수 있습니다..populate()
대부분의 경우. 「의 조인을 하는 것과 달리, 「서버상에서」의 조인을 실시할 수 ..populate()
는 실제로 조인을 "조인"하기 위한 "조인 쿼리"입니다.
★★★★★★★★★★★★★★★★★..populate()
는 관계형 데이터베이스라는 의미에서 실제로는 '실제'가 아닙니다.한편 연산자는 실제로 서버에서 작업을 수행하며 "LEFT JOIN"과 거의 비슷합니다.
Item.aggregate(
[
{ "$lookup": {
"from": ItemTags.collection.name,
"localField": "tags",
"foreignField": "_id",
"as": "tags"
}},
{ "$unwind": "$tags" },
{ "$match": { "tags.tagName": { "$in": [ "funny", "politics" ] } } },
{ "$group": {
"_id": "$_id",
"dateCreated": { "$first": "$dateCreated" },
"title": { "$first": "$title" },
"description": { "$first": "$description" },
"tags": { "$push": "$tags" }
}}
],
function(err, result) {
// "tags" is now filtered by condition and "joined"
}
)
N.B. The.
.collection.name
이 경우 모델에 할당된 MongoDB 컬렉션의 실제 이름인 "문자열"을 실제로 평가합니다.mongoose 는 디폴트로 컬렉션명을 「복수화」해, 실제의 MongoDB 컬렉션명이 인수로 필요하기 때문에(서버 조작이기 때문에), 이것은 컬렉션명을 직접 「하드 코딩」하는 것과는 반대로, mongoose 코드에서 사용하는 편리한 트릭입니다.
어레이에서 불필요한 아이템을 제거하기 위해 를 사용할 수도 있지만, 실제로는 이 폼이 가장 효율적입니다.이 폼은 어그리게이션 파이프라인 최적화에 따라 및 조건이 모두 해당됩니다.
이를 통해 실제로는 3개의 파이프라인 단계가 하나로 롤업됩니다.
{ "$lookup" : {
"from" : "itemtags",
"as" : "tags",
"localField" : "tags",
"foreignField" : "_id",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"tagName" : {
"$in" : [
"funny",
"politics"
]
}
}
}}
이는 실제 작업이 "먼저 참여하도록 컬렉션을 필터링"한 다음 결과를 반환하고 어레이를 "연결 해제"하기 때문에 매우 적합합니다.두 방법 모두 BSON 제한인 16MB를 넘지 않도록 하기 위해 사용됩니다.이것은 클라이언트에 없는 제약입니다.
유일한 문제는 특히 결과를 배열로 만들고 싶을 때 어떤 면에서 "직관과는 반대"로 보인다는 것입니다. 그러나 이것이 바로 원본 문서 형식으로 재구성하기 때문입니다.
또, 현시점에서는, 서버가 사용하는 것과 같은 최종적인 구문을 실제로 쓸 수 없는 것도 유감입니다.IMHO, 이건 고쳐야 할 실수입니다.그러나 현재로서는 단순히 시퀀스를 사용하는 것이 효과적이며 최고의 성능과 확장성을 갖춘 가장 실행 가능한 옵션입니다.
부록 - MongoDB 3.6 이후
여기에 나타낸 패턴은 다른 스테이지가 어떻게 롤링되는지에 따라 상당히 최적화되어 있습니다만, 일반적으로 양쪽의 동작에 고유한 "LEFT JOIN"이1개 실패하는 것은 있습니다.populate()
여기서 빈 어레이를 보존하지 않는 「최적의」사용에 의해서 무효가 됩니다.를 추가할 수 있습니다.preserveNullAndEmptyArrays
그러나 이는 위에서 설명한 "최적화된" 시퀀스를 부정하고 기본적으로 최적화에 일반적으로 결합되는 세 단계를 모두 그대로 유지합니다.
MongoDB 3.6은 "서브 파이프라인" 표현을 허용하는 "표현력 높은" 형태로 확장됩니다.따라서 "LEFT JOIN"을 유지한다는 목표를 충족할 뿐만 아니라 최적의 쿼리를 통해 반환되는 결과를 줄일 수 있으며 구문을 훨씬 단순화할 수 있습니다.
Item.aggregate([
{ "$lookup": {
"from": ItemTags.collection.name,
"let": { "tags": "$tags" },
"pipeline": [
{ "$match": {
"tags": { "$in": [ "politics", "funny" ] },
"$expr": { "$in": [ "$_id", "$$tags" ] }
}}
]
}}
])
선언된 "local" 값과 "foreign" 값을 일치시키기 위해 사용되는 것은 실제로 MongoDB가 원래 구문을 사용하여 "internal" 작업을 수행합니다.이 형태로 표현함으로써 "서브 파이프라인" 내의 초기 표현을 직접 맞춤화할 수 있습니다.
실제로 진정한 "집약 파이프라인"으로서 이 "서브 파이프라인" 표현에서 집약 파이프라인으로 수행할 수 있는 작업은 거의 모두 수행할 수 있습니다. 여기에는 의 수준을 다른 관련 컬렉션으로 "내스트"하는 것도 포함됩니다.
추가 사용법은 여기서 질문의 범위를 조금 벗어나지만, "내포 인구"에 대해서도 의 새로운 사용 패턴은 거의 동일하며 "로트"가 전체 사용에서 더 강력합니다.
작업 예시
다음은 모델에서 정적 방법을 사용하는 예를 보여 줍니다.이 스태틱 메서드가 실장되면 콜은 다음과 같이 됩니다.
Item.lookup(
{
path: 'tags',
query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
},
callback
)
또는 조금 더 현대적이 되도록 개선하면 다음과 같이 됩니다.
let results = await Item.lookup({
path: 'tags',
query: { 'tagName' : { '$in': [ 'funny', 'politics' ] } }
})
유사하다..populate()
실제로는 서버에서 결합을 하고 있습니다.완전성을 위해 여기서 사용하는 경우 반환된 데이터는 부모 및 자식 사례에 따라 mongoose 문서 인스턴스에 다시 캐스트됩니다.
이것은 매우 사소하고 적응하기 쉬우며 대부분의 경우 그대로 사용합니다.
N.B 여기서 비동기 사용은 동봉된 예를 간단히 실행하기 위한 것입니다.실제 실장은 이 의존성이 없습니다.
const async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.connect('mongodb://localhost/looktest');
const itemTagSchema = new Schema({
tagName: String
});
const itemSchema = new Schema({
dateCreated: { type: Date, default: Date.now },
title: String,
description: String,
tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
});
itemSchema.statics.lookup = function(opt,callback) {
let rel =
mongoose.model(this.schema.path(opt.path).caster.options.ref);
let group = { "$group": { } };
this.schema.eachPath(p =>
group.$group[p] = (p === "_id") ? "$_id" :
(p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });
let pipeline = [
{ "$lookup": {
"from": rel.collection.name,
"as": opt.path,
"localField": opt.path,
"foreignField": "_id"
}},
{ "$unwind": `$${opt.path}` },
{ "$match": opt.query },
group
];
this.aggregate(pipeline,(err,result) => {
if (err) callback(err);
result = result.map(m => {
m[opt.path] = m[opt.path].map(r => rel(r));
return this(m);
});
callback(err,result);
});
}
const Item = mongoose.model('Item', itemSchema);
const ItemTag = mongoose.model('ItemTag', itemTagSchema);
function log(body) {
console.log(JSON.stringify(body, undefined, 2))
}
async.series(
[
// Clean data
(callback) => async.each(mongoose.models,(model,callback) =>
model.remove({},callback),callback),
// Create tags and items
(callback) =>
async.waterfall(
[
(callback) =>
ItemTag.create([{ "tagName": "movies" }, { "tagName": "funny" }],
callback),
(tags, callback) =>
Item.create({ "title": "Something","description": "An item",
"tags": tags },callback)
],
callback
),
// Query with our static
(callback) =>
Item.lookup(
{
path: 'tags',
query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
},
callback
)
],
(err,results) => {
if (err) throw err;
let result = results.pop();
log(result);
mongoose.disconnect();
}
)
또는 Node 8.x 이후 버전에서는 좀 더 최신 버전입니다.async/await
추가 의존관계 없음:
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/looktest';
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
const itemTagSchema = new Schema({
tagName: String
});
const itemSchema = new Schema({
dateCreated: { type: Date, default: Date.now },
title: String,
description: String,
tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
});
itemSchema.statics.lookup = function(opt) {
let rel =
mongoose.model(this.schema.path(opt.path).caster.options.ref);
let group = { "$group": { } };
this.schema.eachPath(p =>
group.$group[p] = (p === "_id") ? "$_id" :
(p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });
let pipeline = [
{ "$lookup": {
"from": rel.collection.name,
"as": opt.path,
"localField": opt.path,
"foreignField": "_id"
}},
{ "$unwind": `$${opt.path}` },
{ "$match": opt.query },
group
];
return this.aggregate(pipeline).exec().then(r => r.map(m =>
this({ ...m, [opt.path]: m[opt.path].map(r => rel(r)) })
));
}
const Item = mongoose.model('Item', itemSchema);
const ItemTag = mongoose.model('ItemTag', itemTagSchema);
const log = body => console.log(JSON.stringify(body, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
// Clean data
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
// Create tags and items
const tags = await ItemTag.create(
["movies", "funny"].map(tagName =>({ tagName }))
);
const item = await Item.create({
"title": "Something",
"description": "An item",
tags
});
// Query with our static
const result = (await Item.lookup({
path: 'tags',
query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
})).pop();
log(result);
mongoose.disconnect();
} catch (e) {
console.error(e);
} finally {
process.exit()
}
})()
또한 MongoDB 3.6 이상에서는 및 빌딩이 없어도 다음과 같습니다.
const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/looktest';
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
const itemTagSchema = new Schema({
tagName: String
});
const itemSchema = new Schema({
title: String,
description: String,
tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
},{ timestamps: true });
itemSchema.statics.lookup = function({ path, query }) {
let rel =
mongoose.model(this.schema.path(path).caster.options.ref);
// MongoDB 3.6 and up $lookup with sub-pipeline
let pipeline = [
{ "$lookup": {
"from": rel.collection.name,
"as": path,
"let": { [path]: `$${path}` },
"pipeline": [
{ "$match": {
...query,
"$expr": { "$in": [ "$_id", `$$${path}` ] }
}}
]
}}
];
return this.aggregate(pipeline).exec().then(r => r.map(m =>
this({ ...m, [path]: m[path].map(r => rel(r)) })
));
};
const Item = mongoose.model('Item', itemSchema);
const ItemTag = mongoose.model('ItemTag', itemTagSchema);
const log = body => console.log(JSON.stringify(body, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
// Clean data
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
// Create tags and items
const tags = await ItemTag.insertMany(
["movies", "funny"].map(tagName => ({ tagName }))
);
const item = await Item.create({
"title": "Something",
"description": "An item",
tags
});
// Query with our static
let result = (await Item.lookup({
path: 'tags',
query: { 'tagName': { '$in': [ 'funny', 'politics' ] } }
})).pop();
log(result);
await mongoose.disconnect();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
직접 지원되는 것은 아니지만 쿼리가 반환된 후 다른 필터 단계를 추가하여 요청할 수 있습니다.
첫번째,.populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } )
태그 문서를 필터링하려면 이 작업을 수행해야 합니다.그런 다음 쿼리가 반환된 후 해당 문서가 없는 문서를 수동으로 필터링해야 합니다.tags
입력 기준에 일치하는 문서입니다.예를 들어 다음과 같습니다.
query....
.exec(function(err, docs){
docs = docs.filter(function(doc){
return doc.tags.length;
})
// do stuff with docs
});
교환을 시도하다
.populate('tags').where('tags.tagName').in(['funny', 'politics'])
타고
.populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } )
갱신: 코멘트를 봐 주세요.이 답변은 질문에 올바르게 대응하지 않습니다.다만, 유저의 다른 질문에 대한 답변이 될 가능성이 있기 때문에, 이 「답변」은 삭제하지 않습니다.
첫 번째: 이 질문이 정말 시대에 뒤떨어진 질문이라는 것을 알지만, 저는 이 문제를 정확히 검색했고, 이 SO 게시물은 구글 엔트리 #1이었습니다.그 때문에,docs.filter
mongoose v4.6.0 문서에서 읽은 바와 같이 다음과 같이 간단하게 사용할 수 있습니다.
Item.find({}).populate({
path: 'tags',
match: { tagName: { $in: ['funny', 'politics'] }}
}).exec((err, items) => {
console.log(items.tags)
// contains only tags where tagName is 'funny' or 'politics'
})
이것이 향후 검색 기계 사용자에게 도움이 되기를 바랍니다.
최근 저도 같은 문제를 겪고 나서 다음과 같은 해결책을 생각해 냈습니다.
먼저 tagName이 'funny' 또는 'politics'인 모든 ItemTags를 찾아 ItemTag _ids 배열을 반환합니다.
그런 다음 태그 배열에서 모든 ItemTag _ids가 포함된 항목을 찾습니다.
ItemTag
.find({ tagName : { $in : ['funny','politics'] } })
.lean()
.distinct('_id')
.exec((err, itemTagIds) => {
if (err) { console.error(err); }
Item.find({ tag: { $all: itemTagIds} }, (err, items) => {
console.log(items); // Items filtered by tagName
});
});
@aaronheckmann의 답변은 나에게 효과가 있었지만 나는 대체해야만 했다.return doc.tags.length;
로.return doc.tags != null;
이 필드는 pulit 내에 기재되어 있는 조건과 일치하지 않는 경우 null을 포함하기 때문입니다.마지막 코드는 다음과 같습니다.
query....
.exec(function(err, docs){
docs = docs.filter(function(doc){
return doc.tags != null;
})
// do stuff with docs
});
언급URL : https://stackoverflow.com/questions/11303294/querying-after-populate-in-mongoose
'programing' 카테고리의 다른 글
프로젝터로 약속을 만들고 조작하는 방법 (0) | 2023.03.26 |
---|---|
Postgre 함수의 반환에서 큰따옴표를 제거합니다.SQL (0) | 2023.03.26 |
xml DOMDocument를 문자열로 변환 (0) | 2023.03.26 |
Elastic Search에서 부분 일치를 수행하려면 어떻게 해야 합니까? (0) | 2023.03.26 |
ui 라우터 - 공유 컨트롤러가 포함된 중첩된 보기 (0) | 2023.03.26 |