Question

J'ai une séquence d'identifiants que je veux récupérer. C'est simple:

session.query(Record).filter(Record.id.in_(seq)).all()

Y a-t-il une meilleure façon de le faire?

Était-ce utile?

La solution

Votre code est parfait.

IN est comme un groupe de X = Y joint à OU et est assez rapide dans les bases de données contemporaines.

Toutefois, si votre liste d'identifiants est longue, vous pouvez rendre la requête un peu plus efficace en passant une sous-requête renvoyant la liste d'identifiants.

Autres conseils

Le code tel quel est tout à fait correct. Cependant, quelqu'un me demande un système de couverture entre les deux approches consistant à effectuer un grand RI contre l'utilisation de get () pour des ID individuels.

Si quelqu'un essaie vraiment d'éviter le SELECT, le meilleur moyen de le faire est de configurer les objets dont vous avez besoin en mémoire à l'avance. Vous travaillez par exemple sur une grande table d’éléments. Découpez le travail en morceaux, tels que, commandez l'ensemble du travail par clé primaire ou par plage de dates, peu importe, puis chargez tout le contenu de ce morceau localement dans un cache:

 all_ids = [<huge list of ids>]

 all_ids.sort()
 while all_ids:
     chunk = all_ids[0:1000]

     # bonus exercise!  Throw each chunk into a multiprocessing.pool()!
     all_ids = all_ids[1000:]

     my_cache = dict(
           Session.query(Record.id, Record).filter(
                 Record.id.between(chunk[0], chunk[-1]))
     )

     for id_ in chunk:
         my_obj = my_cache[id_]
         <work on my_obj>

C'est le cas d'utilisation dans le monde réel.

Mais pour illustrer également certaines API SQLAlchemy, nous pouvons créer une fonction qui effectue le IN pour les enregistrements que nous n’avons pas et un get local pour ceux que nous avons. Voici ce que:

from sqlalchemy import inspect


def get_all(session, cls, seq):
    mapper = inspect(cls)
    lookup = set()
    for ident in seq:
        key = mapper.identity_key_from_primary_key((ident, ))
        if key in session.identity_map:
            yield session.identity_map[key]
        else:
            lookup.add(ident)
    if lookup:
        for obj in session.query(cls).filter(cls.id.in_(lookup)):
            yield obj

Voici une démonstration:

from sqlalchemy import Column, Integer, create_engine, String
from sqlalchemy.orm import Session
from sqlalchemy.ext.declarative import declarative_base
import random

Base = declarative_base()


class A(Base):
    __tablename__ = 'a'
    id = Column(Integer, primary_key=True)
    data = Column(String)

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

ids = range(1, 50)

s = Session(e)
s.add_all([A(id=i, data='a%d' % i) for i in ids])
s.commit()
s.close()

already_loaded = s.query(A).filter(A.id.in_(random.sample(ids, 10))).all()

assert len(s.identity_map) == 10

to_load = set(random.sample(ids, 25))
all_ = list(get_all(s, A, to_load))

assert set(x.id for x in all_) == to_load

Si vous utilisez des clés primaires composites, vous pouvez utiliser le tuple _ , comme dans

.
from sqlalchemy import tuple_
session.query(Record).filter(tuple_(Record.id1, Record.id2).in_(seq)).all()

Notez que ceci n'est pas disponible sur SQLite (voir doc ).

Je vous conseillerais de jeter un coup d'œil au code SQL produit. Vous pouvez simplement imprimer str (requête) pour le voir.

Je ne connais pas de méthode idéale pour le faire avec du SQL standard.

Il existe un autre moyen. S'il est raisonnable de s'attendre à ce que les objets en question soient déjà chargés dans la session; vous y avez déjà accédé dans la même transaction, vous pouvez plutôt faire:

map(session.query(Record).get, seq)

Dans le cas où ces objets sont déjà présents, cela sera beaucoup plus rapide, car il n'y aura pas de requête pour récupérer ces objets; D'autre part, si plus d'un petit nombre de ces objets ne sont pas chargés, le processus sera beaucoup, beaucoup plus lent, car cela entraînera une requête par instance manquante, au lieu d'une requête unique pour tous. objets.

Cela peut être utile lorsque vous effectuez des requêtes joinload () avant d'atteindre l'étape ci-dessus. Vous pouvez ainsi être sûr qu'elles ont déjà été chargées. En général, vous devriez utiliser la solution dans la question par défaut et n'explorer cette solution que lorsque vous avez constaté que vous interrogiez plusieurs fois les mêmes objets.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top