SQLAlchemy Join to retrieve data from multiple tables - python

I'm trying to retrieve data from multiple tables with SQLAlchemy using the .join() method.
When I run the query I was expecting to get a single object back which had all the data from the different tables joined so that I could use a.area_name and so on where area_name is on one of the joined tables. Below is the query I am running and the table layout, if anyone could offer insight into how to achieve the behavior I'm aiming for I would greatly appreciate it! I've been able to use the .join() method with this same syntax to match results and return them, I figured it would return the extra data from the rows as well since it joins the tables (perhaps I'm misunderstanding how the method works or how to retrieve the information via the query object?).
If it helps with the troubleshooting I'm using MySQL as the database
query:
a = User.query.filter(User.user_id==1).join(UserGroup,
User.usergroup==UserGroup.group_id).join(Areas, User.area==Areas.area_id).first()
and the tables:
class User(db.Model):
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=True)
usergroup = db.Column(db.Integer, db.ForeignKey('user_group.group_id'), nullable=False)
area = db.Column(db.Integer, db.ForeignKey('areas.area_id'), nullable=False)
class UserGroups(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer, nullable=False, unique=True)
group_name = db.Column(db.String(64), nullable=False, unique=True)
class Areas(db.Model):
id = db.Column(db.Integer, primary_key=True)
area_id = db.Column(db.Integer, nullable=False, unique=True)
area_name = db.Column(db.String(64), nullable=False, unique=True)

So it seems that I need to use a different approach to the query, and that it returns a tuple of objects which I then need to parse.
What worked is:
a = db.session.query(User, UserGroups, Areas
).filter(User.user_id==1
).join(UserGroup,User.usergroup==UserGroup.group_id
).join(Areas, User.area==Areas.area_id
).first()
The rest remaining the same. This then returned a tuple that I could parse where the data from User is a[0], from UserGroups is a[1], and Areas is a[2]. I can then access the group_name column with a[1].group_name etc.
Hopefully this helps someone else who's trying to work with this!

Take a look at SQLAlchemy's relationship function:
http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#one-to-many
You may want to add a new attribute to your User class like so:
group = sqlalchemy.relationship('UserGroups', back_populates='users')
This will automagically resolve the one-to-many relationship between User and UserGroups (assuming that a User can only be member of one UserGroup at a time). You can then simply access the attributes of the UserGroup once you have queried a User (or set of Users) from your database:
a = User.query.filter(...).first()
print(a.group.group_name)
SQLAlchemy resolves the joins for you, you do not need to explicitly join the foreign tables when querying.
The reverse access is also possible; if you just query for a UserGroup, you can access the corresponding members directly (via the back_populates-keyword argument):
g = UserGroup.query.filter(...).first()
for u in g.users:
print(u.name)

Related

Support duplicate entries in a M2M associative table

I am working on a SQL-Alchemy app using flask and flask-db and have been scratching my head over how to solve this question. My models looks like this:
class event_schematics_map():
event_schematics_table = db.Table(
'event_schematics_table',
db.Column('fk_schematic_id', db.Integer, db.ForeignKey('schematics.id')),
db.Column('fk_event_id', db.Integer, db.ForeignKey('events.id'))
)
class Events(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), index=True, unique=False)
date = db.Column(db.String(120), unique=False)
owner = db.Column(db.Integer, db.ForeignKey('user.id'))
schematics = db.relationship('Recipe', secondary=event_schematics_map.event_schematics_table, backref='schematic')
class Schematics(db.Model):
__tablename__ = 'schematics'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.VARCHAR(70), index=True)
schematics_description = db.Column(db.String(1024), index=True)
creator_id = db.Column(db.Integer, db.ForeignKey('user.id'))
Schematics are created and are on the many side of O2M with a user table separately not shown above. The map table is used as the glue in the M2M relationship.
Currently I am adding new schematics to each event and updating the assoc table like so: events.schematics.append(SomeNewSchematic) which works fine until I attempt to enter multiple instances of the exact same Schematic like this:
schem1 = Schematics(name='TheOnlySchematic')
schem2 = Schematics(name='TheOnlySchematic')
event.schematics.append(schem1)
event.schematics.append(schem2)
etc
in which case I can only apply one as I think the entry is being duplicated. I believe this may be solved by an additional field in the assoc table event_schematics_map, but unsure if I am overlooking something simpler or how to implement this.
Effectively I want to support multiple entries of the exact same model
I believe my problem is along the same lines as can I append twice the same object to an instrumentedlist in sqlalchemy - but I could not see a solution for this.
Really appreciate any pointers or to know how to solve this problem.
Thank you for your reply and setting me straight here,
You are quite right, duplicate entries does not make sense.
I ended up solving this by using an associative table as discussed in other answers to track the occurrence of each schematic.

How to Store a List Within a Model in Flask SQLAlchemy

I am trying to store a list within a model using Flask's SQLAlchemy library. The data stored would be a list of latitude longitude points like so:
['41.0282', '73.7787']
I would like to store a value like this inside my User table like the one below:
class User(UserMixin, db.Model):
__tablename__ = 'users'
__table_args__ = {'extend_existing': True}
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
email = db.Column(db.String(64), unique=True, index=True)
Anyone know how I can go about this?
There is no way to store a list in SQL. The proper way to handle multiple values is to create a new table and reference to that table with foreign keys.
If thats to heavyweight you can also store the values as a string and seperate them after your sql query.
This is then stored in your DB
"41.0282, 73.7787"
To get your original list (python):
"41.0282, 73.7787".split(',')

SQLAlchemy bulk create if not exists

I am trying to optimize my code by reducing the calls to the Database. I have the following models:
class PageCategory(Base):
category_id = Column(Text, ForeignKey('category.category_id'), primary_key=True)
page_id = Column(Text, ForeignKey('page.page_id'), primary_key=True)
class Category(Base):
category_id = Column(Text, primary_key=True)
name = Column(Text, nullable=False)
pages = relationship('Page', secondary='page_category')
class Page(Base):
page_id = Column(Text, primary_key=True)
name = Column(Text, nullable=False)
categories = relationship('Category', secondary='page_category')
The code receives a stream of Facebook likes and each one comes with a Pagea Category and the obvious relation between them a PageCategory. I need to find a way to bulk create, if not existing already, the different Pages, Categories and the relation between them. Given that the code needs to be fast I can't afford a round trip to the Database when creating every object.
page = Page(page_id='1', name='1')
category = Category(category_id='2', name='2')
session.add(page)
session.add(category)
session.commit()
...same for PageCategory
Now, given that a page_id and category_id are PK, the database will raise an IntegrityError if we try to insert duplicates, but that is still a round-trip dance. I would need a utility that receives, say a list of objects like session.bulk_save_objects([page1, page2, category1, category2, page_category1, page_category2]) but just create the objects that do not raise an IntegrityError, and ignore the ones that do.
This way I will be avoiding Database IO for every triple of objects. I don't know if this is possible or this exceeds SQLAlchemy capabilities.

Set foreign key to nullable=false?

Do you set your foreign keys as nullable=false if always expect a foreign key on that column in the database?
I'm using sqlalchemy and have set my models with required foreign keys. This sometimes causes me to run session.commit() more often, since I need the parent model to have an id and be fully created in order to build a child object in the ORM. What is considered best practice? My models are below:
class Location(Base):
__tablename__ = 'locations'
id = Column(Integer, primary_key=True)
city = Column(String(50), nullable=False, unique=True)
hotels = relationship('Hotel', back_populates='location')
class Hotel(Base):
__tablename__ = 'hotels'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False, unique=True)
phone_number = Column(String(20))
parking_fee = Column(String(10))
location_id = Column(Integer, ForeignKey('locations.id'), nullable=False)
location = relationship('Location', back_populates='hotels')
You don't need to do session.commit() to get an ID; session.flush() will do.
Even better, you don't need to get an ID at all if you set the relationship because SQLalchemy will figure out the order to do the INSERTs in. You can simply do:
loc = Location(city="NYC", hotels=[Hotel(name="Hilton")])
session.add(loc)
session.commit()
and it will work fine.
I would suggest that you'd better not set nullable=False. Make foreign key nullable is very reasonable in many situations. In your scenario, for example, if I want to insert a hotel whose location is currently underdetermined, you can not accomplish this with the foreign key not null. So the best practice when using foreign key is to set it nullable.
See necessary nullable foreign key Any example of a necessary nullable foreign key?

sqlalchemy foreign key relationship attributes

I have a User table and a Friend table. The Friend table holds two foreign keys both to my User table as well as a status field. I am trying to be able to call attributes from my User table on a Friend object. For example, I would love to be able to do something like, friend.name, or friend.email.
class User(Base):
""" Holds user info """
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(25), unique=True)
email = Column(String(50), unique=True)
password = Column(String(25))
admin = Column(Boolean)
# relationships
friends = relationship('Friend', backref='Friend.friend_id',primaryjoin='User.id==Friend.user_id', lazy='dynamic')
class Friend(Base):
__tablename__ = 'friend'
user_id = Column(Integer, ForeignKey(User.id), primary_key=True)
friend_id = Column(Integer, ForeignKey(User.id), primary_key=True)
request_status = Column(Boolean)
When I get friend objects all I have is the 2 user_ids and i want to display all properties of each user so I can use that information in forms, etc. I am new to sqlalchemy - still trying to learn more advanced features. This is just a snippet from a larger Flask project and this feature is going to be for friend requests, etc. I've tried to look up association objects, etc, but I am having a hard with it.
Any help would be greatly appreciated.
First, if you're using flask-sqlalchemy, why are you using directly sqlalchemy instead of the Flask's db.Model?
I strongly reccomend to use flask-sqlalchemy extension since it leverages the sessions and some other neat things.
Creating a proxy convenience object is straightforward. Just add the relationship with it in the Friend class.
class Friend(Base):
__tablename__ = 'friend'
user_id = Column(Integer, ForeignKey(User.id), primary_key=True)
friend_id = Column(Integer, ForeignKey(User.id), primary_key=True)
request_status = Column(Boolean)
user = relationship('User', foreign_keys='Friend.user_id')
friend = relationship('User', foreign_keys='Friend.friend_id')
SQLAlchemy will take care of the rest and you can access the user object simply by:
name = friend.user.name
If you plan to use the user object every time you use the friend object specify lazy='joined' in the relationship. This way it loads both object in a single query.

Categories