Réseaux sociaux

Mongodb: comment comparer 2 champs issue d’une projection ?

Sous MongoDB il est possible de stocker les informations sous forme d'objets plus ou moins complexes. Il peut arriver que l'on veut comparer plusieurs champs issu d'un sous objet d'une collection.

Dans notre exemple, nous allons utiliser une collection Produit dans lequel le champ Prix permettra de définir les différents prix (d'achat, de vente HT, de vente TTC, …).

Structure de la Collection

Le champ Prix contient ici 2 valeurs que nous allons comparer :

  • le prix de vente HT,
  • le prix de vente TTC

=> données au format d'origine

Comment réaliser une projection avec filtre ?

Ces données étant complexes, il n'est pas possible de comparer directement les valeurs entre elles. Pour cela il est nécessaire de passer par une projection.

Demande

 [
    {
        '$project' : {
            'prix_vte' : {
                '$filtre' : {
                    'entrée' : '$prix', 
                    'comme' : 'liste', 
                    'cond' : {
                        '$eq' : [
                            '$list.id', 'prix_vente_ht_eur'
                        ]
                    }
                }
            }, 
            'prix_vte_ttc' : {
                '$filtre' : {
                    'entrée' : '$prix', 
                    'comme' : 'liste', 
                    'cond' : {
                        '$eq' : [
                            '$list.id', 'prix_vente_ttc_eur'
                        ]
                    }
                }
            }
        }
    }, {
        '$match' : {
            '$expr' : {
                '$gte' : [
                    '$prix_vte.valeur', '$prix_vte_ttc.valeur'
                ]
            }
        }
    }
]

1 Projection des données

En premier nous réalisons une projection "$project" afin de filtrer et nommer des informations pour les réutiliser dans notre filtre "$match".

Dans ' prix_vte ' nous affectons le résultat du filter '$filter' sur le champ prix ayant pour valeur 'id' = 'prix_vente_ ht _eur'

Idem pour ' prix_vte_ttc ' nous affectons le résultat du filter '$filter' sur le champ prix ayant pour valeur 'id' = 'prix_vente_ ttc _eur'.

=> données au format projeté

2 Comparaison des valeurs

Il reste alors la dernière étape qui consistait à comparer nos 2 valeurs avec un '$match' sous forme d'expression ou l'on teste si '$prix_vte' >= '$prix_vte_ttc'

 '$match' : {
            '$expr' : {
                '$gte' : [
                    '$prix_vte.valeur', '$prix_vte_ttc.valeur'
                ]
            }
        }

3 Récupération des résultats

Au final il ne restera plus que les enregistrements qui passent le test.

Ces enregistrements seront enregistrés au format défini précédemment lors de la projection.

Commentaire demander la requête ?

Il suffit d'exécuter la requête à l'aide de la fonction 'aggregate' en procédant ainsi :

db.Produit.aggregate( [ { '$project': { 'prix_vte': { …… }] )

Autres cas de figure

Dans les cas plus simples lorsque nos 2 champs comparés sont de simples champs, il existe plusieurs manières de faire pour comparer 2 champs.

Ici pas de donnée complexe, uniquement 2 champs de type 'double'

L'équivalent en requête MySQL donnerait ceci :

 SELECT * FROM MaCollection champ1 > champ2

Utiliser $où

Vous pouvez utiliser un '$where'. Sachez simplement que ce sera lent ( car du code Javascript doit être exécuté sur chaque enregistrement), alors pensez à bien créer des index.

 db.MaCollection.find( { $where: function() { return this.champ1 > this.champ2 } } );

Ou plus simplement :

 db.MaCollection.find( { $where : "this.champ1 > this.champ2" } );

Si votre requête consistait uniquement en un $where vous pouvez même transmettre uniquement le code javascript


db.MaCollection.find( "this.champ1 > this.champ2" );

Les expressions $expr

Depuis MongoDB 3.6 il est possible d'utiliser les fonctions d'agrégation dans une requête normale 'find'

 db.MaCollection.find( {$expr:{$gt:["$champ1", "$champ2"]}} );

Ou en requête d'agrégation

 db.MaCollection.aggregate({$match:{$expr:{$gt:["$champ1", "$champ2"]}}})

Les projections bis

Il arrive parfois qu'un grand nombre d'enregistrements doivent être traités lors d'une recherche. Afin d'en accélérer grandement la vitesse de traitement, il est parfois nécessaire de faire appel aux projections.

Nous sommes ici dans le cas ou notre collection contient plusieurs milliers d'enregistrements avec une 30aine de champs

Test avec un 'trouver' classique

Temps de réponse de 4,41s

 db.MaCollection.find( { $where : "this.champ1 > this.champ2" } );
Test avec un '$project'

Temps de réponse de 185ms

 db.MaCollection.aggregate([ { '$project': { "isGreater": { "$cmp": [ "$champ1", "$champ2" ] }, 'champ1': 1, 'champ2': 1 } }, { '$match': {'isGreater': 1 } } ]))

Par la projection nous ne récupérons que nos champs 1 et 2, ainsi que le résultat de la comparaison entre champ1 et champ2 que nous assignons dans le champ ' isGreater' que nous testons.


Georges 20 septembre 2021
Partager cet article
Se connecter pour laisser un commentaire.