2013-03-05 4 views
4

У меня есть отношения базы данных с многие-ко-многим ассоциации, но сама таблица ассоциации содержит много атрибутов, которые должны быть доступны, так что я сделал три класса:SQLAlchemy Многие-ко-многим производительности

class User(Base): 
    id = Column(Integer, primary_key=True) 
    attempts = relationship("UserAttempt", backref="user", lazy="subquery") 

class Challenge(Base): 
    id = Column(Integer, primary_key=True) 
    attempts = relationship("UserAttempt", backref="challenge", lazy='subquery') 

class UserAttempt(Base): 
    challenge_id = Column(Integer, ForeignKey('challenge.id'), primary_key=True) 
    user_id = Column(Integer, ForeignKey('user.id'), primary_key=True) 

Это, конечно, упрощенный случай, когда я оставил другие атрибуты, которые мне нужны для доступа. Цель здесь состоит в том, что каждый User может попытаться выполнить любое количество Challenge s, следовательно, таблицу UserAttempt, в которой описывается один конкретный пользователь, работающий с одной задачей.

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

То, что я на самом деле хочу от SQLAlchemy, состоит в том, чтобы вытащить все (или все релевантные) вызовы сразу, а затем связать его с соответствующими попытками. Это не имеет большого значения, если все проблемы вытащить или только сделать, которые имеют фактическую ассоциацию позже, так как это количество проблем составляет только между 100-500.

Мое решение прямо сейчас на самом деле не очень элегантно: я тяну все соответствующие попытки, проблемы и пользователей отдельно, а затем ассоциировать вручную: Петля через все попытки и назначить добавить пользователю вызов &, затем добавить пользователя вызов & в попытка тоже. Это кажется мне жестоким решением, которое не должно быть необходимым.

Однако каждый подход (например, изменение «ленивых» параметров, измененных запросов и т. Д.) Приводит к запросам от сотен до тысяч. Я также пытался писать простые SQL запросы, которые дали бы мои желаемых результатов и придумали что-то вдоль линий SELECT * FROM challenge WHERE id IN (SELECT challenge_id FROM attempts) и работали хорошо, но я не могу получить его перевод на SQLAlchemy

Большое спасибо заранее любые рекомендации, которые вы можете предложить.

ответ

10

Что я на самом деле хочу от SQLAlchemy - это вытащить все (или все соответствующие) вызовы сразу, а затем связать их с соответствующими попытками. Это не имеет большое значение, если все проблемы тянут или только не имеет который фактическое объединение позже,

Вы первый хотите снять, что «ленивый =" подзапросы»директива от отношений() первым; фиксируя отношения, чтобы всегда загружать все, почему вы получаете взрыв запросов. В частности, здесь вы получаете эту задачу -> пытается загружать именно для каждой lazyload UserAttempt-> Challenge, поэтому вы можете составить самую худшую возможную комбинацию загрузки здесь :).

С учетом этого существует два подхода.

Следует помнить, что ассоциация «один к одному» в обычном случае извлекается из сеанса в память сначала первичным ключом, а если присутствует, SQL не испускается.Так что я думаю, что вы могли бы получить именно эффект, кажется, как вы описываете, используя технику, которую я часто использую:

all_challenges = session.query(Challenge).all() 

for user in some_users: # however you got these 
    for attempt in user.attempts: # however you got these 
     do_something_with(attempt.challenge) # no SQL will be emitted 

Если вы хотите использовать вышеуказанный подход с точно «Select * от вызова, где идентификатор в (выберите challenge_id от попытки) ":

all_challenges = session.query(Challenge).\ 
        filter(Challenge.id.in_(session.query(UserAttempt.challenge_id))).all() 

, хотя это, вероятно, более эффективным, как JOIN:

all_challenges = session.query(Challenge).\ 
        join(Challenge.attempts).all() 

или DISTINCT, я предполагаю, что присоединиться к вернёт же challenge.i d, как указано в UserAttempt:

all_challenges = session.query(Challenge).distinct().\ 
        join(Challenge.attempts).all() 

Другой способ - более оперативно загружать загрузку. Вы можете запросить кучу пользователей/попыток/задачи в рамках одного запроса, который будет испускать три ЗЕЬЕСТА:

users = session.query(User).\ 
       options(subqueryload_all(User.attempts, UserAttempt.challenge)).all() 

или потому UserAttempt-> Вызов много-к-одному, объединение может быть лучше:

users = session.query(User).\ 
        options(subqueryload(User.attempts), joinedload(UserAttempt.challenge)).all() 

только из UserAttempt:

attempts = session.query(UserAttempt).\ 
        options(joinedload(UserAttempt.challenge)).all() 
Смежные вопросы