I have the following SQLAlchemy mapped classes:
class User(Base):
__tablename__ = 'users'
email = Column(String, primary_key=True)
name = Column(String)
class Document(Base):
__tablename__ = "documents"
name = Column(String, primary_key=True)
author = Column(String, ForeignKey("users.email"))
class DocumentsPermissions(Base):
__tablename__ = "documents_permissions"
readAllowed = Column(Boolean)
writeAllowed = Column(Boolean)
document = Column(String, ForeignKey("documents.name"))
I need to get a table like this for user.email = "user#email.com":
email | name | document_name | document_readAllowed | document_writeAllowed
How can it be made using one query request for SQLAlchemy? The code below does not work for me:
result = session.query(User, Document, DocumentPermission).filter_by(email = "user#email.com").all()
Thanks,
Try this
q = Session.query(
User, Document, DocumentPermissions,
).filter(
User.email == Document.author,
).filter(
Document.name == DocumentPermissions.document,
).filter(
User.email == 'someemail',
).all()
As #letitbee said, its best practice to assign primary keys to tables and properly define the relationships to allow for proper ORM querying. That being said...
If you're interested in writing a query along the lines of:
SELECT
user.email,
user.name,
document.name,
documents_permissions.readAllowed,
documents_permissions.writeAllowed
FROM
user, document, documents_permissions
WHERE
user.email = "user#email.com";
Then you should go for something like:
session.query(
User,
Document,
DocumentsPermissions
).filter(
User.email == Document.author
).filter(
Document.name == DocumentsPermissions.document
).filter(
User.email == "user#email.com"
).all()
If instead, you want to do something like:
SELECT 'all the columns'
FROM user
JOIN document ON document.author_id = user.id AND document.author == User.email
JOIN document_permissions ON document_permissions.document_id = document.id AND document_permissions.document = document.name
Then you should do something along the lines of:
session.query(
User
).join(
Document
).join(
DocumentsPermissions
).filter(
User.email == "user#email.com"
).all()
One note about that...
query.join(Address, User.id==Address.user_id) # explicit condition
query.join(User.addresses) # specify relationship from left to right
query.join(Address, User.addresses) # same, with explicit target
query.join('addresses') # same, using a string
For more information, visit the docs.
A good style would be to setup some relations and a primary key for permissions (actually, usually it is good style to setup integer primary keys for everything, but whatever):
class User(Base):
__tablename__ = 'users'
email = Column(String, primary_key=True)
name = Column(String)
class Document(Base):
__tablename__ = "documents"
name = Column(String, primary_key=True)
author_email = Column(String, ForeignKey("users.email"))
author = relation(User, backref='documents')
class DocumentsPermissions(Base):
__tablename__ = "documents_permissions"
id = Column(Integer, primary_key=True)
readAllowed = Column(Boolean)
writeAllowed = Column(Boolean)
document_name = Column(String, ForeignKey("documents.name"))
document = relation(Document, backref = 'permissions')
Then do a simple query with joins:
query = session.query(User, Document, DocumentsPermissions).join(Document).join(DocumentsPermissions)
Expanding on Abdul's answer, you can obtain a KeyedTuple instead of a discrete collection of rows by joining the columns:
q = Session.query(*User.__table__.columns + Document.__table__.columns).\
select_from(User).\
join(Document, User.email == Document.author).\
filter(User.email == 'someemail').all()
This function will produce required table as list of tuples.
def get_documents_by_user_email(email):
query = session.query(
User.email,
User.name,
Document.name,
DocumentsPermissions.readAllowed,
DocumentsPermissions.writeAllowed,
)
join_query = query.join(Document).join(DocumentsPermissions)
return join_query.filter(User.email == email).all()
user_docs = get_documents_by_user_email(email)
Related
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()
I am having trouble constructing a query using SQLalchemy. Here is a simplified representation of the models I have defined:
Models
Project
class Project(Base):
__tablename__ = 'project'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True)
# User associations
users = relationship(
'User',
secondary='user_project_association'
)
User
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True)
# Project associations
projects = relationship(
'Project',
secondary='user_project_association'
)
User <-> Project (association)
class UserProjectAssociation(Base):
__tablename__ = 'user_project_association'
# User association.
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
user = relationship('User', backref='project_associations')
# Project association.
project_id = Column(Integer, ForeignKey('project.id'), primary_key=True)
project = relationship('Project', backref='user_associations')
Query
I want to perform a query on the projects table such that the result contains information about the projects as well as information about the associated users - if there are any. I am including a filter based on the user name. I am eventually going to send the result as JSON via a REST API so I would prefer the results as python {dict} objects rather than SQLAlchemy objects. The query I am performing looks like:
# Add return fields
query = session.query(
Project.id,
Project.name,
User.id.label('users.id'),
User.name.label('users.name')
)
# Add join statements
query = query.outerjoin(User, Project.users)
# Add filters
query = query.filter(
Project.name == 'proj1',
User.name != 'jane.doe' # <--- I think this is causing the issue.
)
# Execute
results = query.all()
data = [result._asdict() for result in results]
print(data)
Results
The database contains a project called proj1 which doesn't have any associated users. In this particular scenario, I am filtering on a user column and the user association does not exist. However, I am still expecting to get a row for the project in my results but the query returns an empty list. The result I am expecting would look something like this:
[{'id': 1, 'name': 'proj1', 'users.id': None, 'users.name': None}]
Can someone explain where I am going wrong?
You have to account for the NULL values that result from the left join, since != compares values and NULL is the absence of value, so the result of NULL != 'jane.doe' is NULL, not true:
query = query.filter(
Project.name == 'proj1',
or_(User.name == None, User.name != 'jane.doe')
)
Note that SQLAlchemy handles equality with None in a special way and produces IS NULL. If you want to be less ambiguous you could also use User.name.is_(None).
I am trying to get a SQL Alchemy query of distinct items below filtering on related objects, the equivalent of the below query:
SELECT distinct items.item_id, items.item_name
FROM items
INNER JOIN categories as cat on items.category_id = cat.category_id
INNER JOIN stores on cat.store_id = stores.store_id
WHERE store.store_id = 123
I have created the models as below with foreign keys included but when I run the query below it does not filter correctly.
items_query = (db.session.query(Store, Item)
.filter(Store.store_id == 123)
).all()
#SQL Alchemy Models
class Store(db.Model):
__tablename__ = 'stores'
store_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
store_name = db.Column(db.String(100), unique=True, nullable=False)
def __repr__(self):
return '<Store>'+str(self.store_name)
class Category(db.Model):
__tablename__ = 'categories'
category_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
category_name = db.Column(db.String(100), unique=True, nullable=False)
store_id = db.Column(db.Integer, db.ForeignKey('stores.store_id'))
store = db.relationship('Store', backref=db.backref('categories', lazy='dynamic'))
def __repr__(self):
return '<Category>'+str(self.category_name)
class Item(db.Model):
__tablename__ = 'items'
item_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
item_name = db.Column(db.String(150), unique=True, nullable=False)
category_id = db.Column(db.Integer, db.ForeignKey('categories.category_id'))
category = db.relationship('Category', backref=db.backref('items', lazy='dynamic'))
def __repr__(self):
return '<Item>'+str(self.item_name)
Could anyone assist me to form the query better?
With
(db.session.query(Store, Item)
.filter(Store.store_id == 123)
).all()
you'll get an implicit cross join between Store and Item, which is clearly not what you want.
First build the required joins either explicitly using the relationships:
query = db.session.query(Item.item_id, Item.item_name).\
join(Item.category).\
join(Category.store)
or the shorthand form:
query = db.session.query(Item.item_id, Item.item_name).\
join("category", "store")
Then apply your WHERE clause:
query = query.filter(Store.store_id == 123)
And then distinct():
query = query.distinct()
To sum it up:
query = db.session.query(Item.item_id, Item.item_name).\
join("category", "store").\
filter(Store.store_id == 123).\
distinct().\
all()
Also since you've unique constraint on Item.item_name and the joins should not produce multiple rows per Item because of the direction of the one to many relationships, the distinct() should be unnecessary.
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’ve looked all over the SQLAlchemy tutorial and other similar questions but I seem to be struggling to get this join to work:
The scenario: I have a pages table represented by the Page model. Pages can be created by an user and edited by an user, but not necessarily the same one. My Page model looks like this (abridged):
class Page(Base):
__tablename__ = 'pages'
id = Column(Integer, primary_key = True)
slug = Column(Text)
title = Column(Text)
direct_link = Column(Text)
body = Column(Text)
category_id = Column(Integer, ForeignKey('categories.id'))
published_on = Column(DateTime)
publishing_user_id = Column(Integer, ForeignKey('users.id'))
last_edit_on = Column(DateTime)
last_edit_user_id = Column(Integer, ForeignKey('users.id'))
# Define relationships
publish_user = relationship('User', backref = backref('pages', order_by = id), primaryjoin = "Page.publishing_user_id == User.id")
edit_user = relationship('User', primaryjoin = "Page.last_edit_user_id == User.id")
category = relationship('Category', backref = backref('pages', order_by = id))
My users are stored in the users table represented by the User model. As I said I’ve been all over the SQLAlchemy docs looking for this, I’ve tried to make it look as similar to their example as possible, but no to no avail. Any help would be greatly appreciated.
As of version 0.8, SQLAlchemy can resolve the ambiguous join using only the foreign_keys keyword parameter to relationship.
publish_user = relationship(User, foreign_keys=[publishing_user_id],
backref=backref('pages', order_by=id))
edit_user = relationship(User, foreign_keys=[last_edit_user_id])
Documentation at http://docs.sqlalchemy.org/en/rel_0_9/orm/join_conditions.html#handling-multiple-join-paths
I think you almost got it right; only instead of Model names you should use Table names when defining primaryjoin. So instead of
# Define relationships
publish_user = relationship('User', backref = backref('pages', order_by = id),
primaryjoin = "Page.publishing_user_id == User.id")
edit_user = relationship('User',
primaryjoin = "Page.last_edit_user_id == User.id")
use:
# Define relationships
publish_user = relationship('User', backref = backref('pages', order_by = id),
primaryjoin = "pages.publishing_user_id == users.id")
edit_user = relationship('User',
primaryjoin = "pages.last_edit_user_id == users.id")
Try foreign_keys option:
publish_user = relationship(User, foreign_keys=publishing_user_id,
primaryjoin=publishing_user_id == User.id,
backref=backref('pages', order_by=id))
edit_user = relationship(User, foreign_keys=last_edit_user_id,
primaryjoin=last_edit_user_id == User.id)
The example in this documentation
http://docs.sqlalchemy.org/en/rel_0_9/orm/join_conditions.html#handling-multiple-join-paths isn't for one-to-many.... I think.
In the one-to-many case here's what worked for me:
class Pipeline(Base):
__tablename__ = 'pipelines'
id = Column(UUID(as_uuid=True), primary_key=True, unique=True, default=uuid.uuid4)
...
input_resources = relationship("Resource", foreign_keys="Resource.input_pipeline_id")
output_resources = relationship("Resource", foreign_keys="Resource.output_pipeline_id")
...
class Resource(Base):
__tablename__ = 'resources'
id = Column(UUID(as_uuid=True), primary_key=True, unique=True, default=uuid.uuid4)
....
input_pipeline_id = Column(UUID(as_uuid=True), ForeignKey("pipelines.id"))
output_pipeline_id = Column(UUID(as_uuid=True), ForeignKey("pipelines.id"))
...