简体   繁体   English

如何使用SQLAlchemy获取与三元组条件列表匹配的行

[英]How to get rows which match a list of 3-tuples conditions with SQLAlchemy

Having a list of 3-tuples : 有一个三元组列表:

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

I want to retrieve all the rows from a table where 3 columns matches the tuples. 我想从3列匹配元组的表中检索所有行。 FOr this example, the query WHERE clause could be something like this : 对于此示例,查询WHERE子句可能是这样的:

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

How can I create such a request using SQLAlchemy ? 如何使用SQLAlchemy创建这样的请求? In my case the 3-tuples list will contains hundred of elements, and I'm looking for the best scallable solution. 以我为例,三元组列表将包含数百个元素,而我正在寻找最佳的可调用解决方案。

Thanks for your help, 谢谢你的帮助,

Easiest way would be using SQLAlchemy-provided tuple_ function: 最简单的方法是使用SQLAlchemy提供的tuple_函数:

from sqlalchemy import tuple_

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

This works with PostgreSQL, but breaks with SQLite. 这适用于PostgreSQL,但适用于SQLite。 Not sure about other database engines. 不确定其他数据库引擎。

Fortunately there's a workaround that should work on all databases. 幸运的是,有一种对所有数据库都适用的解决方法。

Start by mapping out all the items with the and_ expression: 首先使用and_表达式映射所有项目:

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

And then create an or_ filter that encloses all the conditions: 然后创建一个包含所有条件的or_过滤器:

q.filter(or_(*conditions))

Here's a simple example: 这是一个简单的例子:

#/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()

Which outputs: 哪个输出:

$ 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)]

A less conventional approach that I suspect would scale well would be to create a temporary table of all your tuples and then join on that: 我怀疑可以扩展的不太常规的方法是创建一个所有元组的临时表,然后加入该表:

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()

which executes SQL like this: 这样执行SQL:

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

Would anyone consider creating an extra key in the original table ? 有人会考虑在原始表中创建一个额外的键吗? ie create a new column with "1"-"2"-"3" instead of another table and check for the uniqueness. 即用“ 1”-“ 2”-“ 3”代替另一个表创建一个新列,并检查其唯一性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM