Как ускорить импорт файла Excel, содержащего более 5000 строк, в базу данных sqlite с помощью django - PullRequest
0 голосов
/ 16 октября 2019

Импорт файла xls (более 5000 строк) в мою базу данных sqlite занимает много времени.

def importeradsl(request):
if "GET" == request.method:
    else:
        excel_file = request.FILES["excel_file"]
        #you may put validations here to check extension or file size
        wb = openpyxl.load_workbook(excel_file)
        #getting a particular sheet by name out of many sheets
        worksheet = wb["Sheet 1"]
        #iterating over the rows and getting value from each cell in row
        for row in worksheet.iter_rows(min_row=2):
            row_data = list()
            for cell in row:
                row_data.append(str(cell.value))
            #Get content fields DerangementCuivre models
            #Client
            nd = row_data[0]
            nom_client = row_data[3]
            nd_contact = row_data[4]
            #Categorie
            code_categorie = row_data[6]
            acces_reseau = row_data[8]
            etat = row_data[9]
            origine = row_data[10]
            code_sig = row_data[11]
            agent_sig = row_data[13]
            date_sig = dt.datetime.strftime(parse(row_data[14]), '%Y-%m-%d %H:%M:%S')
            date_essai = dt.datetime.strftime(parse(row_data[15]), '%Y-%m-%d %H:%M:%S')
            agent_essai = row_data[18]
            try:
                date_ori = dt.datetime.strptime(row_data[19], '%Y-%m-%d %H:%M:%S')
            except ValueError as e:
                print ("Vous", e)
            else:
                date_ori = dt.datetime.strftime(parse(row_data[19]), '%Y-%m-%d %H:%M:%S')
            agent_ori = row_data[20]
            code_ui = row_data[21]
            equipe = row_data[22]
            sous_traitant = row_data[23]
            date_pla = dt.datetime.strftime(parse(row_data[24]), '%Y-%m-%d %H:%M:%S')
            date_rel = dt.datetime.strftime(parse(row_data[25]), '%Y-%m-%d %H:%M:%S')
            date_releve = dt.datetime.strptime(row_data[25], '%Y-%m-%d %H:%M:%S')
            date_essais = dt.datetime.strptime(row_data[15], '%Y-%m-%d %H:%M:%S')
            pst = pytz.timezone('Africa/Dakar')
            date_releve = pst.localize(date_releve)
            utc = pytz.UTC
            date_releve = date_releve.astimezone(utc)
            date_essais = pst.localize(date_essais)
            date_essais = date_essais.astimezone(utc)
            code_rel = row_data[26]
            localisation = row_data[27]
            cause = row_data[28]
            commentaire = row_data[29]
            agent_releve = row_data[30]
            centre_racc = row_data[32]
            rep = row_data[33]
            srp = row_data[34]
            delai = (date_releve - date_essais).total_seconds()
            dali = divmod(delai, 86400)[0]
            semaine = date_releve.isocalendar()[1]
            mois = date_releve.month
            annee = date_releve.year
            if dali > 7:
                etats = "PEX PLUS"
            else:
                etats = "PEX"
            #Enregistrer un client
            Client(nd=nd, nom=nom_client, mobile=nd_contact).save()
            #Enregistrer la categorie
            #Code pour nom categorie - renseigner plus tard
            Categorie(code_categorie=code_categorie, nom="Public").save()
            #Enregistrer agent de signalisation
            AgentSig(matricule=agent_sig, nom="Awa").save()
            #Enregistrer agent d'essai
            AgentEssai(matricule=agent_essai).save()
            #Enregister agent d'orientation
            AgentOri(matricule=agent_ori).save()
            #Enregistrer agent de relève
            AgentRel(matricule=agent_releve).save()
            #Enregistrer le sous-traitant
            SousTraitant(nom=sous_traitant).save()
            #Enregistrer le centre
            Centre(code=centre_racc).save()
            #Enregistrer ui
            UniteIntervention(code_ui=code_ui, 
            sous_traitant=SousTraitant.objects.get(nom=sous_traitant)).save()
            #Enregistrer le repartiteur
            Repartiteur(code=rep, crac=Centre.objects.get(code=centre_racc)).save()
            #Enregistrer team
            Equipe(nom=equipe, unite=UniteIntervention.objects.get(code_ui=code_ui)).save()
            #Enregistrer le SR
            SousRepartiteur(code=srp, rep=Repartiteur.objects.get(code=rep)).save()
            #Enregistrer le drangement
            DerangementAdsl(acces_reseau=acces_reseau,
                            nd_client=Client.objects.get(nd=nd),
                            categorie=Categorie(code_categorie=code_categorie),
                            etat=etat,
                            origine=origine,
                            code_sig=code_sig,
                            agent_sig=AgentSig.objects.get(matricule=agent_sig),
                            date_sig=date_sig,
                            date_essai=date_essai,
                            agent_essai=AgentEssai.objects.get(matricule=agent_essai),
                            date_ori=date_ori,
                            agent_ori=AgentOri.objects.get(matricule=agent_ori),
                            sous_traitant=SousTraitant.objects.get(nom=sous_traitant),
                            unite_int = UniteIntervention.objects.get(code_ui=code_ui),
                            date_pla=date_pla,
                            date_rel=date_rel,
                            code_rel=code_rel,
                            code_local=localisation,
                            cause=cause,
                            comment_cause=commentaire,
                            agent_rel=AgentRel.objects.get(matricule=agent_releve),
                            centre=Centre.objects.get(code=centre_racc),
                            rep=Repartiteur.objects.get(code=rep),
                            srep=SousRepartiteur.objects.get(code=srp),
                            delai=dali,
                            etat_vr=etats,
                            semaine=semaine,
                            mois=mois,
                            annee=annee).save()

1 Ответ

1 голос
/ 16 октября 2019

Есть несколько вещей, которые неверны. Я предлагаю вам следующий подход:

  1. Сделайте ваш код более читабельным
  2. Удалите бесполезные запросы
  3. Избегайте дублирования связанных записей
  4. Кэшируйте вашисвязанные экземпляры.
  5. Использование bulk_create

Глядя на ваш код, с приблизительной оценкой, для каждой записи CSV вы получите более 30 запросов SQL на строку,это немного много ...

1. Сделайте ваш код более читабельным.

Ваша логика синтаксического анализа может быть СУХОЙ, много.

Во-первых, определите, что вы делаете со своими данными. С моей точки зрения, 2 основные функции:

Ничего не делать:

def no_transformation(value)
    return str(value)

Даты разбора

def strptime(value):
    """
    I can't really tell what your 'parse' function does, I let it be but it might 
    be interesting adding your logic in here
    """
    return dt.datetime.strptime(parse(str(value)), '%Y-%m-%d %H:%M:%S')

Теперь вы можете объявить свою конфигурацию парсера:

PARSER_CONFIG=(
    #(column_index, variable_name, transformation_function)
    (0,'nd',no_transformation),
    (10,'origine',no_transformation),
    (11,'code_sig',no_transformation),
    (13,'agent_sig',no_transformation),
    (14,'date_sig',strptime),
    (15,'date_essai',strptime),
    (18,'agent_essai',no_transformation),
    (19,'date_ori',strptime),
    (20,'agent_ori',no_transformation),
    (21,'code_ui',no_transformation),
    (22,'equipe',no_transformation),
    (23,'sous_traitant',no_transformation),
    (24,'date_pla',strptime),
    (25,'date_rel',strptime),
    (26,'code_rel',no_transformation),
    (27,'localisation',no_transformation),
    (28,'cause',no_transformation),
    (29,'commentaire',no_transformation),
    (3,'nom_client',no_transformation),
    (30,'agent_releve',no_transformation),
    (32,'centre_racc',no_transformation),
    (33,'rep',no_transformation),
    (34,'srp',no_transformation),
    (4,'nd_contact',no_transformation),
    (6,'code_categorie',no_transformation),
    (8,'acces_reseau',no_transformation),
    (9,'etat',no_transformation),
    (15',date_essais',strptime),
    (19',date_ori',strptime),
    (25',date_releve',strptime),
)

Теперь вы знаете, как анализировать ваши данные и как их называть. Давай просто поместим эти вещи в диктат.

def parse(row):
    """Transform a row into a dict

    Args:
        row (tuple): Your row's data

    Returns:
        dict: Your parsed data, named into a dict.
    """
    return {
        key:tranfsorm(row[index]) for index, key, transform in PARSER_CONFIG
    }

Отсюда твой парсер способ более читабелен, ты точно знаешь, что ты делаешь со своими данными.

Завершая все это вместе, вы должны получить:

PARSER_CONFIG=(
    #(column_index, variable_name, transformation_function)
    #...
)
def no_transformation(value)
    return str(value)

def strptime(value)
    return str(value)

def parse(row):
    """Transform a row into a dict

    Args:
        row (tuple): Your row's data

    Returns:
        dict: Your parsed data, named into a dict.
    """
    return {
        key:tranfsorm(row[index]) for index, key, transform in PARSER_CONFIG
    }

for row in rows:
    item = parse(row) #< Your data, without related instances yet....

Еще есть немного работы для создания связанных экземпляров, но мы доберемся до конца.

2. Удаление бесполезных запросов.

Вы делаете:

#...First, your create a record
Client(nd=nd, nom=nom_client, mobile=nd_contact).save()
#... Then you fetch it when saving DerangementAdsl
nd_client=Client.objects.get(nd=nd)

В то время как более питонский способ сделать это будет:

#... You create and assign your istance.
client = Client(nd=item.get('nd'), 
                nom=item.get('nom_client'), 
                mobile=item.get('nd_contact')).save()
#...
nd_client=client

Вы только что заработали один запрос / строку SQL! Выполните ту же логику для каждой модели, и вы получите около 20 запросов в строке!

categorie=Categorie.objects.create(code_categorie=item.get('code_categorie'), nom="Public"),
#Enregistrer agent de signalisation
agent_sig=AgentSig.objects.create(matricule=item.get('agent_sig'), nom="Awa"),
#Enregistrer agent d'essai
agent_essai=AgentEssai.objects.create(matricule=item.get('agent_essai')),
#Enregister agent d'orientation
agent_ori=AgentOri.objects.create(matricule=item.get('agent_ori')),
#Enregistrer agent de relève
agent_rel=AgentRel.objects.create(matricule=item.get('agent_releve')),
#Enregistrer le sous-traitant
sous_traitant=SousTraitant.objects.create(nom=item.get('sous_traitant')),
#Enregistrer le centre
centre=Centre.objects.create(code=item.get('centre_racc')),
#Enregistrer ui
unite_int=UniteIntervention.objects.create(code_ui=item.get('code_ui'), sous_traitant=sous_traitant), # < You earn one extrat query with sous_traitant
#Enregistrer le repartiteur
rep=Repartiteur.objects.create(code=item.get('rep'), crac=centre), # < You earn one extrat query with centre
#Enregistrer team
equipe=Equipe.objects.create(nom=item.get('equipe')), unite=unite_int),# < You earn one extrat query with unite_int
#Enregistrer le SR
srep=SousRepartiteur.objects.create(code=item.get('srp'), rep=rep),# < You earn one extrat query with rep

3. Избегайте дублирования связанных записей

Теперь есть одна большая проблема:

Учитывая, что у вас есть несколько строк для каждого client, вы в конечном итоге окажетесь со многими дубликатами, и вы этого не хотите,Вместо использования create вы должны использовать get_or_create .

Обратите внимание, что возвращается кортеж: (экземпляр, создан) Итак ... ваш код должен выглядеть так:

categorie, categorie_created=Categorie.objects.get_or_create(code_categorie=item.get('code_categorie'), nom="Public"),
agent_sig, agent_sig_created=AgentSig.objects.get_or_create(matricule=item.get('agent_sig'), nom="Awa"),
agent_essai, agent_essai_created=AgentEssai.objects.get_or_create(matricule=item.get('agent_essai')),
agent_ori, agent_ori_created=AgentOri.objects.get_or_create(matricule=item.get('agent_ori')),
agent_rel, agent_rel_created=AgentRel.objects.get_or_create(matricule=item.get('agent_releve')),
sous_traitant, sous_traitant_created=SousTraitant.objects.get_or_create(nom=item.get('sous_traitant')),
centre, centre_created=Centre.objects.get_or_create(code=item.get('centre_racc')),
unite_int, unite_int_created=UniteIntervention.objects.get_or_create(code_ui=item.get('code_ui'), sous_traitant=sous_traitant)
rep, rep_created=Repartiteur.objects.get_or_create(code=item.get('rep'), crac=centre)
equipe, equipe_created=Equipe.objects.get_or_create(nom=item.get('equipe')), unite=unite_int
srep, srep_created=SousRepartiteur.objects.get_or_create(code=item.get('srp'), rep=rep)

Тадааааам, вы будете создавать записи, которые "необходимы" только для ваших связанных объектов.

4. Кэширование ваших связанных объектов.

Как и в предыдущем разделе, я считаю, что у вас есть несколько строк для каждого связанного экземпляра, и для каждой строки вы все равно будете получать их из своей БД.

Это нормально, я полагаю, если вы используете SQLite в памяти, он не будет таким медленным, как с другими БД, тем не менее, это будет узким местом. Вы могли бы использовать такой подход, как:

MODEL_CACHE = {}
def get_related_instance(model, **kwargs):
    key = (model,kwargs)
    if key in MODEL_CACHE:
        return instance MODEL_CACHE[key]
    else:
        instance, create = model.objects.get_or_create(**kwargs)
        MODEL_CACH[key]=instance
    return instance

# Instead of having previous lines now you end up with:
categorie = get_related_instance(Categorie,code_categorie=item.get('code_categorie'), nom="Public"),
agent_sig = get_related_instance(AgentSig,matricule=item.get('agent_sig'), nom="Awa"),
agent_essai = get_related_instance(AgentEssai,matricule=item.get('agent_essai')),
agent_ori = get_related_instance(AgentOri,matricule=item.get('agent_ori')),
agent_rel = get_related_instance(AgentRel,matricule=item.get('agent_releve')),
sous_traitant = get_related_instance(SousTraitant,nom=item.get('sous_traitant')),
centre = get_related_instance(Centre,code=item.get('centre_racc')),
unite_int = get_related_instance(UniteIntervention,code_ui=item.get('code_ui'), sous_traitant=sous_traitant)
rep = get_related_instance(Repartiteur,code=item.get('rep'), crac=centre)
equipe = get_related_instance(Equipe,nom=item.get('equipe')), unite=unite_int
srep = get_related_instance(SousRepartiteur,code=item.get('srp'), rep=rep)

Я не могу сказать, сколько вы получите благодаря этому, это действительно зависит от набора данных, который вы пытаетесь импортировать, но из опыта, это довольно радикально!

5 Использовать bulk_create

Вы выполняете

for row in rows:
    DerangementAdsl(...your data...).save() #<That's one DB call

Это один запрос SQL на строку, в то время как вы можете выполнить:

ITEMS = []
for row in rows:
    #...Your parsing we saw previously...
    ITEMS.append(DerangementAdsl(**item))
DerangementAdsl.objects.bulk_create(ITEMS) #<That's one DB call

Собираем все вместе!

PARSER_CONFIG=(
    #(column_index, variable_name, transformation_function)
    #...
)
def no_transformation(value)
    return str(value)

def strptime(value)
    return str(value)

MODEL_CACHE = {}

def get_related_instance(model, **kwargs):
    key = (mode,kwargs)
    if key in MODEL_CACHE:
        return instance MODEL_CACHE[key]
    else:
        instance, create = model.objects.get_or_create(**kwargs)
        MODEL_CACH[key]=instance
    return instance

def parse(row):
    """Transform a row into a dict

    Args:
        row (tuple): Your row's data

    Returns:
        dict: Your parsed data, named into a dict.
    """
    item= {
        key:tranfsorm(row[index]) for index, key, transform in PARSER_CONFIG
    }
    item.update({
        'categorie': get_related_instance(Categorie,code_categorie=item.get('code_categorie'), nom="Public"),
        'agent_sig': get_related_instance(AgentSig,matricule=item.get('agent_sig'), nom="Awa"),
        'agent_essai': get_related_instance(AgentEssai,matricule=item.get('agent_essai')),
        'agent_ori': get_related_instance(AgentOri,matricule=item.get('agent_ori')),
        'agent_rel': get_related_instance(AgentRel,matricule=item.get('agent_releve')),
        'sous_traitant': get_related_instance(SousTraitant,nom=item.get('sous_traitant')),
        'centre': get_related_instance(Centre,code=item.get('centre_racc')),
        'unite_int': get_related_instance(UniteIntervention,code_ui=item.get('code_ui'), sous_traitant=sous_traitant)
        'rep': get_related_instance(Repartiteur,code=item.get('rep'), crac=centre)
        'equipe': get_related_instance(Equipe,nom=item.get('equipe')), unite=unite_int
        'srep': get_related_instance(SousRepartiteur,code=item.get('srp'), rep=rep)
    })
    return item

def importeradsl(request):
    #I skip your conditions for readility
    ITEMS = []
    for row in worksheet.iter_rows(min_row=2):
        ITEMS.append(DerangementAdsl(**parse(row)))

    DerangementAdsl.objects.bulk_create(ITEMS)

Заключение

Следуя этим рекомендациям, вы должны получить оптимизированный скрипт, который будет работать на быстрее, чемисходный, и он будет способ больше читабельный и питонический

Грубо, в зависимости от вашего набора данных, линии 5k должны работать от 10 секунд до нескольких минут.

Если связанный экземпляр каждой строки (client, category ...) уникален, я бы использовал более сложный подход, многократно повторяющийся по вашему набору данных, для создания связанных моделей, используя bulk_create и кешируем их как:

CLIENTS = []
for row in rows:
    CLIENTS.append(Client(**client_parser(row)))
clients=Client.objects.bulk_create(CLIENTS) # You Create *all* your client with only one DB call!

Затем выкэшировать все созданные клиенты. Вы делаете то же самое для всех связанных с вами моделей, и в конечном итоге вы загружаете свои данные, делая дюжину вызовов БД, но это действительно зависит от вашей бизнес-логики: она должна быть спроектирована и для обработки дублированных записей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...