J'essaie de convertir une chaîne contenant une valeur numérique en valeur dans une requête agrégée dans MongoDB.
Exemple de document
{
"_id": ObjectId("5522XXXXXXXXXXXX"),
"Date": "2015-04-05",
"PartnerID": "123456",
"moop": "1234"
}
Exemple de requête globale que j'utilise
{
aggregate: 'my_collection',
pipeline: [
{$match: {
Date :
{$gt:'2015-04-01',
$lt: '2015-04-05'
}}
},
{$group:
{_id: "$PartnerID",
total:{$sum:'$moop'}
}}]}
où les résultats sont
{
"result": [
{
"_id": "123456",
"total": NumberInt(0)
}
}
Comment pouvez-vous convertir la chaîne en valeur numérique?
L'agrégation MongoDB n'est pas autorisée à modifier le type de données existant de champs donnés. Dans ce cas, vous devez créer du code de programmation pour convertir string
en int
. Vérifiez ci-dessous le code
db.collectionName.find().forEach(function(data) {
db.collectionName.update({
"_id": data._id,
"moop": data.moop
}, {
"$set": {
"PartnerID": parseInt(data.PartnerID)
}
});
})
Si la taille de vos collections dépasse le script ci-dessus, les performances ralentissent. Pour perfomace mongo, fournissez les opérations mongo bulk , à l'aide du type de données mis à jour à l'aide d'opérations mongo bulk.
var bulk = db.collectionName.initializeOrderedBulkOp();
var counter = 0;
db.collectionName.find().forEach(function(data) {
var updoc = {
"$set": {}
};
var myKey = "PartnerID";
updoc["$set"][myKey] = parseInt(data.PartnerID);
// queue the update
bulk.find({
"_id": data._id
}).update(updoc);
counter++;
// Drain and re-initialize every 1000 update statements
if (counter % 1000 == 0) {
bulk.execute();
bulk = db.collectionName.initializeOrderedBulkOp();
}
})
// Add the rest in the queue
if (counter % 1000 != 0) bulk.execute();
Cela réduit essentiellement le nombre d'instructions d'opération envoyées au serveur pour n'envoyer qu'une fois toutes les 1 000 opérations en file d'attente.
Vous pouvez facilement convertir le type de données string en type de données numérique.
N'oubliez pas de changer collectionName & FieldName . Par exemple: CollectionNmae: Users & FieldName: Contactno.
Essayez cette requête ..
db.collectionName.find().forEach( function (x) {
x.FieldName = parseInt(x.FieldName);
db.collectionName.save(x);
});
Finalement, j'ai utilisé
db.my_collection.find({moop: {$exists: true}}).forEach(function(obj) {
obj.moop = new NumberInt(obj.moop);
db.my_collection.save(obj);
});
transformer moop
de chaîne en entier dans my_collection en suivant l'exemple de la réponse de Simone MongoDB: Comment changer le type d'un champ? .
Utilisation de MongoDB 4.0 et plus récent
Vous avez le choix entre deux options: $toInt
ou $convert
. En utilisant $toInt
, suivez l'exemple ci-dessous:
filterDateStage = {
'$match': {
'Date': {
'$gt': '2015-04-01',
'$lt': '2015-04-05'
}
}
};
groupStage = {
'$group': {
'_id': '$PartnerID',
'total': { '$sum': { '$toInt': '$moop' } }
}
};
db.getCollection('my_collection').aggregate([
filterDateStage,
groupStage
])
Si l'opération de conversion rencontre une erreur, l'opération d'agrégation s'arrête et génère une erreur. Pour remplacer ce comportement, utilisez plutôt $convert
.
Utilisation de $convert
groupStage = {
'$group': {
'_id': '$PartnerID',
'total': {
'$sum': {
'$convert': { 'input': '$moop', 'to': 'int' }
}
}
}
};
Utilisation de la carte/réduction
Avec map/reduction, vous pouvez utiliser des fonctions javascript telles que parseInt()
pour effectuer la conversion. À titre d'exemple, vous pouvez définir la fonction de carte pour traiter chaque document d'entrée: Dans la fonction, this
fait référence au document que l'opération de réduction de carte est en train de traiter. La fonction mappe la valeur de chaîne moop
convertie en PartnerID
pour chaque document et émet la paire PartnerID
et convertie moop
. C’est ici que la fonction native javascript parseInt()
peut être appliquée:
var mapper = function () {
var x = parseInt(this.moop);
emit(this.PartnerID, x);
};
Ensuite, définissez la fonction de réduction correspondante avec deux arguments keyCustId
et valuesMoop
. valuesMoop
est un tableau dont les éléments sont les valeurs entières moop
émises par la fonction map et regroupées par keyPartnerID
. La fonction réduit le tableau valuesMoop
à la somme de ses éléments.
var reducer = function(keyPartnerID, valuesMoop) {
return Array.sum(valuesMoop);
};
db.collection.mapReduce(
mapper,
reducer,
{
out : "example_results",
query: {
Date: {
$gt: "2015-04-01",
$lt: "2015-04-05"
}
}
}
);
db.example_results.find(function (err, docs) {
if(err) console.log(err);
console.log(JSON.stringify(docs));
});
Par exemple, avec l'exemple de collection de documents suivant:
/* 0 */
{
"_id" : ObjectId("550c00f81bcc15211016699b"),
"Date" : "2015-04-04",
"PartnerID" : "123456",
"moop" : "1234"
}
/* 1 */
{
"_id" : ObjectId("550c00f81bcc15211016699c"),
"Date" : "2015-04-03",
"PartnerID" : "123456",
"moop" : "24"
}
/* 2 */
{
"_id" : ObjectId("550c00f81bcc15211016699d"),
"Date" : "2015-04-02",
"PartnerID" : "123457",
"moop" : "21"
}
/* 3 */
{
"_id" : ObjectId("550c00f81bcc15211016699e"),
"Date" : "2015-04-02",
"PartnerID" : "123457",
"moop" : "8"
}
L'opération Carte/Réduction ci-dessus enregistrera les résultats dans la collection example_results
et la commande Shell db.example_results.find()
donnera:
/* 0 */
{
"_id" : "123456",
"value" : 1258
}
/* 1 */
{
"_id" : "123457",
"value" : 29
}
Voici une solution basée sur MongoDB pour résoudre ce problème que je viens d’écrire pour le plaisir. Il s’agit d’un analyseur chaîne-nombre côté serveur qui prend en charge les nombres positifs et négatifs, ainsi que les nombres décimaux:
db.collection.aggregate({
$addFields: {
"moop": {
$reduce: {
"input": {
$map: { // split string into char array so we can loop over individual characters
"input": {
$range: [ 0, { $strLenCP: "$moop" } ] // using an array of all numbers from 0 to the length of the string
},
"in":{
$substrCP: [ "$moop", "$$this", 1 ] // return the nth character as the mapped value for the current index
}
}
},
"initialValue": { // initialize the parser with a 0 value
"n": 0, // the current number
"sign": 1, // used for positive/negative numbers
"div": null, // used for shifting on the right side of the decimal separator "."
"mult": 10 // used for shifting on the left side of the decimal separator "."
}, // start with a zero
"in": {
$let: {
"vars": {
"n": {
$switch: { // char-to-number mapping
branches: [
{ "case": { $eq: [ "$$this", "1" ] }, "then": 1 },
{ "case": { $eq: [ "$$this", "2" ] }, "then": 2 },
{ "case": { $eq: [ "$$this", "3" ] }, "then": 3 },
{ "case": { $eq: [ "$$this", "4" ] }, "then": 4 },
{ "case": { $eq: [ "$$this", "5" ] }, "then": 5 },
{ "case": { $eq: [ "$$this", "6" ] }, "then": 6 },
{ "case": { $eq: [ "$$this", "7" ] }, "then": 7 },
{ "case": { $eq: [ "$$this", "8" ] }, "then": 8 },
{ "case": { $eq: [ "$$this", "9" ] }, "then": 9 },
{ "case": { $eq: [ "$$this", "0" ] }, "then": 0 },
{ "case": { $and: [ { $eq: [ "$$this", "-" ] }, { $eq: [ "$$value.n", 0 ] } ] }, "then": "-" }, // we allow a minus sign at the start
{ "case": { $eq: [ "$$this", "." ] }, "then": "." }
],
default: null // marker to skip the current character
}
}
},
"in": {
$switch: {
"branches": [
{
"case": { $eq: [ "$$n", "-" ] },
"then": { // handle negative numbers
"sign": -1, // set sign to -1, the rest stays untouched
"n": "$$value.n",
"div": "$$value.div",
"mult": "$$value.mult",
},
},
{
"case": { $eq: [ "$$n", null ] }, // null is the "ignore this character" marker
"then": "$$value" // no change to current value
},
{
"case": { $eq: [ "$$n", "." ] },
"then": { // handle decimals
"n": "$$value.n",
"sign": "$$value.sign",
"div": 10, // from the decimal separator "." onwards, we start dividing new numbers by some divisor which starts at 10 initially
"mult": 1, // and we stop multiplying the current value by ten
},
},
],
"default": {
"n": {
$add: [
{ $multiply: [ "$$value.n", "$$value.mult" ] }, // multiply the already parsed number by 10 because we're moving one step to the right or by one once we're hitting the decimals section
{ $divide: [ "$$n", { $ifNull: [ "$$value.div", 1 ] } ] } // add the respective numerical value of what we look at currently, potentially divided by a divisor
]
},
"sign": "$$value.sign",
"div": { $multiply: [ "$$value.div" , 10 ] },
"mult": "$$value.mult"
}
}
}
}
}
}
}
}
}, {
$addFields: { // fix sign
"moop": { $multiply: [ "$moop.n", "$moop.sign" ] }
}
})
Je ne fais certainement pas cette publicité comme étant le genou de l'abeille ou quoi que ce soit et cela pourrait avoir de graves conséquences sur les performances pour des jeux de données plus volumineux par rapport à des solutions basées sur le client, mais il peut arriver que cela soit utile ...
Le pipeline ci-dessus transformera les documents suivants:
{ "moop": "12345" } --> { "moop": 12345 }
et
{ "moop": "123.45" } --> { "moop": 123.45 }
et
{ "moop": "-123.45" } --> { "moop": -123.45 }
et
{ "moop": "2018-01-03" } --> { "moop": 20180103.0 }
La chaîne peut être convertie en nombres dans MongoDB v4.0 en utilisant $ toInt operator. Dans ce cas
db.col.aggregate([
{
$project: {
_id: 0,
moopNumber: { $toInt: "$moop" }
}
}
])
les sorties:
{ "moopNumber" : 1234 }
Trois choses doivent prendre en compte:
La collation est ce dont vous avez besoin:
db.collectionName.find().sort({PartnerID: 1}).collation({locale: "en_US", numericOrdering: true})
Cela devrait être sauvé. Cela devrait être comme ça:
db. my_collection.find({}).forEach(function(theCollection) {
theCollection.moop = parseInt(theCollection.moop);
db.my_collection.save(theCollection);
});
Essayer:
"TimeStamp":{$toDecimal: { $toDate:"$Datum"}}
Si vous pouvez éditer tous les documents ensemble:
"TimeStamp": {$toDecimal: {$toDate: "$Your Date"}}
Et pour le client, vous définissez la requête:
Date.parse("Your date".toISOString())
C'est ce qui vous fait travailler avec ISODate.