Flask SqlAlchemy join two models without foreign key MYSQL - python

I am joining two models without a foreign key:
Models:
class Users(db.Model):
__tablename__ = "Users"
userName = db.Column(db.String, primary_key=True)
lastLogin = db.Column(db.DateTime)
class TimeOff
__tablename__ = "timeOff"
timeOffID = db.Column(db.Integer, primary_key=True)
userName = db.Column("userName", db.String, db.ForeignKey('appUsers.userName')),
dayWork = db.Column(db.DateTime)
View:
result = db.session.query(models.Users).join(models.TimeOff)
sqlalchemy.exc.InvalidRequestError: Could not find a FROM clause to join from.
Tried joining to but got: Can't find any foreign key relationships between 'TimeOff' and 'Users'.
I dont have a foreign key defined in table

You need to tell SQLAlchemy how to join the tables. Try something like this:
result = db.session.query(Users).join(TimeOff,Users.userName==TimeOff.userName)

To improve upon #Matt Healy's answer, if you also want to be able to access attributes on the joined object you can do something like:
user, timeOff = db.session.query(Users, TimeOff).join(
TimeOff, Users.userName == TimeOff.userName
).first()
Then timeOff.dayWork etc. will give the information you need.

In my situation, I want to join the two objects so that I can get the Author information of the Posts, but I don't want to use the architecture-level foreign key design to associate the two tables.here is my codes:
class Posts(db.Model):
id= db.Column(db.String, primary_key=True)
author_code= db.Column(db.String)
...
class Author:
id= db.Column(db.Integer, primary_key=True)
name= db.Column(db.String),
code= db.Column(db.String)
address= db.Column(db.String)
...
we can add the code like this:
from sqlalchemy.orm import relationship
class Posts(db.Model):
id= db.Column(db.String, primary_key=True)
author_code= db.Column(db.String)
...
# add this line
author_info = relationship('Author',
foreign_keys=[author_code],
primaryjoin='Author.code == Posts.author_code')
then we can get author_info by query like this:
post_id = xxx # some id you defined
post = Posts.query.filter_by(id=post_id).one_or_none()
# here you can get `author_info` attributes you want.
print(dir(post),post.author_info)
You can learn more about this with link here,and read the documents Configuring how Relationship Joins — SQLAlchemy 1.4 Documentation

Its an old post but I had a similar problem
result = session.query(models.Users).join(models.TimeOff, models.Users.userName == models.TimeOff.userName).all()
with this method, I can reach the features of the first object which is Users but not the TimeOff. I am wondering if it is possible to reach the secondary object's attributes. But I hope this helps.

This worked for me,
I have 3 tables, And I have the rtf_id which is unique to all three tables, you have to use the select_from keyword which tells the table starting from left.
db_data = dbobj.session.query(A, B, C). \
select_from(A).join(B, B.rtf_id == A.rtf_id). \
join(C, C.rtf_id == A.rtf_id).all()

Related

Flask-SQLAlchemy returning object in query when working with many-to-many relationship

I'm new with SQLAlch and I'm trying to do a simple query in my database but i'm getting objects in response instead of strings. My data model is the following:
wallet_tags_association = db.Table(
'wallet_tags', db.Model.metadata,
db.Column('wallet_id', db.Integer, db.ForeignKey('wallet.id')),
db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'))
)
class WalletData(db.Model):
__tablename__ = 'wallet'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20))
total_value = db.Column(db.DECIMAL)
tags = db.relationship('Tags', secondary='wallet_tags')
def __init__(self, name):
self.name = name
class Tags(db.Model):
__tablename__ = 'tags'
id = db.Column(db.Integer, primary_key=True)
tag_name = db.Column(db.String(20))
def __init__(self, tag_name):
self.tag_name = tag_name
and the query I'm trying to do is the following:
wallet_tags = WalletData.query.join(Tags, WalletData.tags).all()
for u in wallet_tags:
print(u.tags)
And this is what I got after iterating...
[<Tags: dividends>, <Tags: value>, <Tags: high yield>]
I have tried to follow the SQLAlch docs and there the approach is to use labels. Couldn't find a way to use labels when querying with Models.
Any help will be highly appreciated
Thanks in advance
The corrected query for this needs to be:
wallet_tags = WalletData.query.join(Tags, WalletData.tags).all()
for u in wallet_tags:
print(', '.join([tag.tag_name for tag in u.tags]))
Explanation: There is a many-to-many join between the two main tables (an intermediate table was constructed). The relationship expression on WalletData will pick up a list of connected Tags objects. Iterating through this collection for each WalletData object will yield the desired set of record field names.

SQLAlchemy group_concat and duplicates

When I try to join a many-to-many table and group it by the main-id I am getting duplicates when I add the second many-to-many table.
Here is how my models look like:
Models
user
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
user_fistName = db.Column(db.String(64))
...
student_identifier
student_identifier = db.Table('student_identifier',
db.Column('class_id', db.Integer, db.ForeignKey('class.class_id')),
db.Column('id', db.Integer, db.ForeignKey('user.id'))
)
class
class Class(db.Model):
sqlite_autoincrement=True
class_id = db.Column(db.Integer, primary_key=True)
class_name = db.Column(db.String(128), unique=True)
mm_children = db.relationship('User', secondary=student_identifier, backref=db.backref('classes'))
class_course_identifier
class_course_identifier = db.Table('class_course_identifier',
db.Column('course_id', db.Integer, db.ForeignKey('course.course_id')),
db.Column('class_id', db.Integer, db.ForeignKey('class.class_id'))
)
database structure
Well I am using SQLAlchemy to select the desired tables with the data I want. with this session.query
db.session.query(
Class.class_id,
Class.class_name,
func.group_concat(User.user_fistName),
func.group_concat(Course.course_name)
).filter(Class.courses, User.classes).group_by(Class.class_id)
the problem is that I am getting duplicates of both the courses AND names, so if the course has two users it will print the students and the course two times.
Here is how it is looking:
wrong view
And here is how it should look:
correct view
the problem
the problem is coming when I am adding the second many-to-many table, for example users/student-identifier. If I remove the line where I "join" it, I am getting the duplicates. Is there anyway to correct this? Or should I use RAW-SQL instead(and if yes, how?)
Found out the solution, and it is quite simple.
RAW SQL
SELECT
class.class_id,
class.class_name,
GROUP_CONCAT(DISTINCT course.course_name),
GROUP_CONCAT(DISTINCT user.user_fistName)
FROM
class
JOIN class_course_identifier ON class.class_id = class_course_identifier.class_id
JOIN course ON class_course_identifier.course_id = course.course_id
JOIN student_identifier ON class.class_id = student_identifier.class_id
JOIN user ON student_identifier.id = user.id
GROUP BY class.class_id
SQLAlchemy
db.session.query(
Class.class_id,
Class.class_name,
func.group_concat(User.user_fistName.distinct()),
func.group_concat(Course.course_name.distinct())
).filter(Class.courses, User.classes).group_by(Class.class_id)
Simply add the distinct() to the desired column you want to be unique

Adding a comments field to database with flask/SQLAlchemy?

I'm working on a flask application that has a database that is set up like this and am using SQLAlchemy. (display_name was renamed to persona_name since that's what the steam api calls it.)
I'm still in the process of learning how work with databases, and what I have now works fine, I can track all the lists a user has made, and I can get suspects from all lists that one user has made. Also, removing a suspect from the suspect table also seems to remove the suspect from all lists it appears on.
This seems to work well, List and Suspect are classes that inherit from db.Model while suspect_list is an auxiliary table that is not itself a class.
The way it is set up I want to be able to have a suspect show up on multiple lists, but now I want to be able to add comments for individual suspects on individual lists.
I'm not sure how to go about this, but I have a few ideas and am not sure if they work or would have potential downsides.
1) Can I add a comment field to suspect_list?
2) Do I make a comment model as a class that inherits from db.Model and then add it to the auxiliary table instead?
3) Should I create a new id field for the suspect table, make it the primary key instead of steam_id , and then add a comment field so there can be duplicate suspects that have differing comments?
I probably could implement 3 but I don't think it is a good idea because duplicates of the same suspect probably is something that should be avoided.
As for 1 and 2 I don't know if they would work and if they did I'm not sure how to properly implement them.
This is the code that I have for my Models.py if it is needed
My question is how would I properly add comments to this database model I have set up?
Rather then an association table, you really need an association object. Here's a working example.
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.secret_key = 'MOO.'
app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite://' # In memory.
db = SQLAlchemy(app)
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
def __repr__(self):
return '<User {}>'.format(self.name)
class Suspect(db.Model):
__tablename__ = 'suspect'
id = db.Column(db.Integer, primary_key=True)
steam_name = db.Column(db.String)
def __repr__(self):
return '<Suspect {}>'.format(self.steam_name)
class List(db.Model):
__tablename__ = 'list'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String())
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship('User', backref=db.backref('lists'))
suspects = db.relationship('SuspectList', backref='list')
def __repr__(self):
return '<List {}>'.format(self.name)
class SuspectList(db.Model):
__tablename__ = 'suspect_list'
list_id = db.Column(db.Integer, db.ForeignKey('list.id'), primary_key=True)
suspect_id = db.Column(db.Integer, db.ForeignKey('suspect.id'), primary_key=True)
comment = db.Column(db.String)
suspect = db.relationship('Suspect', backref=db.backref("suspect_list"))
def __repr__(self):
return '<SL: %s %s %s>' % (self.list, self.suspect, self.comment)
if __name__ == '__main__':
with app.app_context():
db.create_all()
hacker = Suspect(steam_name='Bob')
cracker = Suspect(steam_name='Alice')
carol = User(name='Carol')
carols_list = List(name='Carols', user=carol)
hacker_suspect = SuspectList(suspect=hacker, comment='RED HAIR')
cracker_suspect = SuspectList(suspect=cracker, comment='RED EYES')
carols_list.suspects.append(hacker_suspect)
carols_list.suspects.append(cracker_suspect)
db.session.add(carols_list) # Trust SQLAlchemy to add all related.
db.session.commit()
## Querying:
my_list = List.query.filter(User.name == 'Carol').first()
for record in my_list.suspects:
print '{} reported {} with comment {}'.format(
record.list.user.name,
record.suspect.steam_name,
record.comment
)
# Carol reported Bob with comment RED HAIR
# Carol reported Alice with comment RED EYES
Two side-notes-- I found it a bit ugly to be use List as a class name, because when you want to query it, your default name would be list = List.query.fi.... which is a bit of a no-go :). Also, what program did you use to generate your ERD?
if i correctly understand, i would just create a new model related to your List Model
class Comments(db.Model):
id = db.Column(db.Integer, primary_key=True)
comment = db.Column(db.String(300))
list_id = db.Column(db.Integer, db.ForeignKey('list.id'))

SQLAlchemy DELETE Error caused by having a both lazy-load AND a dynamic version of the same relationship

Here is some example code:
users_groups = Table('users_groups', Model.metadata,
Column('user_id', Integer, ForeignKey('users.id')),
Column('group_id', Integer, ForeignKey('groups.id'))
)
class User(Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
class Group(Model):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
users = relationship('User', secondary=users_groups, lazy='select', backref='groups')
users_dynamic = relationship('User', secondary=users_groups, lazy='dynamic')
So what happens here is that if you add a bunch of users to a group like so:
g = Group()
g.users = [User(), User(), User()]
session.add(g)
session.commit()
and then try to delete the group
session.delete(g)
session.commit()
You will get some form of this error:
DELETE statement on table 'users_groups' expected to delete 3 row(s); Only 0 were matched.
Removing the 2nd version of the relationship (the dynamic one in my case) fixes this problem. I am not even sure where to begin in terms of understanding why this is happening. I have been using 2 versions of various relationships in many cases throughout my SQLAlchemy models in order to make it easy to use the most appropriate query-strategy given a situation. This is the first time it has caused an unexpected issue.
Any advice is welcome.
both the Group.users and Group.users_dynamic relationships are attempting to reconcile the fact that the Group is being deleted along with being able to manage the User() objects they refer to; one relationship succeeds while the second one fails, as the rows in the association table were already deleted. The most straightforward solution is to mark all but one of the identical relationships as viewonly:
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
users = relationship('User', secondary=users_groups, lazy='select', backref='groups')
users_dynamic = relationship('User', viewonly=True, secondary=users_groups, lazy='dynamic')
if you're still wanting to have both relationships handle some degree of mutations, you'd need to do this carefully as SQLAlchemy doesn't know how to coordinate among changes in two relationships at the same time, so conflicts like this can continue to happen (like double inserts, etc) if you make equivalent mutations on both relationships. To just take care of the "delete" issue by itself, you can also try setting Group.users_dynamic to passive_deletes=True:
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
users = relationship('User', secondary=users_groups, lazy='select', backref='groups')
users_dynamic = relationship('User', passive_deletes=True, secondary=users_groups, lazy='dynamic')
I just add another simple workaround.
You can delete the collections before deleting the item itself:
>>> for user in group.users:
group.users.remove(user)
>>> db.session.delete(group)
>>> db.session.commit()
Alternatively, you can also set it as an empty list:
>>> group.users = []
>>> db.session.commit()
>>> db.session.delete(group)
>>> db.session.commit()

Sqlalchemy - how to get an object, and filter which rows in the child class

I’m new to Sqlalchemy, and in need of some help.
i have a model, with a one to many relation:
class Metnadev(DeclarativeBase):
__tablename__ = 'metnadev'
#personal info
id = Column(Integer, autoincrement=True, primary_key=True)
first_name = Column(Unicode(255))
last_name = Column(Unicode(255))
birth_day = Column(Date)
activitys = relationship("Activity", backref="metnadev")
class Activity(DeclarativeBase):
__tablename__ = 'activity'
id = Column(Integer, autoincrement=True, primary_key=True)
metnadev_id = Column(Integer, ForeignKey('metnadev.id'))
location = Column(Unicode(255))
visible = Column(Boolean,default=True)
When I do
metnadev = DBSession.query(Metnadev).filter_by(id=kw['id']).one()
I get the object, with the child, great.
I want to get the object, but only get the rows from the child class, where visible == True
I searched but I’m not sure how to do it,
Thanks for the help
This section of the documentation has your answer: http://docs.sqlalchemy.org/en/rel_0_6/orm/relationships.html#building-query-enabled-properties
class Metnadev(DeclarativeBase):
#...
#property
def activities(self):
return object_session(self).query(Activity).filter_by(visible=True).with_parent(self).all()
There's a couple ways you can do this.
For a one-off, you can just run a second query:
from sqlalchemy import and_
activities = Activity.query.filter(and_(Activity.metnadev_id == kw['id'], Activity.visible==True)).all()
You can change the relationship so only visible items are returned:
activities = relationship("Activity",
primaryjoin="and_(Activity.metnadev_id==Metnadev.id, "
"Activity.visible==True)")
If you need more control you can join the tables, but it sounds like the relationship configuration would work for you. Let me know if that's not the case.
Hope that helps!

Categories

Resources