Querying Flask-SQLAlchemy through two table joins - Join not working - mysql

I'm trying to join two tables.
Here are my tables simplified
class Spots(db.Model):
__tablename__ = "spots"
geobaseid = db.Column((db.Integer), primary_key=True,autoincrement=True)
spot = db.Column(db.String(50))
url_slug = db.Column(db.String(50))
region = db.Column(db.String(50))
country = db.Column(db.String(50))
chop = db.Column(db.Integer)
small_wave = db.Column(db.Integer)
flat = db.Column(db.Integer)
big_wave = db.Column(db.Integer)
west_direction = db.Column(db.Integer)
southwest_direction = db.Column(db.Integer)
amount = db.Column(db.Integer)
url_slug = db.Column(db.String(50))
forcast = db.relationship('Forcast_short')
class Forcast_short(db.Model):
__tablename__ = "forcast_short"
id = db.Column((db.Integer), primary_key=True,autoincrement=True)
date = db.Column(db.Date)
geobaseid = db.Column((db.Integer), ForeignKey('spots.geobaseid'))
spot = db.Column(db.String(50))
region = db.Column(db.String(50))
country = db.Column(db.String(50))
latitude = db.Column(db.Numeric(10,8))
longitude = db.Column(db.Numeric(10,8))
deg = db.Column(db.Numeric(65,1))
mps = db.Column(db.Numeric(65,1))
Here's my query that is not working
forcast_query = Forcast_short.query.join(Spots, Spots.geobaseid==Forcast_short.geobaseid).filter(Forcast_short.region == region).all()
What am I doing wrong?
When I run the query I only get results from Forcast_short with and without the filter.
<tbody>
<tr>{%for row in forcast_query%}
<td> {{row.spot}} </td>
<td>{{row.mps}} </td>
<td>{{row.url_slug}} </td>
</tr>
{%endfor%}
</tbody>
This query works in Mysql workbench.
select * from
(
SELECT * FROM sc_db2.forcast_short
) a
join
(
select * from sc_db2.spots
) b
on a.geobaseid = b.geobaseid
;

The way you made your models is quite confusing. In keeping with the documentation of sqlalchemy on Relationship Patterns, which I advise you to read, here is how you should set up a one to many relationship:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
This relationship places a foreign key on the child table referencing the parent. relationship() is then specified on the parent, as referencing a collection of items represented by the child.
ABOUT YOUR QUERY:
After harmonizing your models, your request should look like this:
forcast_query = Forcast_short.query.join(Spots, Spots.geobaseid==Forcast_short.geobaseid).filter(Forcast_short.region == region).filter(Forcast_short.date == date_1).all()

Here's a shorter example which illustrates a one-to-many relationship between spots and forecasts (which I think is what you're trying to do):
from app import db
class Spot(db.Model):
id = db.Column(db.Integer, primary_key=True)
geobaseid = db.Column(db.Integer)
forecasts = db.relationship('Forecast', backref='spot', lazy='dynamic')
class Forecast(db.Model):
id = db.Column(db.Integer, primary_key=True)
region = db.Column(db.String(50))
spot_id = db.Column(db.Integer, db.ForeignKey('spot.id'))
The db object is set up in the app package initialiser, following Miguel Grinberg's pattern:
from flask import Flask
from config import Config
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
migrate = Migrate(app, db)
from app import models
Note that having set up the foreign key relationship, you don't need an explicit join to access the parent's fields from the child object - SQLAlchemy allows you to just use dot notation:
>>> from app import db
>>> from app.models import Spot, Forecast
>>> for spot in Spot.query.all():
... print(spot.id, spot.geobaseid)
...
1 1
2 2
>>> for forecast in Forecast.query.all():
... print(forecast.id, forecast.region, forecast.spot_id, forecast.spot.geobaseid)
...
1 Scotland 2 2
2 England 2 2

Related

SqlAlchemy - make a query where Relationship Attribute is a list

I have two models:
class Profile(Base):
__tablename__ = 'profiles'
id = Column(Integer, primary_key=True)
...
stagesP_list = relationship(
'StageP',
back_populates='profiles_list',
secondary=stageP_profile
)
class Project(Base):
__tablename__ = 'projects'
id = Column(Integer, primary_key=True)
...
stagesP_list = relationship(
'StageP',
back_populates='projects_list',
secondary=stageP_project
)
I need to select Profiles for which at least one value of the Profile.stagesP_list is contained in the project.stagesP_list.
Please help to compose the query or indicate the direction in which to search.
If you have project instance loaded, you can compose the following query:
project = ...
stageP_ids = [obj.id for obj in project.stagesP_list]
query = session.query(Profile).filter(
Profile.stagesP_list.any(StageP.id.in_(stageP_ids))
)
You can also perform joins on the database directly from having only project_id:
query = (
session.query(Profile)
.join(StageP, Profile.stagesP_list)
.join(Project, StageP.projects_list)
.where(Project.id == project_id)
.distinct()
)

Implementation of One-to-One relationship in Flask SQLAlchemy

I am inexperienced programmer. I'd like to add one-to-one relationship CurrencyDefault (that value will be assigned to the field in FlaskForm) between User and Currency:
class User(UserMixin, db.Model):
id = db.Column(db.Integer, db.Sequence('user_id_seq'), primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(64), index=True, unique=True)
pswd_hash = db.Column(db.String(128))
expenses = db.relationship('Expense', backref='user')
currency = db.relationship('Currency', backref='user')
curr_default = ?
# ...
class Currency(db.Model):
id = db.Column(db.Integer, db.Sequence('expense_id_seq'), primary_key=True)
abbr = db.Column(db.String(10))
name = db.Column(db.String(64))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
default = ?
# ...
What I want to achieve is to assign to each user.id one currency.id (one-to-one)
I'd like to ask for some advice what is the best way to make it.
After considering the problem I have some ideas like:
Association Table with uselist=False relationship,
Create a new class CurrencyDefault(id, user_id, currency_id),
Or maybe there is other, better way to achieve it?
I'm very curious of your point of view on this problem.
Implementing solution:
This is how my classes look like now:
class User(UserMixin, db.Model):
id = db.Column(db.Integer, db.Sequence('user_id_seq'), primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(64), index=True, unique=True)
pswd_hash = db.Column(db.String(128))
currency_default_choice = db.Column(db.Integer, b.ForeignKey('currency.id'))
expenses = db.relationship('Expense', backref='user')
currency = db.relationship('Currency', backref='user', foreign_keys="Currency.user_id")
# currency_default = db.relationship(
# 'Currency',
# foreign_keys='User.currency_default_choice',
# backref='currency_default',
# uselist=False,
# )
# ...
class Currency(db.Model):
id = db.Column(db.Integer, db.Sequence('expense_id_seq'), primary_key=True)
abbr = db.Column(db.String(10), b.ForeignKey('currency_official_abbr.abbr'))
name = db.Column(db.String(64))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
currency_default = db.relationship(
'User',
foreign_keys='User.currency_default_choice',
backref='currency_default',
uselist=False,
)
The first problem I find is that I can set Currency.id object created by other_user as currency_default_choice. How to restrict currency_default_choice only to the <Currency> that was created by this user?
What is the difference between setting relationship having foreign key in User class (currency_default_choice = db.Column(db.Integer, b.ForeignKey('currency.id'))) with:
class Currency(db.Model):
# ...
currency_default = db.relationship(
'User',
foreign_keys='User.currency_default_choice',
backref='currency_default',
uselist=False,
)
and setting this relationship on User side with:
class User(db.Model):
# ...
currency_default = db.relationship(
'Currency',
foreign_keys='User.currency_default_choice',
backref='currency_default',
uselist=False,
)
Ad.2. What seems to me is that there is no difference between these two ways because the backref parameter implicates bidirectional behavior so it doesn't matter if I placed db.relationship() in User or Currency class. Is it correct?
Using Python shell I added value to the User.currency_default
>>> app = create_app()
>>> app.app_context().push()
>>> admin = User.query.filter_by(username='admin').first()
<User(id= 1, username = admin, email = admin#admin.com)
>>> currency = Currency.query.filter_by(user=admin)
>>> currency
<flask_sqlalchemy.BaseQuery object at 0x03EA05D0>
>>> currency[0].id
1
>>> admin.currency_default = currency[0]
>>> db.session.commit()
>>> currency[0].currency_default
<User(id= 1, username = admin, email = admin#admin.com)
>>> admin.currency_default_choice
1
and then using Admin Panel after running flask run I wanted to remove introduced value but I got error that I don't understand. Why there is circular dependency between (Currency.currency_default),(User.currency_default) and (User.currency)? I don't understand what is happening. How to fix it?
sqlalchemy.exc.CircularDependencyError: Circular dependency detected.
(ProcessState(OneToManyDP(Currency.currency_default),
<Currency at 0x46542b0>, delete=False),
ProcessState(ManyToOneDP(User.currency_default),
<User at 0x4669b10>, delete=False),
SaveUpdateState(<Currency at 0x46542b0>),
ProcessState(OneToManyDP(User.currency), <User at 0x4669b10>, delete=False),
SaveUpdateState(<User at 0x4669b10>))

How to make this query in sqlalchemy?

SELECT
maintener.*,
(SELECT COUNT(*)
FROM device d
WHERE d.in_stock_maintener_id = maintener.id) AS in_stock_devices
FROM maintener;
I'm creating a report that show all mainteners but i need to show the number of devices that each one of that mainteners has by looking at the devices model reference in_stock_maintener_id;
I have this models in my persist sqlalchemy.
class Maintener(persist.Base):
__tablename__ = 'maintener'
id = Column(Integer, primary_key=True)
name = Column(String(255))
document_number = Column(String(30))
phone_1 = Column(String(12))
phone_2 = Column(String(12))
email = Column(String(255))
class Device(persist.Base):
__tablename__ = 'device'
id = Column(Integer, primary_key=True)
serial = Column(String(45))
in_stock = Column(SmallInteger)
in_stock_maintener_id = Column(ForeignKey(u'maintener.id'), nullable=True, index=True)
in_stock_maintener = relationship(u'Maintener', lazy='noload', \
primaryjoin='Device.in_stock_maintener_id == Maintener.id')
If anyone could help me, i'll be grateful =)
sq = (
session
.query(func.count())
.select_from(Device)
.filter(Device.in_stock_maintener_id == Maintener.id)
).as_scalar()
q = session.query(Maintener, sq.label('in_stock_devices'))
Query above will return an enumerable of tuple(Maintener, Integer).
If you would like to have columns instead (as per your comment), then you can either specify the columns you want in the query implicitly:
q = session.query(Maintener.id, Maintener.name, sq.label('in_stock_devices'))
or if you would like all columns (as in SELECT *), then you could query the Table instead of the mapped entity:
q = session.query(Maintener.__table__, sq.label('in_stock_devices'))
Above I assumed that you use declarative extension.

SQLAlchemy conditional relationship

I have 2 db models like this:
class Movie(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=False)
show_start = db.Column(db.Integer)
show_times = db.relationship('ShowTime')
class ShowTime(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=False)
theatre_id = db.Column(db.String(255))
date_time = db.Column(db.Integer)
movie_id = db.Column(db.Integer, db.ForeignKey('movie.id'))
Is it possible to make the show_times relationship conditional, i.e. it would react to the theatre_id I pass to it and select only show_times with that id?
I need that to pass query params to my REST API built on top of these models, something like this:
http://my-api.com/movies?theatre_id=3
It's done with SQLAlchemy's bindparam() function. A relationship is created for Movie model:
show_times = db.relationship(lambda: ShowTime,
primaryjoin=lambda: db.and_(
Movie.id == ShowTime.movie_id,
ShowTime.theatre_id == db.bindparam('theatre_id')))
Then a query would be something like this:
Movie.query.params(theatre_id=3).options(db.joinedload(Movie.show_times)).all()

sqlalchemy - how to convert query with subquery into relationship

In the code below I want to replace all_holdings in Account with a property called holdings that returns the desired_holdings (which are the holdings representing the latest known quantity which can change over time). I'm having trouble figuring out how to construct the call to relationship.
In addition I'd appreciate any comments on the appropriateness of the pattern (keeping historic data in a single table and using a max date subquery to get most recent), as well as on better alternatives, or improvements to the query.
from sqlalchemy import Column, Integer, String, Date, DateTime, REAL, ForeignKey, func
from sqlalchemy.orm import relationship, aliased
from sqlalchemy.sql.operators import and_, eq
from sqlalchemy.ext.declarative import declarative_base
from db import session
import datetime
import string
Base = declarative_base()
class MySQLSettings(object):
__table_args__ = {'mysql_engine':'InnoDB'}
class Account(MySQLSettings, Base):
__tablename__ = 'account'
id = Column(Integer, primary_key=True)
name = Column(String(64))
all_holdings = relationship('Holding', backref='account')
def desired_holdings(self):
max_date_subq = session.query(Holding.account_id.label('account_id'),
Holding.stock_id.label('stock_id'),
func.max(Holding.as_of).label('max_as_of')). \
group_by(Holding.account_id, Holding.stock_id).subquery()
desired_query = session.query(Holding).join(Account,
Account.id==account.id).join(max_date_subq).\
filter(max_date_subq.c.account_id==account.id).\
filter(Holding.as_of==max_date_subq.c.max_as_of).\
filter(Holding.account_id==max_date_subq.c.account_id).\
filter(Holding.stock_id==max_date_subq.c.stock_id)
return desired_query.all()
def __init__(self, name):
self.name = name
class Stock(MySQLSettings, Base):
__tablename__ = 'stock'
id = Column(Integer, primary_key=True)
name = Column(String(64))
def __init__(self, name):
self.name = name
class Holding(MySQLSettings, Base):
__tablename__ = 'holding'
id = Column(Integer, primary_key=True)
account_id = Column(Integer, ForeignKey('account.id'), nullable=False)
stock_id = Column(Integer, ForeignKey('stock.id'), nullable=False)
quantity = Column(REAL)
as_of = Column(Date)
stock = relationship('Stock')
def __str__(self):
return "Holding(%f, '%s' '%s')"%(self.quantity, self.stock.name, str(self.as_of))
def __init__(self, account, stock, quantity, as_of):
self.account_id = account.id
self.stock_id = stock.id
self.quantity = quantity
self.as_of = as_of
if __name__ == "__main__":
ibm = Stock('ibm')
session.add(ibm)
account = Account('a')
session.add(account)
session.flush()
session.add_all([ Holding(account, ibm, 100, datetime.date(2001, 1, 1)),
Holding(account, ibm, 200, datetime.date(2001, 1, 3)),
Holding(account, ibm, 300, datetime.date(2001, 1, 5)) ])
session.commit()
print "All holdings by relation:\n\t", \
string.join([ str(h) for h in account.all_holdings ], "\n\t")
print "Desired holdings query:\n\t", \
string.join([ str(h) for h in account.desired_holdings() ], "\n\t")
The results when run are:
All holdings by relation:
Holding(100.000000, 'ibm' '2001-01-01')
Holding(200.000000, 'ibm' '2001-01-03')
Holding(300.000000, 'ibm' '2001-01-05')
Desired holdings query:
Holding(300.000000, 'ibm' '2001-01-05')
Following answer provided by Michael Bayer after I posted to sqlalchemy google group:
The desired_holdings() query is pretty complicated and I'm not seeing a win by trying to get relationship() to do it. relationship() is oriented towards maintaining the persistence between two classes, not as much a reporting technique (and anything with max()/group_by in it is referring to reporting).
I would stick #property on top of desired_holdings, use object_session(self) to get at "session", and be done.
See more information on query-enabled properties.