MongoDB - fonctionnalités de base

Objectif:



maîtriser les fonctionnalités de base de mongodb



  • remplir MongoDB de données;
  • écrire plusieurs requêtes pour récupérer et mettre à jour les données
  • créer des index et comparer les performances.






Remplir l'entrepôt de données



À la recherche de suffisamment de données pour étudier les capacités de base de MongoDB, je me suis installé sur le jeu de données NASA Earth Meteorite Landings (1000 lignes avec des informations sur les météorites tombées sur Terre, du référentiel https://github.com/jdorfman/awesome- json-datasets .



Remarque : trouvé des informations plus complètes (45,7K) https://data.nasa.gov/Space-Science/Meteorite-Landings/gh4g-9sfh , mais l'exportation vers JSON via leur API ne donne que 1000 enregistrements (incompréhensible) , il est nécessaire de pelleter les données complètes du fichier CSV exporté https://data.nasa.gov/api/views/gh4g-9sfh/rows.csv?accessType=DOWNLOAD ?



Remarque: oups, il est possible d'obtenir des données complètes en JSON, mais c'est un hack. J'espère sincèrement que ce n'est pas une injection SQL



https :? //Data.nasa.gov/api/id/gh4g-9sfh.json $ select = ` name`,` id`, `nametype`,` recclass`, ` mass`, `fall`,` year`, `reclat`,` reclong`, `geolocation` & $ order =`: id` + ASC & $ limit = 46000 & $ offset = 0



wc ./gh4g-9sfh.json
     45716   128491 10441343 ./gh4g-9sfh.json




Pour interagir avec le serveur sur le réseau, j'ai installé uniquement le client et les outils sur la machine locale:



wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
echo "deb http://repo.mongodb.org/apt/debian buster/mongodb-org/4.4 main" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list

sudo apt-get update
sudo apt-get install -y mongodb-org-shell
sudo apt-get install -y mongodb-org-tools




Vérification de la connexion:



mongo nosql-2020.otus --port 32789

    MongoDB shell version v4.4.1
    connecting to: mongodb://nosql-2020.otus:32789/test?compressors=disabled&gssapiServiceName=mongodb
    Implicit session: session { "id" : UUID("5ff24788-0710-4a1a-821f-7acb2eddfb4f") }
    MongoDB server version: 4.4.1
    Welcome to the MongoDB shell.
    For interactive help, type "help".
    For more comprehensive documentation, see
            https://docs.mongodb.com/
    Questions? Try the MongoDB Developer Community Forums
            https://community.mongodb.com




Importation de données d'une machine locale vers une machine distante:



mongoimport --host nosql-2020.otus  --db "otus_003" --port 32789 --collection eml45k --jsonArray --file ./003_MONGODB.files/gh4g-9sfh.json
    
    2020-10-12T01:01:13.826+0100    connected to: mongodb://nosql-2020.otus:32789/
    2020-10-12T01:01:16.827+0100    [#######.................] otus_003.eml45k      2.99MB/9.96MB (30.0%)
    2020-10-12T01:01:19.827+0100    [###############.........] otus_003.eml45k      6.44MB/9.96MB (64.6%)
    2020-10-12T01:01:22.827+0100    [#######################.] otus_003.eml45k      9.81MB/9.96MB (98.5%)
    2020-10-12T01:01:23.035+0100    [########################] otus_003.eml45k      9.96MB/9.96MB (100.0%)
    2020-10-12T01:01:23.035+0100    45716 document(s) imported successfully. 0 document(s) failed to import.




Remarque : 10 secondes pour tout, récupération de données amusante







> show databases
admin     0.000GB
config    0.000GB
local     0.000GB
otus_003  0.000GB
test      0.000GB
> use otus_003
switched to db otus_003
> show collections
em
l




Nous recherchons une météorite avec un nom connu:



> db.eml45k.find({name:"Bjelaja Zerkov"})

    { "_id" : ObjectId("5f8380a91c0ab84b54bfe394"), "name" : "Bjelaja Zerkov", "id" : "5063", "nametype" : "Valid", "recclass" : "H6", "mass" : "1850", "fall" : "Fell", "year" : "1796-01-01T00:00:00.000", "reclat" : "49.783330", "reclong" : "30.166670", "geolocation" : { "latitude" : "49.78333", "longitude" : "30.16667" } }




Nous recherchons une météorite utilisant des coordonnées connues:



> db.eml45k.find({ "geolocation" : { "latitude" : "44.83333" , "longitude" : "95.16667" } })

{ "_id" : ObjectId("5f8380a91c0ab84b54bfe322"), "name" : "Adzhi-Bogdo (stone)", "id" : "390", "nametype" : "Valid", "recclass" : "LL3-6", "mass" : "910", "fall" : "Fell", "year" : "1949-01-01T00:00:00.000", "reclat" : "44.833330", "reclong" : "95.166670", "geolocation" : { "latitude" : "44.83333", "longitude" : "95.16667" } }




Récupérer une liste des météorites tombées triées par année de chute (je me demande pourquoi la NASA n'a pas d'heure de chute spécifique au temps moyen de Greenwich) et limiter la liste des champs sélectionnables:

> db.eml45k.find( { }, {year: 1, id: 1, name: 1, _id: 0 }).sort( { year: -1 } )

{ "name" : "Northwest Africa 7701", "id" : "57150", "year" : "2101-01-01T00:00:00.000" }
{ "name" : "Chelyabinsk", "id" : "57165", "year" : "2013-01-01T00:00:00.000" }
{ "name" : "Northwest Africa 7755", "id" : "57166", "year" : "2013-01-01T00:00:00.000" }
{ "name" : "Northwest Africa 7812", "id" : "57258", "year" : "2013-01-01T00:00:00.000" }
{ "name" : "Northwest Africa 7822", "id" : "57268", "year" : "2013-01-01T00:00:00.000" }
{ "name" : "Northwest Africa 7856", "id" : "57421", "year" : "2013-01-01T00:00:00.000" }
{ "name" : "Northwest Africa 7855", "id" : "57420", "year" : "2013-01-01T00:00:00.000" }
{ "name" : "Northwest Africa 7857", "id" : "57422", "year" : "2013-01-01T00:00:00.000" }
{ "name" : "Northwest Africa 7858", "id" : "57423", "year" : "2013-01-01T00:00:00.000" }
{ "name" : "Northwest Africa 7861", "id" : "57425", "year" : "2013-01-01T00:00:00.000" }
{ "name" : "Northwest Africa 7862", "id" : "57426", "year" : "2013-01-01T00:00:00.000" }
{ "name" : "Northwest Africa 7863", "id" : "57427", "year" : "2013-01-01T00:00:00.000" }
{ "name" : "Battle Mountain", "id" : "56133", "year" : "2012-01-01T00:00:00.000" }
{ "name" : "Sutter's Mill", "id" : "55529", "year" : "2012-01-01T00:00:00.000" }
{ "name" : "Antelope", "id" : "57455", "year" : "2012-01-01T00:00:00.000" }
{ "name" : "Catalina 009", "id" : "57173", "year" : "2012-01-01T00:00:00.000" }
{ "name" : "Jiddat al Harasis 799", "id" : "57428", "year" : "2012-01-01T00:00:00.000" }
{ "name" : "Johannesburg", "id" : "55765", "year" : "2012-01-01T00:00:00.000" }
{ "name" : "Ksar Ghilane 011", "id" : "55606", "year" : "2012-01-01T00:00:00.000" }
{ "name" : "Ksar Ghilane 010", "id" : "55605", "year" : "2012-01-01T00:00:00.000" }
Type "it" for more
>




Quelque chose que je n'ai pas trouvé? comment combiner deux champs directement lors de la sélection:



"geolocation" : { "latitude" : "49.78333", "longitude" : "30.16667" } 




à un certain point (il convient de noter que la commande a été modifiée conformément à la documentation ( https://docs.mongodb.com/manual/geospatial-queries/#geospatial-legacy , c'est-à-dire



< field >: [< longitude >, < latitude >]):




"geolocation" : { "type" : "Point", "coordinates" : [  30.16667 , 49.78333 ] } }




directement lorsqu'on lui demande de faire (qui sait?) quelque chose comme ça:



> db.eml45k.find({ 
    [ 
        {$toDouble: "$geolocation.longitude"} ,
        {$toDouble: "$geolocation.latitude"} 
    ] : {
        $geoWithin: {
            $geometry: {
                type : "Polygon" ,
                coordinates: [[
                    ... ,
                ]]
            }
        }
    }
}) 




Par conséquent, j'ai créé un champ artificiel dans la collection:



db.eml45k.updateMany( 
    {},
    [{
        $set: {
            "pointed_geolocation.type" : "Point",
            "pointed_geolocation.coordinates" : [ 
                { $toDouble : "$geolocation.longitude" } , 
                { $toDouble: "$geolocation.latitude" } 
            ]
        }
    }]
);

{ "acknowledged" : true, "matchedCount" : 45716, "modifiedCount" : 45716 }




et nous pouvons enfin partir à la recherche de météorites non détectées tombées dans une certaine zone:



> db.eml45k.find({ 
    "pointed_geolocation.coordinates" : {
        $geoWithin: {
            $geometry: {
                type : "Polygon" ,
                coordinates: [[
                    [ 47.0 , 33.0  ], 
                    [ 47.0 , 65.0 ], 
                    [ 169.0 , 65.0 ],
                    [ 169.0 ,  33.0 ],
                    [ 47.0 , 33.0 ]
                ]]
            }
        }
    },
    'fall': 'Fell'
},
{ 
    year: {$year: { "$toDate": "$year"}}, 
    "pointed_geolocation.coordinates": 1, 
    name: 1, 
    _id: 0 
}).sort( { year: -1 } )




Échantillon
{ «name»: «Chelyabinsk», «pointed_geolocation»: { «coordinates»: [ 61.11667, 54.81667 ] }, «year»: 2013 }

{ «name»: «Dashoguz», «pointed_geolocation»: { «coordinates»: [ 59.685, 41.98444 ] }, «year»: 1998 }

{ «name»: «Kunya-Urgench», «pointed_geolocation»: { «coordinates»: [ 59.2, 42.25 ] }, «year»: 1998 }

{ «name»: «Sterlitamak», «pointed_geolocation»: { «coordinates»: [ 55.98333, 53.66667 ] }, «year»: 1990 }

{ «name»: «Undulung», «pointed_geolocation»: { «coordinates»: [ 124.76667, 66.13889 ] }, «year»: 1986 }

{ «name»: «Omolon», «pointed_geolocation»: { «coordinates»: [ 161.80833, 64.02 ] }, «year»: 1981 }

{ «name»: «Yardymly», «pointed_geolocation»: { «coordinates»: [ 48.25, 38.93333 ] }, «year»: 1959 }

{ «name»: «Vengerovo», «pointed_geolocation»: { «coordinates»: [ 77.26667, 56.13333 ] }, «year»: 1950 }

{ «name»: «Kunashak», «pointed_geolocation»: { «coordinates»: [ 61.36667, 55.78333 ] }, «year»: 1949 }

{ «name»: «Krasnyi Klyuch», «pointed_geolocation»: { «coordinates»: [ 56.08333, 54.33333 ] }, «year»: 1946 }

{ «name»: «Lavrentievka», «pointed_geolocation»: { «coordinates»: [ 51.56667, 52.45 ] }, «year»: 1938 }

{ «name»: «Pavlodar (stone)», «pointed_geolocation»: { «coordinates»: [ 77.03333, 52.3 ] }, «year»: 1938 }

{ «name»: «Kainsaz», «pointed_geolocation»: { «coordinates»: [ 53.25, 55.43333 ] }, «year»: 1937 }

{ «name»: «Ichkala», «pointed_geolocation»: { «coordinates»: [ 82.93333, 58.2 ] }, «year»: 1936 }

{ «name»: «Nikolaevka», «pointed_geolocation»: { «coordinates»: [ 78.63333, 52.45 ] }, «year»: 1935 }

{ «name»: «Brient», «pointed_geolocation»: { «coordinates»: [ 59.31667, 52.13333 ] }, «year»: 1933 }

{ «name»: «Pesyanoe», «pointed_geolocation»: { «coordinates»: [ 66.08333, 55.5 ] }, «year»: 1933 }

{ «name»: «Kuznetzovo», «pointed_geolocation»: { «coordinates»: [ 75.33333, 55.2 ] }, «year»: 1932 }

{ «name»: «Boriskino», «pointed_geolocation»: { «coordinates»: [ 52.48333, 54.23333 ] }, «year»: 1930 }

{ «name»: «Khmelevka», «pointed_geolocation»: { «coordinates»: [ 75.33333, 56.75 ] }, «year»: 1929 }

Type «it» for more

> it

{ «name»: «Mamra Springs», «pointed_geolocation»: { «coordinates»: [ 62.08333, 45.21667 ] }, «year»: 1927 }

{ «name»: «Demina», «pointed_geolocation»: { «coordinates»: [ 84.76667, 51.46667 ] }, «year»: 1911 }

{ «name»: «Krutikha», «pointed_geolocation»: { «coordinates»: [ 77, 56.8 ] }, «year»: 1906 }

{ «name»: «Barnaul», «pointed_geolocation»: { «coordinates»: [ 84.08333, 52.73333 ] }, «year»: 1904 }

{ «name»: «Tyumen», «pointed_geolocation»: { «coordinates»: [ 65.53333, 57.16667 ] }, «year»: 1903 }

{ «name»: «Ochansk», «pointed_geolocation»: { «coordinates»: [ 55.26667, 57.78333 ] }, «year»: 1887 }







Etrange, je ne savais pas que Chelyabinsky ne figurait pas dans la catégorie des trouvés.



Agrégons et trouvons combien ont été trouvés et combien ne l'étaient pas:



db.eml45k.aggregate([
{ $match: { 
    "pointed_geolocation.coordinates" : {
        $geoWithin: {
            $geometry: {
                type : "Polygon" ,
                coordinates: [[
                    [ 47.0 , 33.0  ], 
                    [ 47.0 , 65.0 ], 
                    [ 169.0 , 65.0 ],
                    [ 169.0 ,  33.0 ],
                    [ 47.0 , 33.0 ]
                ]]
            }
        }
    }
} },
    {"$group" : {_id: "$fall", count: { $sum: 1 }}}
])

{ "_id" : "Fell", "count" : 26 }
{ "_id" : "Found", "count" : 63 }




Au total, 63 sur 89 ont été trouvés, et __26__ - __not__ ont été trouvés, il y a donc une chance :)



Utilisation des index Supprimons



tous les index de la collection des expériences précédentes:



db.eml45k.dropIndexes()

{
        "nIndexesWas" : 1,
        "msg" : "non-_id indexes dropped for collection",
        "ok" : 1
}




Essayons de voir le temps d'exécution estimé de la requête:



db.eml45k.find({ 
    "pointed_geolocation.coordinates" : {
        $geoWithin: {
            $geometry: {
                type : "Polygon" ,
                coordinates: [[
                    [ 47.0 , 33.0  ], 
                    [ 47.0 , 65.0 ], 
                    [ 169.0 , 65.0 ],
                    [ 169.0 ,  33.0 ],
                    [ 47.0 , 33.0 ]
                ]]
            }
        }
    }
}).explain("executionStats").executionStats.executionTimeMillis
...
110
...
110
...
109




Le résultat est d'environ 110 secondes en moyenne.



Indexons:


db.eml45k.createIndex( { "pointed_geolocation" : "2dsphere" } )

    {
        "ok" : 0,
        "errmsg" : "Index build failed: 98b9ead2-c156-4312-81af-1adf5896e3c9: Collection otus_003.eml45k ( 6db2d178-61b5-4627-8512-fcf919fe596f ) :: caused by :: Can't extract geo keys: { _id: ObjectId('5f838e30fb89bd9d553ae27f'), name: \"Bulls Run\", id: \"5163\", nametype: \"Valid\", recclass: \"Iron?\", mass: \"2250\", fall: \"Fell\", year: \"1964-01-01T00:00:00.000\", pointed_geolocation: { type: \"Point\", coordinates: [ null, null ] } }  Point must only contain numeric elements",
        "code" : 16755,
        "codeName" : "Location16755"
    }





L'erreur est due à des valeurs NULL, je n'ai pas trouvé quelque chose tout de suite comment (qui sait?) Pour l'exclure de l'index lors de l'indexation, je vais donc supprimer ces clés:



db.eml45k.updateMany(
    { "pointed_geolocation.coordinates" : [ null , null ] },     
    [{         
        $set: { "pointed_geolocation": null }
    }] 
);




Essayer à nouveau l'index



db.eml45k.createIndex( { "pointed_geolocation" : "2dsphere" } )
    
    {
        "ok" : 0,
        "errmsg" : "Index build failed: d33b31d4-4778-4537-a087-58b7bd1968f3: Collection otus_003.eml45k ( 6db2d178-61b5-4627-8512-fcf919fe596f ) :: caused by :: Can't extract geo keys: { _id: ObjectId('5f838e35fb89bd9d553b3b8f'), name: \"Meridiani Planum\", id: \"32789\", nametype: \"Valid\", recclass: \"Iron, IAB complex\", fall: \"Found\", year: \"2005-01-01T00:00:00.000\", reclat: \"-1.946170\", reclong: \"354.473330\", geolocation: { latitude: \"-1.94617\", longitude: \"354.47333\" }, pointed_geolocation: { type: \"Point\", coordinates: [ 354.47333, -1.94617 ] } }  longitude/latitude is out of bounds, lng: 354.473 lat: -1.94617",
        "code" : 16755,
        "codeName" : "Location16755"
    }




Erreur __longitude / latitude est hors limites, lng: 354.473 lat: -1.94617__ et dans la documentation https://docs.mongodb.com/manual/geospatial-queries/#geospatial-legacy




    Valid longitude values are between -180 and 180, both inclusive.
    Valid latitude values are between -90 and 90, both inclusive.




et 354.47333 n'est pas inclus dans la plage de -180 à 180.



Très étrange, au début, j'ai pensé qu'il fallait un amendement partout de moins 180 pour faire



(`$subtract: [{ $toDouble : "$geolocation.longitude" }, 180.0]`)




, mais au final ce n'est pas si simple.



Quelles longitudes ne sont pas à portée:



db.eml45k.find({"pointed_geolocation.coordinates.0": {$lt: -180}} ) #  ,    
db.eml45k.find({"pointed_geolocation.coordinates.0": {$lt: 0}} ) #   ,    
db.eml45k.find({"pointed_geolocation.coordinates.0": {$gt: 180}} ) #  ,     


    { "_id" : ObjectId("5f838e35fb89bd9d553b3b8f"), "name" : "Meridiani Planum", "id" : "32789", "nametype" : "Valid", "recclass" : "Iron, IAB complex", "fall" : "Found", "year" : "2005-01-01T00:00:00.000", "reclat" : "-1.946170", "reclong" : "354.473330", "geolocation" : { "latitude" : "-1.94617", "longitude" : "354.47333" }, "pointed_geolocation" : { "type" : "Point", "coordinates" : [ 354.47333, -1.94617 ] } }





En conséquence, une seule météorite a des coordonnées étranges. Après une recherche, j'ai découvert que cette météorite Meridiani Planum avait été accidentellement trouvée par le rover Opportunity en 2005

( http://old.mirf.ru/Articles/art2427_2.htm ). Ceci est un ( AVERTISSEMENT ) ont observé de météorite martienne ( AVERTISSEMENT ) sur Mars. Voici les jokers de la NASA.



Supprimons-le de la collection.



db.eml45k.remove({"id" : "32789"})
WriteResult({ "nRemoved" : 1 })




Indexage

> db.eml45k.createIndex( { "pointed_geolocation" : "2dsphere" } )
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}




Nous mesurons



db.eml45k.find({ 
    "pointed_geolocation.coordinates" : {
        $geoWithin: {
            $geometry: {
                type : "Polygon" ,
                coordinates: [[
                    [ 47.0 , 33.0  ], 
                    [ 47.0 , 65.0 ], 
                    [ 169.0 , 65.0 ],
                    [ 169.0 ,  33.0 ],
                    [ 47.0 , 33.0 ]
                ]]
            }
        }
    }
}).explain("executionStats").executionStats.executionTimeMillis




En conséquence, lors de la revérification de 104 ... 107 ... 106 ...



C'est un peu étrange, pas très intelligent.



Index supprimé, vérifié.



Sans index et avec index - c'est pareil.



Je l'essaye séparément pour Tcheliabinsk:



db.eml45k.find(
    {"pointed_geolocation.coordinates" : [ 61.11667, 54.81667 ]}
).explain("executionStats").executionStats.executionTimeMillis




sans index et avec un index - pareil.



Vous devez être plus prudent, l'indice est construit pour le pointed_geolocation domaine et pointed_geolocation.coordinates quels impliqué dans la requête .



Par conséquent, la requête



db.eml45k.find({ 
    "pointed_geolocation" : {
        $geoWithin: {
            $geometry: {
                type : "Polygon" ,
                coordinates: [[
                    [ 47.0 , 33.0  ], 
                    [ 47.0 , 65.0 ], 
                    [ 169.0 , 65.0 ],
                    [ 169.0 ,  33.0 ],
                    [ 47.0 , 33.0 ]
                ]]
            }
        }
    }
}).explain("executionStats").executionStats.executionTimeMillis




sans l'indice 125, 123, 119, 123 millisecondes et avec l'indice - 7, 4, 4, 5.



Tout a fonctionné.



All Articles