Mongodb o以有意义的方式“查询”任何内容,而不引入不必要的开销,这将破坏应用程序性能,并大大增加代码维护的复杂性,然后使用值而不是键来识别那些有意义的数据点,而不是以这种方式使用。您尝试了什么?请考虑阅读和编辑你的问题。你有没有机会去修复那个数据设计?您将日期作为
Mongodb o以有意义的方式“查询”任何内容,而不引入不必要的开销,这将破坏应用程序性能,并大大增加代码维护的复杂性,然后使用值而不是键来识别那些有意义的数据点,而不是以这种方式使用。您尝试了什么?请考虑阅读和编辑你的问题。你有没有机会去修复那个数据设计?您将日期作为,mongodb,aggregation-framework,Mongodb,Aggregation Framework,o以有意义的方式“查询”任何内容,而不引入不必要的开销,这将破坏应用程序性能,并大大增加代码维护的复杂性,然后使用值而不是键来识别那些有意义的数据点,而不是以这种方式使用。您尝试了什么?请考虑阅读和编辑你的问题。你有没有机会去修复那个数据设计?您将日期作为字符串作为键(lvals)。你不能轻易地对此提出质疑,更不用说agg了。您的文档将得到更好的服务,如{date:ISODate(“20121207”),value:“value1”},这样您的查询就变得简单了。不,日期是字符串格式的,只有最后才
o以有意义的方式“查询”任何内容,而不引入不必要的开销,这将破坏应用程序性能,并大大增加代码维护的复杂性,然后使用值而不是键来识别那些有意义的数据点,而不是以这种方式使用。您尝试了什么?请考虑阅读和编辑你的问题。你有没有机会去修复那个数据设计?您将日期作为字符串作为键(lvals)。你不能轻易地对此提出质疑,更不用说agg了。您的文档将得到更好的服务,如
{date:ISODate(“20121207”),value:“value1”}
,这样您的查询就变得简单了。不,日期是字符串格式的,只有最后才可以完成。您确实应该避免创建多个管道阶段,这些管道阶段可以连续执行相同的操作(例如,$project
)。您还可以使用$toDate
,它实际上可以识别dd-mm-YYYY
。实际上,您错过了问题中关于使用输入对象创建查询的部分。不完全是OP所要求的,但在多个管道上进行了一次很好的尝试!有时我使用“更大”的函数来递增地显示正在发生的事情,因为MQL的新手有时无法遵循$maps的$filters
或$maps
,等等。我想OP说他需要传递开始和结束日期,而不是从输入文档获取它。。。。?但最后:你下面的全面回答明确了一点:我们希望在设计中使用值,而不是键。解释得很好。谢谢我有一个查询,在输入列中,但COL是动态的,我只能传递开始和结束日期。你能帮我在哪里做修改吗
{
col1: {
12 - 02 - 2019: val1,
14 - 02 - 2019: val3
},
col2: {
12 - 02 - 2019: val1,
14 - 02 - 2019: val3
},
col3: {
12 - 02 - 2019: val1,
14 - 02 - 2019: val3
}
}
{
_id: ObjectId('65656222dss5ds'),
data: {
col1: {
'12-07-2012': 'value1',
'13-07-2012': 'value2',
'14-07-2012': 'value3',
'15-07-2012': 'value5'
},
col2: {
'12-07-2012': 'value1',
'13-07-2012': 'value2',
'14-07-2012': 'value3',
'15-07-2012': 'value5'
},
col3: {
'12-07-2012': 'value1',
'13-07-2012': 'value2',
'14-07-2012': 'value3',
'15-07-2012': 'value5'
}
}
}
c = db.foo.aggregate([
// Start the journey of turning lvals into rvals...
{$project: {x: {$objectToArray: "$$CURRENT.data"}}}
// ... and do it again!
,{$project: {QQ: {$map: {
input: "$x",
as: "z",
in: {
vv: {$objectToArray: "$$z.v"},
colk: "$$z.k"
}
}}
}}
// At this point we have no more lvals of interest, but we have too
// many arrays. Let's simplify and turn it into individual docs:
,{$unwind: "$QQ"}
// At this point we have a bunch of docs where QQ.colk is the collection
// key and QQ.vv is an array of (k,v) value pairs of (string date, value):
// {
// "_id" : 1,
// "QQ" : {
// "vv" : [
// {"k" : "12-07-2012", "v" : "value44"},
// {"k" : "13-07-2012", "v" : "value45"},
// {"k" : "14-07-2012", "v" : "value46"},
// {"k" : "15-07-2012", "v" : "value47"
// ],
// "colk" : "col3"
// }
// }
//
// OK. Now it is time to turn those DD-MM-YYYY strings into dates so we
// can do a proper filter. We do so by running the QQ.vv array through
// the $map function and using $dateFromParts + $substr to make a date.
// Note that we "reuse" projected field QQ (i.e. input was QQ and the
// project is QQ, sort of like saying QQ = f(QQ) ) and just keep carrying
// along colk:
,{$project: {QQ: {$map: {
input: "$QQ.vv",
as: "z",
in: {
v: "$$z.v",
d: {$dateFromParts : {
"year": {$toInt: {$substr: ["$$z.k",6,4]}},
"month": {$toInt: {$substr: ["$$z.k",3,2]}},
"day": {$toInt: {$substr: ["$$z.k",0,2]}}
}}
}
}},
colk: "$QQ.colk"
}}
// We now have filterable dates in an array associated with colk.
// Now we can filter! I hardcode the dates here but it should be clear this is
// where variables would come into play:
,{$project: {QQ: {$filter: {
input: "$QQ",
as: "zz",
cond: { $and: [
{$gt: [ "$$zz.d", new ISODate("20120713") ]},
{$lt: [ "$$zz.d", new ISODate("20120716") ]}
]}
}},
colk: "$colk"
}}
// Almost home! Now: reconstitute the collection key (colk):
,{$group: {_id: "$colk", members: {$push: "$QQ"} }}
]);
{
"_id" : "col1",
"members" : [
[
{
"v" : "value3",
"d" : ISODate("2012-07-14T00:00:00Z")
},
{
"v" : "value5",
"d" : ISODate("2012-07-15T00:00:00Z")
}
],
[
{
"v" : "value22",
"d" : ISODate("2012-07-14T00:00:00Z")
},
{
"v" : "value23",
"d" : ISODate("2012-07-15T00:00:00Z")
}
]
]
} ```
const { MongoClient } = require('mongodb');
const url = 'mongodb://localhost:27017';
const opts = { useNewUrlParser: true, useUnifiedTopology: true };
// Basic logging helper
const log = data => console.log(JSON.stringify(data, undefined, 2));
// Sample document
const data = {
data: {
col1: {
'12-07-2012': 'value1',
'13-07-2012': 'value2',
'14-07-2012': 'value3',
'15-07-2012': 'value5'
},
col2: {
'12-07-2012': 'value1',
'13-07-2012': 'value2',
'14-07-2012': 'value3',
'15-07-2012': 'value5'
},
col3: {
'12-07-2012': 'value1',
'13-07-2012': 'value2',
'14-07-2012': 'value3',
'15-07-2012': 'value5'
}
}
};
// Sample input conditions
const input = {
col1: {
'12-07-2012': 'value1', // clearly pairs of "from" and "to"
'14-07-2012': 'value3'
},
col2: {
'12-07-2012': 'value1',
'14-07-2012': 'value3'
},
col3: {
'12-07-2012': 'value1',
'14-07-2012': 'value3'
}
};
// Helper for converting strings to valid ISO dates
const toDate = dateStr => new Date(dateStr.split("-").reverse().join("-"));
// Helper for the $filter arguments for $or
const makeCond = input => Object.entries(input)
// get key and value pairs of object and make an array per 'key'
.map(([k,v]) =>
({
// Reduce the v objects as key value pairs into a single array
'$and': Object.entries(v).reduce((o, [k,v], i) =>
[
...o, // spread the reduced array
// Add and spread these new array elements
...[
// Use $gte or $lte depending on current index
{ [(i == 0) ? '$gte' : '$lte']: [ '$$this.date', toDate(k) ] },
{ [(i == 0) ? '$gte' : '$lte']: [ '$$this.value', v ] }
]
],
// The initial array for reduce
[{ '$eq': [ '$$this.col', k ] }])
})
);
const makeOrCondition = input => Object.entries(input)
.map(([col,v]) =>
({
col,
date: Object.keys(v).reduce((o,k,i) =>
({ ...o, [(i == 0) ? '$gte' : '$lte']: toDate(k) }), {}),
value: Object.values(v).reduce((o,v,i) =>
({ ...o, [(i == 0) ? '$gte': '$lte']: v }), {})
})
);
(async function() {
let client;
try {
client = await MongoClient.connect(url, opts);
let db = client.db('test');
await db.collection('example').deleteMany({});
await db.collection('example').insertOne(data);
// Debug the makeCond
//log(makeCond(input));
// Covert objects to arrays of arrays
const mapObjects = {
'$map': {
'input': { '$objectToArray': '$data' },
'in': {
'$let': {
'vars': { 'col': '$$this.k' },
'in': {
'$map': {
'input': { '$objectToArray': '$$this.v' },
'in': {
'col': '$$col',
'date': { '$toDate': '$$this.k' },
'value': '$$this.v'
}
}
}
}
}
}
};
// Flatten arrays of arrays to single array
const joinArrays = {
'$reduce': {
'input': mapObjects,
'initialValue': [],
'in': { '$concatArrays': [ '$$value', '$$this' ] }
}
};
// Apply the filter to the array elements
const filterArray = {
'$filter': {
'input': joinArrays,
'cond': { '$or': makeCond(input) }
}
};
// Basically an inline version of $group
const grouper = {
'$reduce': {
'input': filterArray,
'initialValue': [],
'in': {
'$let': {
'vars': { 'current': '$$this' },
'in': {
'$concatArrays': [
// Filter reduce output from the matching col
{ '$filter': {
'input': '$$value',
'cond': { '$ne': [ '$$current.col', '$$this.k' ] }
}},
// Conditionally join to:
{ '$cond': {
'if': {
'$ne': [
{ '$indexOfArray': [
'$$value.k', '$$this.col'
]},
-1
]
},
// Concat the inner array where matched
'then': [{
'k': '$$this.col',
'v': {
'$concatArrays': [
{ '$arrayElemAt': [
'$$value.v',
{ '$indexOfArray': ['$$value.k', '$$this.col'] }
]},
[{ 'k': '$$this.date', 'v': '$$this.value' }]
]
}
}],
// Create the inner array where not matched
'else': [{
'k': '$$this.col',
'v': [{
'k': '$$this.date',
'v': '$$this.value'
}]
}]
}}
]
}
}
}
}
};
const pipeline = [
{ '$match': {
'$expr': { '$gt': [{ '$size': filterArray }, 0] }
}},
{ '$project': {
'data': {
'$arrayToObject': {
'$map': {
'input': grouper,
'in': {
// reformat
'k': '$$this.k',
'v': {
'$arrayToObject': {
'$map': {
'input': '$$this.v',
'in': {
'k': {
'$dateToString': {
'date': '$$this.k',
'format': '%d-%m-%Y'
}
},
'v': '$$this.v'
}
}
}
}
}
}
}
}
}}
];
log(pipeline);
let result = await db.collection('example').aggregate(pipeline).toArray();
log(result);
// Create example2
await db.collection('example').aggregate([
{ '$project': { 'data': joinArrays } },
{ '$out': 'example2' }
]).toArray();
/*
* Simple $elemMatch and $filter usage when already an array
*
*/
let result2 = await db.collection('example2').aggregate([
{ '$match': {
'data': {
'$elemMatch': {
'$or': makeOrCondition(input)
}
}
}},
{ '$project': {
'data': {
'$filter': {
'input': '$data',
'cond': { '$or': makeCond(input) }
}
}
}}
]).toArray();
log(result2);
// Create example3
await db.collection('example2').aggregate([
{ '$unwind': '$data' },
{ '$replaceRoot': { 'newRoot': '$data' } },
{ '$out': 'example3' }
]).toArray();
/*
* Really simple when the elements are discreet documents
* in their own collection
*/
let result3 = await db.collection('example3').find({
'$or': makeOrCondition(input)
}).toArray();
log(result3);
} catch (e) {
console.error(e);
} finally {
if (client)
client.close();
}
})()
{
"_id": "5d6a7ac8736dce1c76d9d3e8",
"data": {
"col1": {
"12-07-2012": "value1",
"13-07-2012": "value2",
"14-07-2012": "value3"
},
"col2": {
"12-07-2012": "value1",
"13-07-2012": "value2",
"14-07-2012": "value3"
},
"col3": {
"12-07-2012": "value1",
"13-07-2012": "value2",
"14-07-2012": "value3"
}
}
}
// Covert objects to arrays of arrays
const mapObjects = {
'$map': {
'input': { '$objectToArray': '$data' },
'in': {
'$let': {
'vars': { 'col': '$$this.k' },
'in': {
'$map': {
'input': { '$objectToArray': '$$this.v' },
'in': {
'col': '$$col',
'date': { '$toDate': '$$this.k' },
'value': '$$this.v'
}
}
}
}
}
}
};
// Apply the filter to the array elements
const filterArray = {
'$filter': {
'input': joinArrays,
'cond': { '$or': makeCond(input) }
}
};