Объединить несколько отношений «один ко многим» в json — oracle — python

#python #sql #oracle #cx-oracle

#python #sql #Oracle #cx-oracle

Вопрос:

(Я знаю, что это можно сделать с помощью SQL Alchemy, но это также упражнение в обучении, поэтому я понимаю, как это делается без таких инструментов, как SQL Alchemy) Моя компания использует Oracle DB 10.1.

Структура БД Первичная таблица (первичный ключ) Вторая таблица (внешний ключ с первичным ключом) Третья таблица (внешний ключ с первичным ключом)

Вторая таблица может содержать от 0 до 4 связанных записей, третья таблица может иметь 0 — бесконечное число (хотя вряд ли их будет больше 20)

Мой SQL-запрос работает нормально и отлично передает данные. Если мы получим несколько записей в других таблицах, это просто вернет несколько строк с одним и тем же первичным ключом. Кажется стандартным (новым для SQL, поэтому я предполагаю, что так и должно быть).

Проблема заключается в том, когда я пытаюсь обработать данные в единственную запись JSON, перебирающую все. Вот что у меня есть в настоящее время в качестве моего сценария:

 cursor = conn.cursor()

    sql = """
        SELECT *
        FROM RC_CAL 
               join CALENDAR_EVENT 
                 ON RC_CAL.rc_id = CALENDAR_EVENT.rc_id
               left outer join CALENDAR_EVENT CALENDAR_EVENT_1 
                            ON RC_CAL.rc_id = CALENDAR_EVENT_1.rc_id 
               left outer join RC_HISTORY RC_HISTORY_1 
                            ON RC_CAL.rc_id = RC_HISTORY_1.rc_id
    """

    has_where = False
    params = {}
    query_conditions = []
    if is_active:
        has_where = True
        query_conditions.append('WHERE CALENDAR_EVENT.ACTIVE_YN = :ACTIVE')
        params['ACTIVE'] = is_active
    if env:
        env_string = 'AND CALENDAR_EVENT.env = :ENV' if has_where else 'WHERE CALENDAR_EVENT.env = :ENV'
        has_where = True
        query_conditions.append(env_string)
        params['ENV'] = env
    if from_date:
        from_string = 'AND CALENDAR_EVENT.end_dt >= :FROM_DATE' if has_where else 'WHERE CALENDAR_EVENT.end_dt >= :FROM_DATE'
        has_where = True
        query_conditions.append(from_string)
        params['FROM_DATE'] = from_date
    if to_date:
        to_string = 'AND CALENDAR_EVENT.end_dt <= :TO_DATE' if has_where else 'WHERE CALENDAR_EVENT.end_dt <= :TO_DATE'
        query_conditions.append(to_string)
        params['TO_DATE'] = to_date

    if len(query_conditions) > 0:
        sql  = " ".join(query_conditions)

    cursor.execute(sql, **params)

    rows = cursor.fetchall()

    events = []
    for rcId, data in groupby(rows, key=lambda t: t[0]):
        rc_item = None
        generic_list = []
        history_list = []
        for item in data:
            if not rc_item:
                rc_item = EventModalV2(*item[0:11])
            generic_id = item[11]
            history_id = item[17]
            generic_data = item[11:17]
            history_data = item[17:]
            if not any(generic_id in generic_item for generic_item in generic_list) and generic_id:
                rc_item.update_with_generic(generic_data)
                generic_list.append(generic_data)
            if not any(history_id in history_item for history_item in history_list) and history_id:
                rc_item.update_with_history(history_data)
                history_list.append(history_data)
                
        events.append(rc_item)

    cursor.close()
    conn.close()

    return events
  

пример набора данных, если у меня было несколько записей в таблице 3, хотя их могло быть больше, если в таблице 2 указано больше записей (где указано PROD, что является частью таблицы 2. Это таблица, в которой мы храним даты в зависимости от продукта среды:

 (170079, 3, 1, None, None, None, 'Test Demo Event - DS', 'N', 'N', None, datetime.datetime(2020, 11, 5, 19, 16, 22), 237247, datetime.datetime(2020, 11, 1, 4, 0), 'PROD', datetime.datetime(2020, 11, 1, 4, 0), 91, 'Y', 5051, <cx_Orac
le.LOB object at 0x00000237AE8954B8>, 'rc_create', datetime.datetime(2020, 11, 5, 19, 16, 22))


(170079, 3, 1, None, None, None, 'Test Demo Event - DS', 'N', 'N', None, datetime.datetime(2020, 11, 5, 19, 16, 22), 237247, datetime.datetime(2020, 11, 1, 4, 0), 'PROD', datetime.datetime(2020, 11, 1, 4, 0), 91, 'Y', 5052, <cx_Orac
le.LOB object at 0x00000237AE895580>, 'rc_patch', datetime.datetime(2020, 11, 5, 19, 17, 7))


(170079, 3, 1, None, None, None, 'Test Demo Event - DS', 'N', 'N', None, datetime.datetime(2020, 11, 5, 19, 16, 22), 237247, datetime.datetime(2020, 11, 1, 4, 0), 'PROD', datetime.datetime(2020, 11, 1, 4, 0), 91, 'Y', 5053, <cx_Orac
le.LOB object at 0x00000237AE895648>, 'rc_patch', datetime.datetime(2020, 11, 5, 19, 17, 59))


(170079, 3, 1, None, None, None, 'Test Demo Event - DS', 'N', 'N', None, datetime.datetime(2020, 11, 5, 19, 16, 22), 237247, datetime.datetime(2020, 11, 1, 4, 0), 'PROD', datetime.datetime(2020, 11, 1, 4, 0), 91, 'Y', 5054, <cx_Orac
le.LOB object at 0x00000237AE895710>, 'rc_patch', datetime.datetime(2020, 11, 5, 19, 19, 9))

  

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

Комментарии:

1. Добавить инструментарий, чтобы показать, что происходит медленно? Просмотрите план выполнения SQL?

Ответ №1:

Во-первых, как предложил Крис, выясните, где происходит замедление. В зависимости от того, что вы найдете, у меня есть пара предложений:

(1) Избегайте извлечения значений LOB, поскольку для извлечения данных требуется дополнительный цикл туда и обратно, что замедляет процесс. Вы можете увидеть пример здесь.

(2) Если предыдущего предложения недостаточно, вы также можете рассмотреть возможность простой выборки трех (четырех?) запрашивает независимо и объединяет их в Python (с помощью dicts). Это предотвращает необходимость многократной выборки строк верхнего уровня с последующим удалением дубликатов.