Конвертировать запрос бокового объединения в sqlalchemy - PullRequest
0 голосов
/ 30 августа 2018

У меня проблемы с переводом запроса, созданного в sql (postgres), в sqlalchemy. В частности, мои попытки сопоставления в sqlalchemy приводят к абсурдным рекурсивным результатам, которые будут выполняться гораздо медленнее, чем то, что я изначально написал.

Учитывая следующий тип структуры таблицы:

metadata
------------------------------
primary_id      - integer
secondary_count - integer
property        - string  (many to each primary_id)

data
-----------------------------
primary_id      - integer
secondary_id    - integer (many to each primary_id)
primary_json    - json bytes
secondary_json  - json bytes

Я пытаюсь получить пары первичных и вторичных данных с такими данными:

  1. мы сопоставляем данное свойство
  2. мы возвращаем только «некоторые» из первичных данных (скажем, 1000)
  3. мы возвращаем «лучшие» первичные данные, то есть первичные данные с наиболее вторичными данными.
  4. мы получаем только «некоторые» (скажем, 10) вторичные данные на первичную запись

Первое легко выполнить с помощью объединения двух таблиц, однако второе сложнее. Решение, к которому я обращаюсь (см. здесь для объяснения, которое привело меня к этому решению) в необработанном SQL:

SELECT primary_id, primary_json, secondary_json, secondary_count
FROM
  (
    SELECT primary_id, secondary_count
    FROM metadata
    WHERE property='whatever I want'
    -- Get the "best" 1000 results
    ORDER BY secondary_count DESC
    LIMIT 1000
  ) my_primary_ids
 LEFT OUTER JOIN LATERAL
  (
    SELECT primary_json, seondary_json
    FROM data
    WHERE primary_id = my_primary_ids.primary_id
    -- Only return 10 pieces of secondary json per primary json
    LIMIT 10
  ) json_content ON true;

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

Например, код sqlalchemy (при условии, что приведенные ниже определения объектов таблиц соответствуют приведенному выше) является частичным решением. Я думаю, что я могу добавить недостающие столбцы (как вы увидите в сгенерированном sql):

from sqlalchemy import true

my_prim_ids_al = (
    query(Metadata.primary_id.label('primary_id'), 
          Metadata.secondary_count.label('secondary_count'))
    .filter_by(property='whatever I want')
    .order_by(Metadata.secondary_count)
    .limit(1000)
    .from_self()
    .subquery('my_primary_ids')
    )
json_content_al = (
    query(Data.primary_json.label('primary_json'),
          Data.secondary_json.label('secondary_json'))
    .filter_by(primary_id=my_primary_ids_al.c.primary_id)
    .limit(10)
    .from_self()
    .subquery('json_content')
    .lateral()
    )
joined_query = (
    my_primary_ids_al
    .outerjoin(json_content_al, true())
    .subquery('joined_query')
    )

Объединенный запрос в расширенной форме выглядит следующим образом с вышеупомянутой нелепой вложенной структурой:

SELECT anon_1.primary_id, anon_1.secondary_count
FROM
  (
    SELECT metadata.primary_id AS primary_id, 
           metadata.secondary_count AS secondary_count
    FROM metadata
    WHERE metadata.property = 'whatever I want'                                    
    ORDER BY metadata.secondary_count DESC
    LIMIT :param_1
  ) AS anon_1 
LEFT OUTER JOIN LATERAL 
  (
    SELECT anon_4.anon_3_secondary_json AS anon_3_secondary_json, 
           anon_4.anon_3_primary_json AS anon_3_primary_json, 
    FROM 
      (
        SELECT anon_3.secondary_json AS anon_3_secondary_json, 
               anon_3.primary_json AS anon_3_primary_json,
        FROM 
          (
             SELECT data.secondary_json AS secondary_json, 
                    data.primary_json AS primary_json,
             FROM data 
             JOIN
               (
                  SELECT anon_1.primary_id AS primary_id,
                         anon_1.secondary_count AS secondary_count 
                  FROM 
                    (
                      SELECT metadata.primary_id AS primary_id,
                             metadata.secondary_count AS secondary_count
                      FROM metadata
                      WHERE metadata.property = 'whatever I want'
                      ORDER BY metadata.secondary_count DESC
                      LIMIT :param_1
                   ) AS anon_1
                 ) AS primary_ids ON data.primary_id = primary_ides.primary_id
             ) AS anon_3
           LIMIT :param_2) AS anon_4) AS anon_2 ON true

Опять же, я понимаю, что это неполная попытка, поскольку не все столбцы ВЫБРАТЬСЯ в начале, но ключевая проблема заключается в том, что sqlalchemy создает абсурдное количество вложенных запросов в подзапросе бокового соединения. . Это основная проблема, которую мне не удалось решить, и до тех пор, пока она не будет решена, нет смысла завершать оставшуюся часть запроса.

1 Ответ

0 голосов
/ 30 августа 2018

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

В результате при вызове from_self() создается новый Query, который выбирает из предыдущего оператора SELECT Query. Применение subquery() создает из этого подзапрос, давая 2 уровня вложенности. Конечно, этот подзапрос должен использоваться в еще одном запросе, так что будет как минимум 3 уровня вложенности. И когда автокорреляция завершается неудачно и подзапрос включается во второй как есть, вы получаете глубоко вложенный запрос.

...