Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Join multiple tables in SQLAlchemy/Flask

I am trying to figure out the correct join query setup within SQLAlchemy, but I can't seem to get my head around it.

I have the following table setup (simplified, I left out the non-essential fields):

class Group(db.Model):     id            = db.Column(db.Integer, primary_key = True)     number        = db.Column(db.SmallInteger, index = True, unique = True)     member        = db.relationship('Member', backref = 'groups', lazy = 'dynamic')  class Member(db.Model):     id            = db.Column(db.Integer, primary_key = True)     number        = db.Column(db.SmallInteger, index = True)     groupid       = db.Column(db.Integer, db.ForeignKey('group.id'))     item          = db.relationship('Item', backref = 'members', lazy = 'dynamic')  class Version(db.Model):     id           = db.Column(db.Integer, primary_key = True)     name         = db.Column(db.String(80), index = True)     items        = db.relationship('Item', backref='versions', lazy='dynamic')    class Item(db.Model):     id           = db.Column(db.Integer, primary_key = True)     member       = db.Column(db.Integer, db.ForeignKey('member.id'))     version      = db.Column(db.Integer, db.ForeignKey('version.id')) 

So the relationships are the following:

  • 1:n Group Member
  • 1:n Member Item
  • 1:n Version Item

I would like to construct a query by selecting all Item-Rows from the database, that have a certain version. Then I would like to order them by Group and then by Member. The output using Flask/WTForm should look something like this:

* GroupA   * MemberA      * ItemA (version = selected by user)      * ItemB ( dito )   * Member B      * ItemC ( dito )   .... 

I have come up with something like the following query, but I am pretty sure that it is not correct (and inefficient)

   session.query(Item,Member,Group,Version)     .join(Member).filter(version.id==1)     .order_by(Group).order_by(Member).all() 

My first intuitive approach would have been to create something like

Item.query.join(Member, Item.member==Member.id)     .filter(Member.versions.name=='MySelection')     .order_by(Member.number).order_by(Group.number) 

but obviously, this doesn't work at all. The join operation on the Version table does not seem to produce the type of join between the two tables that I expected. Maybe I am totally misunderstanding the concept, but after reading the tutorials this would have made sense to me.

like image 967
user3347953 Avatar asked Feb 24 '14 18:02

user3347953


People also ask

How do I join a table in SQLAlchemy ORM?

I've found that the following works to join two tables: result = session. query(User, Document). select_from(join(User, Document)). filter(User.

What is join in SQLAlchemy?

Python Flask and SQLAlchemy ORM Effect of joining is achieved by just placing two tables in either the columns clause or the where clause of the select() construct. Now we use the join() and outerjoin() methods. The join() method returns a join object from one table object to another.

How does SQLAlchemy connect to Flask?

Step 1 - Install the Flask-SQLAlchemy extension. Step 2 - You need to import the SQLAlchemy class from this module. Step 3 - Now create a Flask application object and set the URI for the database to use. Step 4 - then use the application object as a parameter to create an object of class SQLAlchemy.

Why can't I join two tables in SQLAlchemy?

The error message is telling you that SQLAlchemy can't determine how to join the two tables users and friendships, because there is more than one foreign key linking them. You need to explicitly define the join condition.

How to join table_1 to table_2 in SQL?

The session.query will join table_1 to table_2 on ID and ID_1 columns and uses the filter condition as provided. And lastly, it will print the expected rows of the table_1 after all conditions get applied. The JOIN and the FILTER condition can be changed as per the use case.

How to create queries on two tables at the same time?

Now that we have two tables, we will see how to create queries on both tables at the same time. To construct a simple implicit join between Customer and Invoice, we can use Query.filter () to equate their related columns together. Below, we load the Customer and Invoice entities at once using this method −

How does left join work in SQL Server?

The answer is simple and it’s related to how LEFT JOIN works. It takes the first table ( customer ) and joins all its rows (4 of them) to the next table ( city ). The result of this is 4 rows because the customer could belong to only 1 city.


1 Answers

Following will give you the objects you need in one query:

q = (session.query(Group, Member, Item, Version)         .join(Member)         .join(Item)         .join(Version)         .filter(Version.name == my_version)         .order_by(Group.number)         .order_by(Member.number)         ).all() print_tree(q) 

However, the result you get will be a list of tuples (Group, Member, Item, Version). Now it is up to you to display it in a tree form. Code below might prove useful though:

def print_tree(rows):     def get_level_diff(row1, row2):         """ Returns tuple: (from, to) of different item positions.  """         if row1 is None: # first row handling             return (0, len(row2))         assert len(row1) == len(row2)         for col in range(len(row1)):             if row1[col] != row2[col]:                 return (col, len(row2))         assert False, "should not have duplicates"      prev_row = None     for row in rows:         level = get_level_diff(prev_row, row)         for l in range(*level):             print 2 * l * " ", row[l]             prev_row = row 

Update-1: If you are willing to forgo lazy = 'dynamic' for the first two relationships, you can a query to load a whole object network (as opposed to tuples above) with the code:

q = (session.query(Group)         .join(Member)         .join(Item)         .join(Version)         # @note: here we are tricking sqlalchemy to think that we loaded all these relationships,         # even though we filter them out by version. Please use this only to get data and display,         # but not to continue working with it as if it were a regular UnitOfWork         .options(             contains_eager(Group.member).             contains_eager(Member.items).             contains_eager(Item.version)             )         .filter(Version.name == my_version)         .order_by(Group.number)         .order_by(Member.number)         ).all()  # print tree: easy navigation of relationships for g in q:     print "", g     for m in g.member:         print 2 * " ", m         for i in m.items:             print 4 * " ", i 
like image 141
van Avatar answered Sep 17 '22 17:09

van