如何过滤精确到多个

时间:2017-03-08 14:15:59

标签: python sqlalchemy flask-sqlalchemy

我在Flask SQLAlchemy中有User和Room模型。我需要过滤如果房间存在用户[user1,user2,...]。过滤器必须准确。
这是我的模特:

room_users_table = db.Table(
  'room_users',
    db.metadata,
    db.Column('user', db.Integer, db.ForeignKey('user.id')),
    db.Column('room', db.Integer, db.ForeignKey('room.id'))
)

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True)
    first_name = db.Column(db.String(80))
    last_name = db.Column(db.String(80))
    password = db.Column(db.String(80))
    email = db.Column(db.String(120), unique=True)
    rooms = db.relationship(
        "Room",
        secondary=room_users_table,
        back_populates="users"
    )

class Room(db.Model):
   __tablename__ = 'room'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True)
    users = db.relationship(
        "User",
        secondary=room_users_table,
        back_populates="rooms"
    )

谢谢,我已经搜索了所有类似问题但无法找到答案。

2 个答案:

答案 0 :(得分:0)

经过两天的研究,我有一个临时解决方案。

-- room_users is the "secondary" table in 
   -- sqlalchemy many to many between User and Room
select room.name, room_users.room 
    from room_users join room on room_users.room = room.id 
    where room_users.user in (1,2) 
    group by room_users.room having count(*) = 2;

在python中它将是这样的:

# members contains user emails
members = ['email1@mail.com', 'email2@mail.com']
db.session.query(Room.name).join(Room.users).filter(
    User.email.in_(members)
).group_by(Room.id).having(
    func.count(Room.id) == len(members)
).first()

答案 1 :(得分:0)

您可以使用relational division版本和其他一些过滤来实现此目的:

首先建立一个临时"表" (联合)您要搜索的电子邮件:

In [46]: emails = ['email1@mail.com', 'email2@mail.com']

In [47]: emails_union = db.union(*(db.select([db.literal(email).label('email')])
                                   for email in emails)).alias()

这可能看起来有点不受欢迎,但它本质上形成了一个像这样的SQL UNION:

SELECT 'email1@mail.com' AS email
UNION
SELECT 'email2@mail.com' AS email

并给它一个别名。某些数据库可能支持从列表for example with Postgresql you could

生成新关系的其他方法
In [64]: from sqlalchemy.dialects.postgresql import array

In [65]: emails_relation = db.func.unnest(array(emails)).alias()

除法本身是使用双重否定或2个嵌套NOT EXISTS条件完成的:

In [48]: db.session.query(Room).\
    ...:     filter(~db.session.query().select_from(emails_union).
    ...:                filter(~Room.users.any(email=emails_union.c.email)).
    ...:                exists(),
    ...:            ~Room.users.any(User.email.notin_(emails))).\
    ...:     all()
Out[48]: [<__main__.Room at 0x7fad4d238128>]

In [49]: [(r.name, [u.email for u in r.users]) for r in _]
Out[49]: [('room1', ['email1@mail.com', 'email2@mail.com'])]

该查询几乎回答了问题&#34;找到Room&#34;中不存在此类电子邮件的Room.users个问题。 - 找到包含所有给定电子邮件的房间 - 然后它应用第3个NOT EXISTS条件,过滤掉带有额外电子邮件的房间。如果没有它,查询也会返回 room2 ,其中包含电子邮件1,2和3。

搜索是针对这些数据进行的:

In [10]: users = [User(id=id_, email='email{}@mail.com'.format(id_))
    ...:          for id_ in range(1, 10)]

In [11]: rooms = [Room(id=id_, name='room{}'.format(id_))
    ...:          for id_ in range(1, 10)]

In [18]: db.session.add_all(users)

In [19]: db.session.add_all(rooms)

In [20]: for room, user1, user2 in zip(rooms, users, users[1:]):
    ...:     room.users.append(user1)
    ...:     room.users.append(user2)
    ...:     

In [21]: rooms[1].users.append(users[0])

In [22]: db.session.commit()