2012-02-04 8 views
6

Tener una lista de 3-tuplas:Como llegar filas que coinciden con una lista de condiciones 3-tuplas con SQLAlchemy

[(a, b, c), (d, e, f)] 

Quiero recuperar todas las filas de una tabla en la que 3 columnas coincide con las tuplas. Para este ejemplo, la cláusula de consulta WHERE podría ser algo como esto:

(column_X = a AND column_Y = b AND column_Z = c) 
OR (column_X = d AND column_Y = e AND column_Z = f) 

¿Cómo puedo crear una solicitud de este tipo usando SQLAlchemy? En mi caso, la lista de 3 tuplas contendrá cientos de elementos, y estoy buscando la mejor solución escalable.

Gracias por su ayuda,

Respuesta

8

La manera más fácil estaría utilizando proporcionado por SQLAlchemy tuple_ función:

from sqlalchemy import tuple_ 

session.query(Foo).filter(tuple_(Foo.a, Foo.b, Foo.c).in_(items)) 

Esto funciona con PostgreSQL, pero se rompe con SQLite. No estoy seguro acerca de otros motores de base de datos.

Afortunadamente hay una solución que debería funcionar en todas las bases de datos.

de inicio mediante la asignación de todos los elementos, con el and_ expresión:

conditions = (and_(c1=x, c2=y, c3=z) for (x, y, z) in items) 

y luego crear un filtro or_ que encierra todas las condiciones:

q.filter(or_(*conditions)) 

Aquí está un ejemplo sencillo:

#/usr/bin/env python 
from sqlalchemy import create_engine 
from sqlalchemy import Column, Integer 
from sqlalchemy.sql import and_, or_ 
from sqlalchemy.orm import sessionmaker 
from sqlalchemy.ext.declarative import declarative_base 

engine = create_engine('sqlite:///') 
session = sessionmaker(bind=engine)() 
Base = declarative_base() 

class Foo(Base): 
    __tablename__ = 'foo' 

    id = Column(Integer, primary_key=True) 
    a = Column(Integer) 
    b = Column(Integer) 
    c = Column(Integer) 

    def __init__(self, a, b, c): 
     self.a = a 
     self.b = b 
     self.c = c 

    def __repr__(self): 
     return '(%d %d %d)' % (self.a, self.b, self.c) 

Base.metadata.create_all(engine) 

session.add_all([Foo(1, 2, 3), Foo(3, 2, 1), Foo(3, 3, 3), Foo(1, 3, 4)]) 
session.commit() 
items = ((1, 2, 3), (3, 3, 3)) 
conditions = (and_(Foo.a==x, Foo.b==y, Foo.c==z) for (x, y, z) in items) 
q = session.query(Foo) 
print q.all() 
q = q.filter(or_(*conditions)) 
print q 
print q.all() 

Qué salidas:

$ python test.py 
[(1 2 3), (3 2 1), (3 3 3), (1 3 4)] 
SELECT foo.id AS foo_id, foo.a AS foo_a, foo.b AS foo_b, foo.c AS foo_c 
FROM foo 
WHERE foo.a = :a_1 AND foo.b = :b_1 AND foo.c = :c_1 OR foo.a = :a_2 AND foo.b = :b_2 AND foo.c = :c_2 
[(1 2 3), (3 3 3)] 
+0

Muchas gracias, ¡es perfecto! –

2

Un enfoque menos convencional que sospecho que escalar bien sería crear una tabla temporal de todas las tuplas y luego unirse en la que:

import sqlalchemy 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy import Column, Integer, Table 
from sqlalchemy.orm import sessionmaker 
Base = declarative_base() 
engine = sqlalchemy.create_engine('sqlite:///:memory:') 
Session = sessionmaker(bind=engine) 
session = Session() 

class Triple(Base): 
    __tablename__ = 'triple' 
    id = Column(Integer(), primary_key=True) 
    x = Column(Integer()) 
    y = Column(Integer()) 
    z = Column(Integer()) 

ws_table = Table('where_sets', Base.metadata, 
     Column('x', Integer()), 
     Column('y', Integer()), 
     Column('z', Integer()), 
     prefixes = ['temporary'] 
    ) 

Base.metadata.create_all(engine) 

... 

where_sets = [(1, 2, 3), (3, 2, 1), (1, 1, 1)] 
ws_table.create(engine, checkfirst=True) 
session.execute(ws_table.insert(), [dict(zip('xyz', s)) for s in where_sets]) 
matches = session.query(Triple).join(ws_table, (Triple.x==ws_table.c.x) & (Triple.y==ws_table.c.y) & (Triple.z==ws_table.c.z)).all() 

que ejecuta SQL como esto:

INSERT INTO triple (x, y, z) VALUES (?, ?, ?) 
(1, 2, 3) 
INSERT INTO triple (x, y, z) VALUES (?, ?, ?) 
(3, 1, 2) 
INSERT INTO triple (x, y, z) VALUES (?, ?, ?) 
(1, 1, 1) 
SELECT triple.id AS triple_id, triple.x AS triple_x, triple.y AS triple_y, triple.z AS triple_z 
FROM triple JOIN where_sets ON triple.x = where_sets.x AND triple.y = where_sets.y AND triple.z = where_sets.z 
+0

Supongo que esta solución podría ser mucho más lenta que la anterior ¿no crees? De todos modos, gracias por el ejemplo :-) –

+0

@Thibaut: ¡No lo sabría hasta que lo probaras! Todo lo que sé es que he puesto de rodillas los sistemas de producción de gran escala con enormes cláusulas de "ENTRADA", y no sé si las grandes cláusulas de "DONDE" serían mejores en principio. Pero ya conoces muchos INSERT y un pequeño JOIN estará bien. Como usted pidió específicamente la "mejor solución escalable", eso es lo que pensé, pero tal vez unos pocos cientos no sean suficientes para que importe de todos modos. En cualquier caso, está ahí si lo necesitas. –

Cuestiones relacionadas