Чтобы выполнить всю работу в 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 не обязательно быстрее.