Запросите Sqlite для нескольких аргументов одновременно и обработки пропущенных значений - PullRequest
4 голосов
/ 06 января 2012

Есть ли возможность сделать что-то подобное внутри SQL-запроса? Может быть, предоставить список в качестве входного аргумента? Даты, которые я хочу, являются последовательными, но не все даты существуют в базе данных. Если дата не существует, результатом должно быть «Нет».

dates = [dt.datetime(2008,1,1), dt.datetime(2008,1,2), dt.datetime(2008,1,3), dt.datetime(2008,1,4), dt.datetime(2008,1,5)]
id = "361-442"
result = []
for date in dates:
    curs.execute('''SELECT price, date FROM prices where date = ? AND id = ?''', (date, id))
    query = curs.fetchall()
    if  query == []:
        result.append([None, arg])
    else:
        result.append(query)

1 Ответ

5 голосов
/ 06 января 2012

Чтобы выполнить всю работу в sqlite , вы можете использовать ЛЕВОЕ СОЕДИНЕНИЕ, чтобы заполнить недостающие цены с помощью None:

sql='''
SELECT p.price, t.date
FROM ( {t} ) t
LEFT JOIN price p
ON p.date = t.date
WHERE p.id = ?
'''.format(t=' UNION ALL '.join('SELECT {d!r} date'.format(d=d) for d in date))

cursor.execute(sql,[id])
result=cursor.fetchall()

Однако это решение требует формирования (потенциально) огромная строка в Python для создания временной таблицы всех желаемых дат.Это не только медленно (включая время, необходимое sqlite для создания временной таблицы), но и хрупко: если len(date) больше, чем около 500, то sqlite поднимает

OperationalError: too many terms in compound SELECT

Вы можетеобойти это, если у вас уже есть все нужные даты в другой таблице.Тогда вы могли бы заменить уродливый SQL «UNION ALL» выше на что-то вроде

SELECT p.price, t.date
FROM ( SELECT date from dates ) t
LEFT JOIN price p
ON p.date = t.date

Хотя это улучшение, мои тесты timeit (см. Ниже) показывают, что выполнение части работы в Python все еще быстрее:


Выполнение части работы в Python :

Если вы знаете, что даты являются последовательными и поэтому могут быть выражены как диапазон, то:

curs.execute('''
    SELECT date, price
    FROM prices
    WHERE date <= ?
        AND date >= ?
        AND id = ?''', (max(date), min(date), id))

В противном случае, если даты являются произвольными, то:

sql = '''
    SELECT date, price
    FROM prices
    WHERE date IN ({s})
        AND id = ?'''.format(s={','.join(['?']*len(dates))})
curs.execute(sql,dates + [id])

Чтобы сформировать список result с добавлением None для отсутствующих цен, вы можете сформировать dict из (date,price) пар и используйте метод dict.get() для предоставления значения по умолчанию None, если отсутствует клавиша date:

result = dict(curs.fetchall())
result = [(result.get(d,None), d) for d in date]

Примечание для формирования dict в видесопоставляя даты с ценами, я поменял местами порядок date и price в запросах SQL.


Тесты времени :

Я сравнил этитри функции:

def using_sqlite_union():
    sql = '''
        SELECT p.price, t.date
        FROM ( {t} ) t
        LEFT JOIN price p
        ON p.date = t.date
    '''.format(t = ' UNION ALL '.join('SELECT {d!r} date'.format(d = str(d))
                                      for d in dates))
    cursor.execute(sql)
    return cursor.fetchall()

def using_sqlite_dates():
    sql = '''
        SELECT p.price, t.date
        FROM ( SELECT date from dates ) t
        LEFT JOIN price p
        ON p.date = t.date
    '''
    cursor.execute(sql)
    return cursor.fetchall()

def using_python_dict():
    cursor.execute('''
        SELECT date, price
        FROM price
        WHERE date <= ?
            AND date >= ?
            ''', (max(dates), min(dates)))

    result = dict(cursor.fetchall())
    result = [(result.get(d,None), d) for d in dates]
    return result

N = 500
m = 10
omit = random.sample(range(N), m)
dates = [ datetime.date(2000, 1, 1)+datetime.timedelta(days = i) for i in range(N) ]
rows = [ (d, random.random()) for i, d in enumerate(dates) if i not in omit ]

rows определяют данные, которые были вставлены в таблицу price.


Результаты тестов по времени :

Время выполнения:

python -mtimeit -s'import timeit_sqlite_union as t' 't.using_python_dict()'

дает следующие тесты:

·────────────────────·────────────────────·
│  using_python_dict │ 1.47 msec per loop │
│ using_sqlite_dates │ 3.39 msec per loop │
│ using_sqlite_union │ 5.69 msec per loop │
·────────────────────·────────────────────·

using_python_dict isпримерно в 2,3 раза быстрее, чем using_sqlite_dates.Даже если мы увеличим общее число дат до 10000, соотношение скоростей останется прежним:

·────────────────────·────────────────────·
│  using_python_dict │ 32.5 msec per loop │
│ using_sqlite_dates │ 81.5 msec per loop │
·────────────────────·────────────────────·

Вывод: перенести всю работу в sqlite не обязательно быстрее.

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