Let's suppose I have the following fields:
userId: int
userSituation: int
userStatus: int
and if I create the following indexes:
{ userId, userSituation }
{ userId, userStatus }
Will MongoDB benefit in some way? For example, both indexes start from the same ordered index of userId ? Or would I be better of changing the 2nd index to { userStatus , userId}, so I would have a prefix for userStatus?
Related
I create an API that updates a record associated with a foreign key
if I just put a value to items so I want it to return remove other values that I don't put
if I edit some value in items so I want it to return the value that I edited
if I put value over value of items so I want it to return the old value of items and the value that I put over
example: const record = {id:1,name:"abc",items:[{id:1,name:"abc",recordId:1},{id:2,name:"abcd",recordId:1}]}
const update = await dbRecord.update({id,name},{where: {id: req.params.id},include:[model:'items',id:[1,2]});
You can use Sequelize mixins. Sequelize has special methods that uses the prefix get add set concatenated with the model name.
const update = await dbRecord.update(
{ id, name },
{ where: { id: req.params.id } }
);
//array get from body
ex: array = [
{ id: 1, name: "abc", recordId: 1 },
{ id: 2, name: "abcd", recordId: 1 },
];
const itemId = array.map((arr) => arr.id);
// result = [1,2]
await dbItems.bulkCreate(array, {
updateOnDuplicate: ["name"],
});
for (var i = 0; i < update.items.length; i++) {
const item = update.items[i];
if (!itemId.includes(container.id)) {
await container.destroy();
}
}
so it create update delete in once time.
Here is the group operation that I want to create depending on a nomenclature object.
private static GroupOperation createStatsGroupOperationFromNomenclature(Nomenclature nomenclature) {
Fields groupFields = Fields.fields("departmentCode");
nomenclature.getDepartmentCode().ifPresent(code -> groupFields.and("subDepartmentCode"));
nomenclature.getSubDepartmentCode().ifPresent(code -> groupFields.and("categoryCode"));
nomenclature.getCategoryCode().ifPresent(code -> groupFields.and("subCategoryCode"));
return group(groupFields)
.count().as("nbProducts")
.sum("$proposedMatchesAmount").as("nbProposedMatches")
.sum("$reviewedMatchesAmount").as("nbReviewedMatches");
}
With the previous function if I provide a departmentCode and a subDepartmentCode inside the nomenclature parameter, here is the mongo query that is executed :
{
_id: {
"departmentCode": "$departmentCode",
"subDepartmentCode": "$subDepartmentCode"
},
"nbProduct": {
$sum: 1
},
"proposedMatchesAmount": {
$sum: "$proposedMatchesAmount"
},
"reviewedMatchesAmount": {
$sum: "$reviewedMatchesAmount"
}
}
The result of this query are parsed in the following object :
#Builder
#Value
public class ProductsStatsDocument {
#Id
Nomenclature nomenclature;
Integer nbProducts;
Integer nbProposedMatches;
Integer nbReviewedMatches;
}
Problems append when I provide only a departmentCode inside the nomenclature parameter. Then the builded group operation has the following mongo query language equivalent:
{
_id: "$departmentCode",
"nbProduct": {
$sum: 1
},
"proposedMatchesAmount": {
$sum: "$proposedMatchesAmount"
},
"reviewedMatchesAmount": {
$sum: "$reviewedMatchesAmount"
}
}
And the result of this query couldn't be parsed to the previous ProductsStatsDocument because the result _id field id now a String and not a Nomenclature object.
Is it possible to force the group method to use an object as result _id field even with only one field ? Or is there an other way to build such a mongo group operation ?
=================================================================
Found the "why" of this issue. Here is a piece of code from spring data that transform the GroupOperation into a bson object :
} else if (this.idFields.exposesSingleNonSyntheticFieldOnly()) {
FieldReference reference = context.getReference((Field)this.idFields.iterator().next());
operationObject.put("_id", reference.toString());
} else {
And here is the exposesSingleNonSyntheticFieldOnly method :
boolean exposesSingleNonSyntheticFieldOnly() {
return this.originalFields.size() == 1;
}
As you can see, as soon as there is only one field to group on, it's used as _id result value.
So finally the solution that seems to works for now is to create a custom AggregationOperation that manage the document transformation _id part :
public class ProductsStatsGroupOperation implements AggregationOperation {
private static GroupOperation getBaseGroupOperation() {
return group()
.count().as("nbProducts")
.sum("$proposedMatchesAmount").as("nbProposedMatches")
.sum("$reviewedMatchesAmount").as("nbReviewedMatches");
}
private final Nomenclature nomenclature;
public ProductsStatsGroupOperation(Nomenclature nomenclature) {
this.nomenclature = nomenclature;
}
#Override
public Document toDocument(AggregationOperationContext context) {
Document groupOperation = getBaseGroupOperation().toDocument(context);
Document operationId = new Document();
for (Field field : getFieldsToGroupOn()) {
FieldReference reference = context.getReference(field);
operationId.put(field.getName(), reference.toString());
}
((Document)groupOperation.get("$group")).put("_id", operationId);
return groupOperation;
}
private Fields getFieldsToGroupOn() {
Fields groupFields = Fields.fields("departmentCode");
if (nomenclature.getDepartmentCode().isPresent()) {
groupFields = groupFields.and("subDepartmentCode");
}
if (nomenclature.getSubDepartmentCode().isPresent()) {
groupFields = groupFields.and("categoryCode");
}
if (nomenclature.getCategoryCode().isPresent()) {
groupFields = groupFields.and("subCategoryCode");
}
return groupFields;
}
}
There is a bad thing about this solution: the overrided method toDocument seems to be deprecated.
I have a Mongo collection with documents like this:
a: { product: 1, country: 2, stock: 1}
b: { product: 1, country: 3, stock: 3}
c: { product: 2, country: 1, stock: 1}
Sometimes I want to get the stock of a product in all countries (so I retrieve the product stock in all countries and then I add them) and other times I want the stock in an specific country.
Is it possible to make a single method like:
findByProductAndCountry(Integer product, Integer country)
that works like this:
findByProductAndCountry(1, 2) //returns document a
findByProductAndCountry(1, null) //returns documents a and b
Thanks in advance!
Answering to your question: No. It is not possible to write such a query in mongodb so you can not achieve that with a single spring data mongodb method.
What I suggest is to write a default method in the repository interface for that. This way you can have it with the rest of your query methods:
public interface ProductRepository extends MongoRepository<Product, String> {
List<Product> findByProduct(int product);
List<Product> findByProductAndCountry(int product, int country);
default List<Product> findByProductAndNullableCountry(Integer product, Integer country) {
if (country != null) {
return findByProductAndCountry(product, country);
} else {
return findByProduct(product);
}
}
}
How to perform bulk operation for mongo db? I already have script to change data type but it is not considering huge volume.
Collection 'store' has column called 'priceList' which is Array having multiple fields one of which is 'value'. Right now it is integer and now I want to convert it to custom record object.
Current schema
store
- _id
- name [String]
- priceList [Array]
- amount [Record] //{"unscaled":<value>, "scaled", <value>}
- value [Integer]
Need to convert value to [Record] as mentioned in above format
For e.g:- value: 2 will become value: {"unscaled":2, "scaled", 0};
db.store.find({priceList: { $exists : true}}).forEach(function(obj){
obj.priceList.forEach(function(y){
y.value = ({"unscaled":NumberLong(y.value),"scaled",NumberInt(0)});
db.store.save(obj);
})
});
Thanks!!
you try like this,
db.store.find({
priceList: {
$exists: true
}
}).forEach(function(myDoc) {
var child = myDoc.priceList;
for (var i = 0; i < child.length; i++) {
var ob = child[i];
var obj = {"unscaled":NumberLong(ob.value),"scaled":NumberInt(0)};
if ('value' in ob) {
ob.value = obj;
child[i] = ob;
}
}
db.store.update({
_id: myDoc._id
}, {
$set: {
subMenu: child
}
});
});
Hope this helps (updated) !
db.store.find({priceList: {$exists: true}})
.toArray()
.forEach(o => db.store.update(
{_id: o._id},
{ $set: {
priceList: o.priceList.map(l => Object.assign(l, {
amount: {
unscaled: l.value,
scaled: 0
}
}))
}
}
))
I have book and author collection.in this name and works_written are the same value column respectively.so i tried the following script but it emit only first map values,second map values not emitted.
book = function() {
emit(this.id, {name: this.name,editions:this.editions});
}
author = function() {
emit(this.id, {name:this.name,works_written: this.works_writtten,});
}
r_b = function(k, values) {
var result = {};
values.forEach(function(value) {
var name;
for (name in value) {
if (value.hasOwnProperty(name)) {
result[name] = value[name];
}
}
});
return result;
};
r_a = function(k, values) {
var result = {};
values.forEach(function(value) {
var works_written;
for (works_written in value) {
if (value.hasOwnProperty(works_written)) {
result[works_written] = value[works_written];
}
}
});
return result;
};
res = db.book.mapReduce(book, r_ja, {out: {reduce: 'joined'}})
res = db.author.mapReduce(author, r_jp, {out: {reduce: 'joined'}})
can someone help me out?
From looking at your code, it seems like you have two collections, "book" and "author". Each book is structured as
{
id: <some id>,
name: <some name>,
editions: <comma-separated string of editions>
}
and each author is structured as
{
id: <some id>,
name: <some name>,
works_written: <comma-separated string of works written>
}
It would be more reasonable to store both works_written and editions as arrays rather than comma-separated lists each packed into an individual string. This would make iterating over the array possible.
Additionally, do you have multiple documents for each author and each book? If not, you do not need a mapreduce to do what you are attempting to do - a simple find() should work.
In case I have misinterpreted, what exactly are you attempting to do?