Mongo DB: Funktion mit Hilfe von der Aggregatsfunktion optimieren

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
tobias.billen
User
Beiträge: 5
Registriert: Montag 30. Juli 2018, 14:47

Hi zusammen,

ich habe eine Funktion get_data_of_imei(). Diese Funktion soll ich so umschreiben, dass ich von MongoDB die Aggeragtsfunktion nutzen soll. Der Hintergrund ist, dass die Funktion get_data_of_imei zwar das richtige Ergebnis liefert, aber deutlich zu langsam ist. Hier ist die Funktion:

Code: Alles auswählen

def get_data_of_imei(imei: str) -> list:
    # Collection where incoming streams are saved
    db.set_collection("tracking")
    # filter streams by imei
    datas = db.find_document_by_parameter({"imei": imei})
    # list of numeric keys (these keys have to delete)
    del_list = []
    for data in datas:
        for key in list(data.keys()):
            if key.isdigit():
                # search description of numeric keys 
                db.set_collection("trackerIDs")
                tmp = db.find_document_by_parameter({"Property ID in AVL packet": int(key)})
                # if key is found, add it with the value of this numeric key
                if tmp and len(tmp) > 0:
                    k = tmp[0]['Property Name']
                    data[k] = data[key]
                    del_list.append(str(key))
    # remove all numerics keys                
    for i in range(0, len(datas)):
        del datas[i]["_id"]
        for dl in del_list:
            if dl in datas[i]:
                del datas[i][dl]
    return datas
Die DB Klasse sieht wie folgt aus:

Code: Alles auswählen

from pymongo import MongoClient


class DB:

    def __init__(self, connection):
        client = MongoClient(
            f"mongodb://{connection['user']}:{connection['pw']}@{connection['ip']}:{connection['port']}/")
        self.db = client[connection['d_db']]
        self.collection = self.db[connection['d_collection']]

    def set_collection(self, collection) -> None:
        self.collection = self.db[collection]

    def set_document(self, document) -> int:
        return self.collection.insert_one(document).inserted_id

    def find_document_by_parameter(self, parameter, limit: int = -1) -> list:
        return list(self.collection.find(parameter) if limit < 1 else self.collection.find(parameter).limit(limit))

    def find_all_documents(self) -> list:
        return list(self.collection.find())

    def delete_document(self, parameter) -> int:
        return self.collection.delete_many(parameter).deleted_count

    def update_document(self, parameter, new_value) -> int:
        return self.collection.update_many(parameter, new_value).modified_count

    def get_current_collection(self) -> str:
        return self.collection.name

    def get_value_by_aggregate(self, aggregate):
        return self.collection.aggregate(aggregate)

In der Mongo DB sind 2 Sammlungen: "trackerIDs" und "tracking"

Auszug aus "trackerIDs":

Code: Alles auswählen

[
  {
    "_id": "64b0fec445c13fbbd834f9f4",
    "Bytes": "1",
    "Description": "Logic: 0/1",
    "HW Support": "FMBXXX FMB001\nFMB010\nFMB110\nFMB120\nFMB122\nFMB125\nFMU125\nFMC125\nFMM125\nFMB130\nFMU130\nFMC130\nFMM130\nFMB140\nFMB900\nFMB920\nFMB962\nFMB964\nFMB202\nFMB204\nFMB206\nMTB100",
    "Max": "1",
    "Min": "0",
    "Multiplier": "",
    "Parameter Group": "Permanent I/O elements",
    "Property ID in AVL packet": "1",
    "Property Name": "Digital Input 1",
    "Type": "Unsigned",
    "Units": ""
  },
  {
    "_id": "64b0fec545c13fbbd834f9f5",
    "Bytes": "1",
    "Description": "Logic: 0/1",
    "HW Support": "FMBXXX FMB110\nFMB120\nFMB122\nFMB125\nFMU125\nFMC125\nFMM125\nFMB130\nFMU130\nFMC130\nFMM130\nFMB140\nFMB202\nFMB204\nFMB206\nFMB640",
    "Max": "1",
    "Min": "0",
    "Multiplier": "",
    "Parameter Group": "Permanent I/O elements",
    "Property ID in AVL packet": "2",
    "Property Name": "Digital Input 2",
    "Type": "Unsigned",
    "Units": ""
  }]
  
Auszug von "tracking":

Code: Alles auswählen

[
  {
    "_id": "64b6624aef973d34f38b8d81",
    "11": "893407150",
    "14": "4460403",
    "16": "72586",
    "21": "4",
    "24": "0",
    "67": "4290",
    "68": "0",
    "69": "2",
    "113": "100",
    "116": "1",
    "181": "0",
    "182": "0",
    "200": "0",
    "205": "56380",
    "240": "0",
    "241": "26203",
    "altitude": "0",
    "angle": "0",
    "imei": "350317178147371",
    "latitude": "0",
    "longitude": "0",
    "priority": "0",
    "satellites": "0",
    "speed": "0",
    "timestamp": "1689674310000"
  },
  {
    "_id": "64b6624aef973d34f38b8d83",
    "11": "893407150",
    "14": "4460403",
    "16": "72586",
    "21": "4",
    "24": "0",
    "67": "4290",
    "68": "0",
    "69": "2",
    "113": "100",
    "116": "1",
    "181": "0",
    "182": "0",
    "200": "0",
    "205": "56380",
    "240": "0",
    "241": "26203",
    "altitude": "0",
    "angle": "0",
    "imei": "350317178147371",
    "latitude": "0",
    "longitude": "0",
    "priority": "0",
    "satellites": "0",
    "speed": "0",
    "timestamp": "1689674308000"
  }]
  

Hierbei gilt, dass die nummerischen Keys in der Sammlung "tracking" durch "Property ID in AVL packet" aus der Sammlung "trackerIDs" definiert ist. Diese Nummerischen Keys aus tracking sollen durch den Wert in "Description" ersetzt werden.


Das Ergebnis aus der Funktion get_data_of_imei() ist:

Code: Alles auswählen

[
  {
    "Active GSM Operator": 26203,
    "Battery Current": 0,
    "Battery Level": 100,
    "Battery Voltage": 4290,
    "GNSS HDOP": 0,
    "GNSS PDOP": 0,
    "GNSS Status": 2,
    "GSM Cell ID": 56380,
    "GSM Signal": 4,
    "ICCID1": 893407150,
    "ICCID2": 4460403,
    "Movement": 0,
    "Sleep Mode": 0,
    "Speed": 0,
    "Total Odometer": 72586,
    "altitude": 0,
    "angle": 0,
    "imei": "{imei}",
    "latitude": 0,
    "longitude": 0,
    "priority": 0,
    "satellites": 0,
    "speed": 0,
    "timestamp": {timestamp},
    "unknown": 1
  }]
  
Ich hoffe, ihr könnt mir eine Aggregatsfunktion geben. Sollte es keine Möglichkeit geben, die Aggregatsfunktion von MongoDB zu nutzen, würde ich mich aber über Optimierungen in meinem Code freuen.

Glückauf aus dem Ruhrgebiet

Tobias

PS.: ChatGPT ist an dieser Aufgabe gescheitert.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Zur Funktion `get_data_of_imei`: man verändert keine Datenstrukturen, sondern erzeugt bei Bedarf neue. Über einen Index iteriert man nicht, und es ist gut so, dass Python eine Fehlermeldung liefert, wenn man das Wörterbuch ändert, über das man gerade iteriert. Das sollte man nicht durch eine Kopie der Keys versuchen zu umgehen.
Alle Keys aller Wörterbücher zu gehen, und dann für jeden numerischen Key immer wieder die Properties abzufragen, ist wirklich sehr langsam. Besser wäre es, erst alle Keys zu sammeln und die Abfrage nur einmal zu machen.
Eine Liste ist "wahr", wenn sie mehr als ein Element enthält, die Prüfung len(tmp) > 0 ist also überflüssig. `tmp` ist ein schlechter Variablenname, weil da ja gar nichts mit Temperaturen gemacht wird, und lokale Variablen sind immer temporär.

Code: Alles auswählen

def translate_dict_keys(data, translation_map):
    return {
        translation_map.get(key, key): value
        for key, value in data.items()
        if translation_map.get(key, key) is not None
    }

def get_data_of_imei(imei: str) -> list:
    # Collection where incoming streams are saved
    db.set_collection("tracking")
    # filter streams by imei
    datasets = db.find_document_by_parameter({"imei": imei})
    all_keys = set.union(*(set(d) for d in datasets))

    # search description of numeric keys 
    db.set_collection("trackerIDs")
    translation_map = {
        '_id': None
    }
    for key in all_keys:
        if key.isdigit():
            properties = db.find_document_by_parameter({"Property ID in AVL packet": int(key)})
            if properties:
                new_key = properties[0]['Property Name']
                translation_map[key] = new_key
    
    transformed_datasets = [
        translate_dict_keys(data, translation_map)
        for data in datasets
    ]
    return transformed_datasets
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Die DB-Klasse an sich ist ja nicht sehr hilfreich. Eigentlich möchte man nicht die Collection erst setzen um dann eine Suche laufen zu lassen. Das ist viel zu fehleranfällig. MongoClient hat doch schon eine gute API, die man auch direkt nutzen kann. Der get_data_of_imei-Funktion fehlt das db-Argument, so dass das ganze dann so aussehen könnte:

Code: Alles auswählen

def translate_dict_keys(data, translation_map):
    return {
        translation_map.get(key, key): value
        for key, value in data.items()
        if translation_map.get(key, key) is not None
    }

def get_data_of_imei(db_client, imei):
    # filter streams by imei
    datasets = list(db_client["tracking"].find({"imei": imei})
    
    numeric_keys = [
        key
        for key in set.union(*(set(d) for d in datasets))
        if key.isdigit()
    ]

    # search description of numeric keys
    properties = db_client["trackerIDs"].find({"Property ID in AVL packet": {'$in': numeric_keys}})
    translation_map = {
        '_id': None,
        **{
            property_["Property ID in AVL packet"]: property_["Property Name"]
            for property_ in properties
        }
    }
    
    return [
        translate_dict_keys(data, translation_map)
        for data in datasets
    ]
Antworten