I have this kind of model for User. Users are able to have multiple friends and also one best friend.
friendship = Table('friendship', Base.metadata,
Column('m1_id', Integer, ForeignKey('user.id')),
Column('m2_id', Integer, ForeignKey('user.id'))
)
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(80), unique=True)
email = Column(String(120), unique=True, info={'validators': Email()})
age = Column(Integer())
best_friend_id = Column(Integer, ForeignKey('user.id'))
best_friend = relationship(lambda: User, remote_side=[id])
friends = relationship('User',
secondary=friendship,
primaryjoin=id==friendship.c.m1_id,
secondaryjoin=id==friendship.c.m2_id,
backref=backref('ofriends', lazy='dynamic'),
lazy='dynamic'
)
I m trying to get a query witch contains count of friends and is ordered by the name of persons best friend, but i cannot figure out how.
query = db_session.query(User, func.count(friendship)-1).join(friendship, User.id==friendship.c.m1_id).group_by(User)
Then this fails:
query.order_by(User.best_friend.name).all()
Thank you
friendship and best_friend are, in your model, unrelated. If you want to use the best_friend relationship in your query, you'll have to join it in somehow; since this is a self join, you also need to come up with an alias for the left and right instances of User, so they can be distinguished.
The following query gives users and the total number of friends they have, ordered by the name of whoever their best friend is (data about that best friend is not included in the result set)
>>> best_friend_alias = sa.orm.aliased(User)
>>> query = (Query([User, sa.func.count(friendship)-1])
... .join(friendship, User.id==friendship.c.m1_id)
... .join((best_friend_alias, User.best_friend))
... .group_by(User)
... .order_by(best_friend_alias.name))
>>> print query
SELECT "user".id AS user_id, "user".name AS user_name, "user".email AS user_email, "user".age AS user_age, "user".best_friend_id AS user_best_friend_id, count() - :count_1 AS anon_1
FROM "user" JOIN friendship ON "user".id = friendship.m1_id JOIN "user" AS user_1 ON user_1.id = "user".best_friend_id GROUP BY "user".id, "user".name, "user".email, "user".age, "user".best_friend_id ORDER BY user_1.name
Related
I have many-to-many relationships for users and roles and I want to select user which have specific roles using realtions.
For example I want to get user having:
roles = ["role_1", "role_2", "role_3"]
so I tried
query.filter(Users.roles.contains(roles))
(where roles - List[Roles])
but I got
sqlalchemy.exc.ArgumentError: Mapped instance expected for relationship comparison to object. Classes, queries and other SQL elements are not accepted in this context; for comparison with a subquery, use Users.roles.has(**criteria).
then I tried
query.filter(Users.roles.has(Roles.name.in_(roles)))
where roles already List[str]
And I got
sqlalchemy.exc.InvalidRequestError: 'has()' not implemented for collections. Use any().
but any() selects entry that has any associated role when I need entry that has all required roles. So how to select it right way using relationships instead of joins and etc.?
class Users(sa.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
login = Column(String(50), unique=False)
roles = relationship('Roles', secondary='user_roles_map',
cascade='all, delete')
class Roles(sa.Model):
__tablename__ = 'roles'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(40), unique=True)
class UserRolesMap(sa.Model):
__tablename__ = 'user_roles_map'
id_seq = Sequence(__tablename__ + "_id_seq")
id = Column(Integer(), id_seq, server_default=id_seq.next_value(),
unique=True, nullable=False)
user_id = Column(
Integer, ForeignKey('users.id'),
primary_key=True)
role_id = Column(
Integer, ForeignKey('roles.id'),
primary_key=True)
I didn't find what I was looking for, so for now I just wrote it with joins:
query = db_session.query(Users) \
.filter_by(**parameters)
.join(UserRolesMap, UserRolesMap.user_id == Users.id)\
.filter(UserRolesMap.role_id.in_(roles_ids))\
.group_by(Users)\
.having(func.count(UserRolesMap.role_id) >= len(roles_ids))
where roles_ids was collected from Roles table before. And if you need user with only required roles you can replace ">=" with "==".
I am trying to port the following query to SQLAlchemy:
SELECT u.username, GROUP_CONCAT(DISTINCT userS.name)
FROM Skills AS filterS
INNER JOIN UserSkills AS ufs ON filterS.id = ufs.skill_id
INNER JOIN Users AS u ON ufs.user_id = u.id
INNER JOIN UserSkills AS us ON u.id = us.user_id
INNER JOIN Skills AS userS ON us.skill_id = userS.id
WHERE filterS.name IN ('C#', 'SQL')
GROUP BY u.id;
I don't understand how to achieve AS statement in SQLAlchemy. Here is what I currently have:
# User class has attribute skills, that points to class UserSkill
# UserSkill class has attribute skill, that points to class Skill
db.session.query(User.id, User.username, func.group_concat(Skill.name).label('skills')).\
join(User.skills).\
join(UserSkill.skill).filter(Skill.id.in_(skillIds)).\
order_by(desc(func.count(Skill.id))).\
group_by(User.id).all()
Please help.
I figured this out. Here are the classes that are used in my Flask app:
class User(Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = Column(db.String(80), unique=True, nullable=False)
skills = db.relationship('UserSkill')
class Skill(Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = Column(db.String(80))
class UserSkill(Model):
status = db.Column(db.Enum(SkillStatus))
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
skill_id = db.Column(db.Integer, db.ForeignKey('skills.id'), primary_key=True)
skill = db.relationship("Skill")
So, the actual code would look like this:
from sqlalchemy.orm import aliased
userSkillF = aliased(UserSkill)
userSkillI = aliased(UserSkill)
skillF = aliased(Skill)
skillI = aliased(Skill)
db.session.query(User.id, User.username,\
func.group_concat(func.distinct(skillF.name)).label('skills'),\
func.group_concat(func.distinct(skillI.name)).label('other_skills')).\
join(userSkillF, User.skills).\
join(userSkillI, User.skills).\
join(skillF, userSkillF.skill).filter(skillF.id.in_(skillIds)).\
join(skillI, userSkillI.skill).\
group_by(User.id).all()
Many thanks Ilja Everilä, fresh look on SqlAlchemy docs made me understand aliased now.
We can do the join without relationships as well. Explicitly mention the condition on join.
Example
from sqlalchemy.orm import aliased
user1 = aliased(UserSkill)
user2 = aliased(UserSkill)
query_result = db.session.query(
func.distinct(User.id).label('user_id'),
User.username,
).join(
user1,
User.id == user1.user_id,
).join(
user2,
user2.id == User.id,
).filter(
user1.user_id == id,
).all()
Still wrapping my head around SqlAlchemy and have run into a few issues. Not sure if it is because I am creating the relationships incorrectly, querying incorrect, or both.
The general idea is...
one-to-many from location to user (a location can have many users but users can only have one location).
many-to-many between group and user (a user can be a member of many groups and a group can have many members).
Same as #2 above for desc and user.
My tables are created as follows:
Base = declarative_base()
class Location(Base):
__tablename__ = 'location'
id = Column(Integer, primary_key=True)
name = Column(String)
group_user_association_table = Table('group_user_association_table', Base.metadata,
Column('group_id', Integer, ForeignKey('group.id')),
Column('user_id', Integer, ForeignKey('user.id')))
class Group(Base):
__tablename__ = 'group'
id = Column(Integer, primary_key=True)
name = Column(String)
users = relationship('User', secondary=group_user_association_table, backref='group')
desc_user_association_table = Table('desc_user_association', Base.metadata,
Column('desc_id', Integer, ForeignKey('desc.id')),
Column('user_id', Integer, ForeignKey('user.id')))
class Desc(Base):
__tablename__ = 'desc'
id = Column(Integer, primary_key=True)
name = Column(String)
users = relationship('User', secondary=desc_user_association_table, backref='desc')
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
user_name = Column(String)
location_id = Column(Integer, ForeignKey('location.id'))
groups = Column(String, ForeignKey('group.id'))
descs = Column(String, ForeignKey('desc.id'))
location = relationship('Location', backref='user')
Here are some examples as to how I am creating the data (all being scraped from the web):
location = Location(id=city[1], name=city[0]) #city = ('name', id)
profile = User()
profile.id = int(str(span2class[0].a['href'][7:]))
profile.user_name = str(span2class[0].a.img['alt'])
profile.location_id = location.id
g = Group(id=gid, name=str(group.contents[0])) # add the group to the Group table
self.db_session.add(g)
# Now add the gid to a list that will be added to the profile that eventually gets added to the user table
profile.groups.append(str(gid)) # stick the gid into the list
profile.groups = ','.join(profile.groups) # convert list to csv string
# Repeat basically same thing above for desc
self.db_session.add(profile)
self.db_session.commit()
As far as queries go, I've got some of the basic ones working such as:
for instance in db_session.query(User).all():
print instance.id, instance.user_name
But when it comes to performing a join to get (for example) group.id and group.name for a specific user.id... nothing I've tried has worked. I am guessing that the form would be something like the following:
db_session.query(User, Group).join('users').filter(User.id==42)
but that didn't work.
Joins works from left to right, so you should join on the relationship from User to Group:
db_session.query(User, Group).join(User.group).filter(User.id == 42)
But this return you a list of tuples (<User>, <Group>), so if the user belongs to 2 or more groups, you will receive 2 or more rows.
If you really want to load both the user and its groups in one (SQL) query, a better way would be to load a user, but configure query to preload groups:
u = (session.query(User)
.options(joinedload(User.group))
.get(42)
)
print("User = {}".format(u))
for g in u.group:
print(" Group = {}".format(g))
I have one model that is Users in which there is a field in this model that I would like to store a list of Users. The idea is that you can add frieds and store them somewhere.
class User (db.Model):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
username = Column(String(50), unique = True)
email = Column(String(120), unique = True)
password = Column(String(50))
date = Column(DateTime(), default=datetime.now())
friends = "Should be a list of users"
I have thought to have a string with the id of each user but, is there any posibility to do it with a relationship to the same model? like this:
friends = relationship("User")
Thanks a lot!
Proposed solutions based on Adjacency List Relationships would only work in case when someone can be a friend of maximum one person, which I do not believe to be the case in the real world.
A pattern you need to apply in this case is called Self-Referential Many-to-Many Relationship. Please read the sample linked to above. In order to make it work for your model, you would need to create additional table to keep the pairs of friends, and configure the relationship as below:
# object model
t_userfriend = Table("user_friend", Base.metadata,
Column("user_id", Integer, ForeignKey("users.id"), primary_key = True),
Column("friend_id", Integer, ForeignKey("users.id"), primary_key = True),
)
class User (Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String(50), unique = True)
# ...
friends = relationship("User",
secondary = t_userfriend,
primaryjoin = (id == t_userfriend.c.user_id),
secondaryjoin = (id == t_userfriend.c.friend_id),
backref = "friend_of",
)
I guess that the other question you need to ask yourself is whether in your model if A is a friend of B, does this mean that B is a friend of A? In case this is true, you might want/need to:
either store just one side of the relationship, and calculate the other
make sure you always store both sides to the relationship
You can use Adjacency List Relationship and this link have the same issue so you can learn from it.
How to create relationship many to many in SQLAlchemy (python, flask) for model User to itself
Yes you can do it with Adjacency List Relationships.
class User (db.Model):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
username = Column(String(50), unique = True)
email = Column(String(120), unique = True)
password = Column(String(50))
date = Column(DateTime(), default=datetime.now())
friends = relationship("User",
backref=backref('parent', remote_side=[id])
)
I have two tables, users and contacts. I query the contacts table and get a list of a user's contacts. I would then like to be able to write Contact.first_name (where first_name is a row from the users table) and print out that contact's first name.
Currently, my Contact object does not recognize any attributes of the user table.
Here is some code:
class User(Base):
""" Basic User definition """
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
first_name = Column(Unicode(255))
last_name = Column(Unicode(255))
contacts = relationship('Contact', backref='users')
class Contact(Base):
__tablename__ = 'contacts'
id = Column(Integer, primary_key=True)
user_id = Column(Integer)
contact_id = Column(Integer)
__table_args__ = (ForeignKeyConstraint([id], [User.id]), {})
Here is my query:
Contact.query.filter(Contact.user_id == self.user_id).filter(Contact.state == True).all()
To be honest, I'm unsure of how to properly map my two foreign keys Contact.user_id and Contact.contact_id to the User.id row. Maybe this is the source of my problem?
I'm very new to using SQLAlchemy, so this is a learning experience here. Thanks for your help.
What you have here is class User which essentially refers to itself. In other words, it's a self-referential many-to-many relationship. Your model definitions should look like this:
# This is so called association table, which links two tables in many-to-many
# relationship. In this case it links same table's ('users') different rows.
user_contacts = Table(
'user_contacts', Base.metadata,
Column('user_id', Integer, ForeignKey('users.id'), primary_key=True),
Column('contact_id', Integer, ForeignKey('users.id'), primary_key=True),
)
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
contacts = relationship(
'User',
secondary=user_contacts,
primaryjoin=id==user_contacts.c.user_id,
secondaryjoin=id==user_contacts.c.contact_id
)
Then you can do things like the following:
u1 = User(first_name='Foo', last_name='Foo')
u2 = User(first_name='Bar', last_name='Bar')
u3 = User(first_name='Baz', last_name='Baz')
u1.contacts = [u2, u3]
session.add(u1)
session.commit()
# ... and in some other place in your code ...
u = User.query.get(1)
print u.contacts[0].first_name