How to search a column in a collection in mongodb with $in which includes an array of elements for search and also caseInsensitive matching of those elements in the column ?
Use $in with the match being case insensitive:
Data example:
{
name : "...Event A",
fieldX : "aAa"
},
{
name : "...Event B",
fieldX : "Bab"
},
{
name : "...Event C",
fieldX : "ccC"
},
{
name : "...Event D",
fieldX : "dDd"
}
And we want documents were "fieldX" is contained in any value of the array (optValues):
var optValues = ['aaa', 'bbb', 'ccc', 'ddd'];
var optRegexp = [];
optValues.forEach(function(opt){
optRegexp.push( new RegExp(opt, "i") );
});
db.collection.find( { fieldX: { $in: optRegexp } } );
This works for $all either.
I hope this helps!
p.s.: This was my solution to search by tags in a web application.
You can use $elemMatch with regular expressions search, e.g. let's search for "blue" color in the following collection:
db.items.save({
name : 'a toy',
colors : ['red', 'BLUE']
})
> ok
db.items.find({
'colors': {
$elemMatch: {
$regex: 'blue',
$options: 'i'
}
}
})
>[
{
"name": "someitem",
"_id": { "$oid": "4fbb7809cc93742e0d073aef"},
"colors": ["red", "BLUE"]
}
]
This works for me perfectly.
From code we can create custom query like this:
{
"first_name":{
"$in":[
{"$regex":"^serina$","$options":"i"},
{"$regex":"^andreW$","$options":"i"}
]
}
}
This will transform to following in mongo after query:
db.mycollection.find({"first_name":{"$in":[/^serina$/i, /^andreW$/i]}})
Same for "$nin".
This is pretty simple
const sampleData = [
RegExp("^" + 'girl' + "$", 'i'),
RegExp("^" + 'boy' + "$", 'i')
];
const filerObj = { gender : {$in : sampleData}};
The way to do it in Java is:
List<String> nameList = Arrays.asList(name.split(PATTERN));
List<Pattern> regexList = new ArrayList<>();
for(String name: nameList) {
regexList.add(Pattern.compile(name , Pattern.CASE_INSENSITIVE));
}
criteria.where("Reference_Path").in(regexList);
Here is my case insensitive search (query) with multiple condition (regex) from data of array, I've used $in but it doesn't support case insensitive search.
Example Data
{
name : "...Event A",
tags : ["tag1", "tag2", "tag3", "tag4]
},
{
name : "...Event B",
tags : ["tag3", "tag2"]
},
{
name : "...Event C",
tags : ["tag1", "tag4"]
},
{
name : "...Event D",
tags : ["tag2", "tag4"]
}
My query
db.event.find(
{ $or: //use $and or $or depends on your needs
[
{ tags : {
$elemMatch : { $regex : '^tag1$', $options : 'i' }
}
},
{ tags : {
$elemMatch : { $regex : '^tag3$', $options : 'i' }
}
}
]
})
Related
I'm trying to clean all document from a special chars, but something not working as well.
My field descrizione contains some special chars. In this case ^ and -:
^CT 6 STP ANTIPERDITE OLIO MOT BENZ-DIE
db.Collection.find({descrizione: /[$&+,:;=?##|<>.^*()%!-]/g}).forEach(function(doc) {
doc.descrizione = doc.descrizione.replace(/[$&+,:;=?##|<>.^*()%!-]/g,"");
db.Collection.update({ "_id": doc._id },{ "$set": { "descrizione": doc.descrizione } });
});
but something not working as well
This is my document: (Mongo Version 3.4.1)
{
"_id" : ObjectId("60f028bda2255f376aedeb7f"),
"id_am" : "0124525046",
"id_kromeda" : "0124525046",
"descrizione" : "Alternatore",
"prezzo" : "334.60",
"classe" : "200",
"campo_a" : "BOSCH VARI 2",
"campo_b" : "BOSCH VARI 2",
"produttore" : "BOSCH VARI 2",
"prezzo_officine" : "421.60",
"rincaro" : "0%",
"cod_listino" : "027",
"cod_produttore" : "0124525046",
"unita_acquisto" : "1",
"pezzi_unita" : "NR",
"is_enabled" : true,
"cd_type" : "SPARE"
}
You can use Aggregation pipeline with 2 $set stages. In each stage filter out one special character with $replaceAll.
db.collection.update({},
[
{
$set: {
descrizione: {
$replaceAll: {
input: "$descrizione",
find: "^",
replacement: ""
}
}
}
},
{
$set: {
descrizione: {
$replaceAll: {
input: "$descrizione",
find: "-",
replacement: ""
}
}
}
}
],
{
multi: true
})
Working example
I have such Article-documents:
{
"_id" : "rNiwdR8tFwbTdr2oX",
"createdAt" : ISODate("2018-08-25T12:23:25.797Z"),
"title" : "Happy",
"lines" : [
{
"id" : "5efa6ad451048a0a1807916c",
"text" : "Test 1",
"align" : "left",
"indent" : 0
},
{
"id" : "ae644f39553d46f85c6e1be9",
"text" : "Test 2"
},
{
"id" : "829f874878dfd0b47e9441c2",
"text" : "Test 3"
},
{
"id" : "d0a46ef175351ae1dec70b9a",
"text" : "Test 4"
},
{
"id" : "9bbc8c8d01bc7029220bed3f",
"text" : "Test 5"
},
{
"id" : "6b5c02996a830f807e4d8e35",
"text" : "Test 6",
"indent" : 0
}
]
}
I need to update some Lines.
For example I have array with ids of the line which must be updated.
let lineIds = [
"5efa6ad451048a0a1807916c",
"829f874878dfd0b47e9441c2",
"6b5c02996a830f807e4d8e35"
];
So I try to update attributes "attr" for the "lines" and I do following:
'articles.updateLines': function (articleId, lineIds, attr, value) {
return Articles.update({
'_id': articleId,
'lines.id': { $in: lineIds }
},
{
$set: {
['lines.$.' + attr]: value
}
},
{ multi: true }
);
}
The problem is that just the first line (with id="5efa6ad451048a0a1807916c") is updated.
Any ideas? Thanks! :)
You can use $[]. This will works only MongoDB version 3.6 and above.
Refer link :
https://docs.mongodb.com/manual/reference/operator/update/positional-all/
You can also see this stackoverflow question reference:
How to add new key value or change the key's value inside a nested array in MongoDB?
You can convert below query in your function
db.col.update(
{ '_id':"rNiwdR8tFwbTdr2oX", },
{ $set: { "lines.$[elem].text" : "hello" } },
{ arrayFilters: [ { "elem.id": { $in: lineIds } } ],
multi: true
})
'lines.id': { $in: lineIds }
This won't work, because lines is an array.
What I understand from your question, you can prepare a new array with proper processing and replace the lines array with the new one. Here is an idea how to do this:
'articles.updateLines': function (articleId, lineIds, attr, value) {
let lines = Articles.findOne(articleId).lines;
// prepare a new array with right elements
let newArray = [];
for(let i=0; i<lines.length; i++){
if(lineIds.includes(lines[i].id)){
newArray.push(value)
}
else newArray.push(lines[i])
}
return Articles.update({
'_id': articleId,
},
{
$set: {
lines: newArray
}
}
);
}
I have a collection where documents can have an unknown number of sub documents:
"agent_id": {
"0":"1234",
"1":"2234",...etc
How do I search for an exact match in all the agent_id sub-fields?
You need to dynamically create an object with properties that are a concatenation of the embedded document name agent_id with the dot (.) and the field name, enclosed in quotes, something like this:
var query = {
"agent_id.0": "78343",
"agent_id.1": "78343",
"agent_id.2": "78343",
"agent_id.3": "78343",
...
"agent_id.n": "78343"
}
One way to create the object is generate the sub-documents keys with mapReduce. The following demonstrates this approach. In the Map-Reduce operation, an array of keys in the agent_id subdocument is generated to an output collection "collection_keys" and then used to produce the find() query expression:
Suppose you populate a sample collection
db.collection.insert([
{
"agent_id": {
"0":"1234",
"1":"2234",
"56":"8451",
"74":"1475",
"10":"1234"
}
},
{
"agent_id": {
"5":"5874",
"18":"2351"
}
}
])
Running the following mapReduce operation
var mr = db.runCommand({
"mapreduce" : "collection",
"map" : function() {
for (var key in this.agent_id) { emit(key, null); }
},
"reduce" : function(key, stuff) {
return null
},
"out": "collection" + "_keys"
});
var query = { "$or": [] },
value = "1234";
db[mr.result].distinct("_id").forEach(function (key){
var obj = {};
obj["agent_id." + key] = value;
query["$or"].push(obj)
});
printjson(query);
will produce:
{
"$or" : [
{
"agent_id.0" : "1234"
},
{
"agent_id.1" : "1234"
},
{
"agent_id.10" : "1234"
},
{
"agent_id.18" : "1234"
},
{
"agent_id.5" : "1234"
},
{
"agent_id.56" : "1234"
},
{
"agent_id.74" : "1234"
}
]
})
You can then use the query document in your find() query:
db.collection.find(query)
which will produce the result:
/* 0 */
{
"_id" : ObjectId("561d5312cd05efc95a1ea1f4"),
"agent_id" : {
"0" : "1234",
"1" : "2234",
"56" : "8451",
"74" : "1475",
"10" : "1234"
}
}
I'm using Mongoose and have a schema like this:
var chat = new mongoose.Schema({
chatId : String,
members : [{
id : String,
name : String
}]
});
Suppose I have two chat document like this
{
chatId : 'Edcjjb',
members : [
{
id : 'a1',
name : 'aaa'
},
{
id : 'b1',
name : 'bbb'
}
]
}
{
chatId : 'Fxcjjb',
members : [
{
id : 'a1',
name : 'aaa'
},
{
id : 'b1',
name : 'bbb'
},
{
id : 'c1',
name : 'ccc'
}
]
}
I want to find all those documents which have only specfied members Id.
For example, if I specify a1 and b1
then only the first document should be retrieved as the second document contains id c1 as well.
And if I specifiy a1,b1,c1
then only second document should be specified.
Please tell me how to do this in mongoose
You can specify a clause on the array size, like
{ members : { $size : 2 } } in your first example and
{ members : { $size : 3 } } in the second one.
Can that work for you?
EDIT: I should also mention that the other part of the query should be
{ "members.id": { $all: [ "a1" , "b1" ] } }
and, for the second example,
{ "members.id": { $all: [ "a1" , "b1", "c1" ] } }
Suppose I have documents that, among others, have these fields:
{
"class" : String,
"type" : String,
"name" : String,
}
For example, many like this:
{
"class": "class A",
"type": "type 1",
"Name": "ObjectA1"
}
{
"class": "class A",
"type": "type 2",
"Name": "ObjectA2_1"
}
{
"class": "class A",
"type": "type 2",
"Name": "ObjectA2_2"
}
{
"class": "class B ",
"type": "type 3",
"Name": "ObjectB3"
}
What I want is a query that returns me the following structure
{
"class A" : {
"type 1" : ["ObjectA1"],
"type 2" : ["ObjectA2_1", "ObjectA2_2"]
},
"class B" : {
"type 3" : ["ObjectB3"]
}
}
I tried using aggregate with $group but could not do this. Any thoughts?
PS: I would like to do this on mongodb shell, not mongoose or something like this.
The problem with using the aggregation framework will be that you cannot specify an arbitrary key name for a property of an object. So reshaping using that would not be possible without being able to specify all of the possible key names.
So to get the result you would need to work something in JavaScript such as mapReduce:
First define a mapper:
var mapper = function () {
var key = this["class"];
delete this._id;
delete this["class"];
emit( key, this );
};
Then a reducer:
var reducer = function (key, values) {
var reducedObj = {};
values.forEach(function(value) {
if ( !reducedObj.hasOwnProperty(value.type) )
reducedObj[value.type] = [];
reducedObj[value.type].push( value.Name );
});
return reducedObj;
};
And because you have ( in your sample at least ) possible items that will be emitted from the mapper with only 1 key value you will also need a finalize function:
var finalize = function (key,value) {
if ( value.hasOwnProperty("name") ) {
value[value.type] = value.name;
delete value.type;
delete value.name;
}
return value;
};
Then you call the mapReduce function as follows:
db.collection.mapReduce(
mapper,
reducer,
{ "out": { "inline": 1 }, "finalize": finalize }
)
And that gives the following output:
"results" : [
{
"_id" : "class A",
"value" : {
"type 1" : [
"ObjectA1"
],
"type 2" : [
"ObjectA2_1",
"ObjectA2_2"
]
}
},
{
"_id" : "class B ",
"value" : {
"type" : "type 3",
"Name" : "ObjectB3"
}
}
],
While the result is formatted in a very mapReduce way, it is definitely much the same as your result.
But if you really did want to take that further, you can always do the following:
Define another mapper:
var mapper2 = function () {
emit( null, this );
};
And another reducer:
var reducer2 = function (key,values) {
reducedObj = {};
values.forEach(function(value) {
reducedObj[value._id] = value.value;
});
return reducedObj;
};
Then run the first mapReduce with the output to a new collection:
db.collection.mapReduce(
mapper,
reducer,
{ "out": { "replace": "newcollection" }, "finalize": finalize }
)
Followed by a second mapReduce on the new collection:
db.newcollection.mapReduce(
mapper2,
reducer2,
{ "out": { "inline": 1 } }
)
And there is your result:
"results" : [
{
"_id" : null,
"value" : {
"class A" : {
"type 1" : [
"ObjectA1"
],
"type 2" : [
"ObjectA2_1",
"ObjectA2_2"
]
},
"class B " : {
"type" : "type 3",
"Name" : "ObjectB3"
}
}
}
],
I found a workaround for what I needed. It's not the same but solves my problem.
db.myDb.aggregate(
{
$group:{
_id: {
class_name : "$class",
type_name : "$name"
},
items: {
$addToSet : "$name"
}
}
},
{
$group:{
_id : "$_id.class_name",
types : {
$addToSet : {
type : "$_id.type_name",
items : "$items"
}
}
}
})
this gave me something like:
{
_id : "class A",
types: [
{
type: "type 1",
items: ["ObjectA1"]
},
{
type: "type 2",
items: ["ObjectA2_1", "ObjectA2_2"]
}
]
},
{
_id : "class B",
types: [
{
type: "type 3",
items: ["ObjectB3"]
}
]
}
Both code and example were written here so there may be typos.
So this is about it. I want to thank #Neil Lunn for his awesome answer and dedication.
Marcel