2017-03-08 58 views
0

我在Flask SQLAlchemy中有用戶和房間模型。如果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" 
    ) 

謝謝,我已經找遍了所有simular問題,並不能找到答案。

+0

通過精確你的意思是一個'Room'絕只有'用戶'與給定的電子郵件作爲房間用戶?不是帶有給定電子郵件的房間,也可能是其他房間。 –

+0

是的,你理解它是正確的。只有給定用戶的房間。例如,如果我想發送消息兩個User1和User2,我需要知道與我們3的空間是否已經存在。我只需要3個房間,而不是3個房間和其他用戶。我希望,榜樣會幫助你。 –

回答

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; 

在蟒蛇會是這樣的:

# members contains user emails 
members = ['[email protected]', '[email protected]'] 
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() 
+0

由於JOIN只會生成與IN條件相匹配的行,從而篩選出可能的額外用戶,因此所有具有給定電子郵件用戶的房間將生成len(成員)組中的行。例如,如果您擁有用戶email1,2和3的房間,則此查詢將返回它。 –

+0

你是對的,它不工作。但不是在你的例子中,因爲count會是2.但是當我過濾三個用戶,並找到有2個用戶作爲成員的房間並且最後一個成員將是另一個用戶時,這個房間將在結果中,因爲count是我不明白爲什麼這麼複雜。我需要重新考慮應用程序體系結構。謝謝@IljaEverilä –

+1

不確定你的意思是「count will be 3」。對於所有擁有電子郵件地址爲* [email protected]*和* [email protected]*的用戶房間,這將是2,因爲在JOIN和WHERE子句過濾後,這些行將留在每個房間組中。 –

0

可以使用一個版本的relational division和一些額外的過濾實現這一目標:

首先建立一個臨時性的「表」(一個聯盟)你想搜索的電子郵件:

In [46]: emails = ['[email protected]', '[email protected]'] 

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

這可能看起來有點不友好的,但它本質上形成一個SQL UNION這樣的:

SELECT '[email protected]' AS email 
UNION 
SELECT '[email protected]' 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', ['[email protected]', '[email protected]'])] 

查詢幾乎回答的問題是「找到那些沒有這種電子郵件的Room」,該電子郵件不存在於Room.users「 - 它找到了具有所有給定電子郵件的房間 - 然後它應用第三個NOT EXISTS條件,該條件過濾掉帶有額外電子郵件的房間。沒有它,查詢也將返回房間2,裏面有郵件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()