Кэширование промежуточного стола с psycopg2 - PullRequest
1 голос
/ 03 октября 2019

Возьмите этот блок вызовов psycopg2, который включает два SELECT s:

import psycopg2

with psycopg2.connect("dbname=test user=postgres") as conn:
    with conn.cursor() as cur:
        cur.execute("SELECT a, b, c FROM table WHERE a > 5 and d < 10;")
        r1 = cur.fetchall()
        cur.execute("SELECT a, b, c FROM table WHERE a > 5 and d > 20;")
        r2 = cur.fetchall()

Это немного неэффективно;потенциально O (N) проверка WHERE a > 5 выполняется дважды, когда кажется, что она может быть выполнена только один раз, с подзапросами, выполненными для этого промежуточного результата.

Какой канонический способ сделать это с помощью API psycopg2?

Что-то вроде:

with psycopg2.connect("dbname=test user=postgres") as conn:
    with conn.cursor() as cur:
        cur.execute("SELECT a, b, c FROM table WHERE a > 5")
        # ...
        cur.execute("SELECT a, b, c FROM temp_table WHERE d < 10;")
        r1 = cur.fetchall()
        cur.execute("SELECT a, b, c FROM temp_table WHERE d > 20;")
        r2 = cur.fetchall()

Наилучшее решение для использования литерала "CREATE TEMP TABLE..."?

Я пришел к этому из Django ORM перспектива, где последующие оценки QuerySet повторно используют кэшированные результаты. Есть ли что-либо подобное, предлагаемое API psycopg2?

1 Ответ

3 голосов
/ 05 октября 2019

Вы можете выполнить один запрос и разбить результаты на два списка:

with psycopg2.connect("dbname=test user=postgres") as conn:
    with conn.cursor() as cur:
        cur.execute("SELECT a, b, c, d FROM my_table WHERE a > 5 and (d < 10 or d > 20);")
        rows = cur.fetchall()

r1 = [(i[0], i[1], i[2]) for i in rows if i[3] < 10]
r2 = [(i[0], i[1], i[2]) for i in rows if i[3] > 20]

Приведенное выше решение должно быть наиболее эффективным в тех случаях, когда набор результатов невелик. Кроме того, вы можете создать временную таблицу:

with psycopg2.connect("dbname=test user=postgres") as conn:
    with conn.cursor() as cur:
        cur.execute("""
            CREATE TEMP TABLE t AS
            SELECT a, b, c, d 
            FROM my_table 
            WHERE a > 5 and (d < 10 or d > 20);""")
        cur.execute("SELECT a, b, c FROM t WHERE d < 10;")
        r1 = cur.fetchall()
        cur.execute("SELECT a, b, c FROM t WHERE d > 20;")
        r2 = cur.fetchall()        

Временная таблица будет автоматически удалена при закрытии соединения.

Если результирующий набор слишком велик, чтобы быть практически обработанным на клиентеside, используйте курсор на стороне сервера. Когда вы извлекаете отдельные строки в цикле, строки фактически извлекаются с сервера в сегментах. Вы можете определить размер ковшей, установив itersize. .

r1 = []
r2 = []
with psycopg2.connect("dbname=test user=postgres") as conn:
    with conn.cursor('my_cursor') as cur:
        cur.itersize = 1000
        cur.execute("SELECT a, b, c, d FROM my_table WHERE a < 5 and (d < 10 or d > 20);")
        for row in cur:
            if row[3] < 10:
                r1.append((row[0], row[1], row[2]))
            else:
                r2.append((row[0], row[1], row[2]))
...