How to obtain data from a table that has been joined - python

I have two tables items and games.
#app.route('/collection/<username>/<int:page>/<platform>/<path:path>')
def collection(username, page=1, platform='DMG', path=None):
# first I get the user by his username
user = User.query.filter_by(username=username).first()
# then I get all items form the user and related games
items = user.items.join(Game)
# until now it works perfectly fine
# now I would like to obtain all titles from the joined table games
game_titles = items.filter(Game.title).all()
# but unfortunately I get only an empty list
What is missing?
Here my models:
class Game(db.Model):
__tablename__ = 'games'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(64), index=True)
publisher = db.Column(db.String(32), index=True)
region = db.Column(db.String(3), index=True)
code_platform = db.Column(db.String(3), index=True)
code_identifier = db.Column(db.String(4), index=True)
code_region = db.Column(db.String(3), index=True)
code_revision = db.Column(db.String(1))
code = db.Column(db.String(16), index=True, unique=True)
year = db.Column(db.Integer)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
items = db.relationship('Item', backref='game', lazy='dynamic')
def __repr__(self):
return '<Game %r>' % (self.title)
class Item(db.Model):
__tablename__ = 'items'
id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(8), index=True)
cart = db.Column(db.Boolean)
box = db.Column(db.Boolean)
manual = db.Column(db.Boolean)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
game_id = db.Column(db.Integer, db.ForeignKey('game.id'))
def __repr__(self):
return '<Collection %r>' % (self.user_id)

You have two options. Using SQLAlchemy ORM:
game_titles = [i.game.title for i in user.items]
To make this more efficient, you can apply the joinedload optimization:
game_titles = [i.game.title for i in user.items.options(joinedload(Item.game))]
Alternatively, you can use SQLAlchemy core if all you care about are the titles (and nothing else):
game_titles = user.items.join(Item.game).with_entities(Game.title).all()
You can even skip fetching the user altogether if you don't care about the user at all:
game_titles = User.query.join(User.items).join(Item.game).filter(User.username == username).with_entities(Game.title).all()
As an aside, .filter and .filter_by correspond to the selection operator in relational algebra, whereas .with_entities and db.session.query(...) correspond to the projection operator, contrary to what you had initially assumed.

Try something like this:
items.join(Game).options(joinedload(Item.game, innerjoin=True))
Essentially, you're joining with Game and explicitly loading it, where the innerjoin forces it to do so only on the games listed in the table you're joining with (items)

Related

Left join in (flask)sqlalchemy with getting unmatched values and filter on the right table

I want to get a list of all assignments, with the progress of the user (the UserAssignments table) also in the result set. That means there should be a join between the assignments and userassignments table (where the assignmentid is equal), but also a filter to check if the progress is from the current user. The diagram of the database and the actual models are listed below.
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), index=True, unique=True, nullable=False)
password_hash = db.Column(db.String(128), nullable=False)
roleid = db.Column(db.Integer, db.ForeignKey('role.roleid'), nullable=False)
groups = db.relationship('Group', secondary=users_groups, lazy='dynamic')
assignments = db.relationship('Assignment', secondary=users_assignments, lazy='dynamic')
class Assignment(db.Model):
assignmentid = db.Column(db.Integer, primary_key=True)
assignmentname = db.Column(db.String(128))
assignmentranking = db.Column(db.Integer)
assignmentquestion = db.Column(db.String, nullable=False)
def __repr__(self):
return '<Assignment {}>'.format(self.assignmentid)
class UserAssignments(db.Model):
__tablename__ = 'user_assignments'
userid = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
assignmentid = db.Column(db.Integer, db.ForeignKey('assignment.assignmentid'), primary_key=True)
status = db.Column(db.Integer)
progress = db.Column(db.String)
def __repr__(self):
return '<UserAssignments {}>'.format(self.userid, self.assignmentid)
diagram
I tried the following query, but that resulted only the assignments with a matched userassignment (progress). (the userid is given into the function)
results = db.session.query(Assignment, UserAssignments).join(UserAssignments, (UserAssignments.assignmentid == Assignment.assignmentid)&(UserAssignments.userid==userid), isouter=True).filter(UserAssignments.userid==userid).all()
I also tried the query without the filter, but that resulted in all userassignments (also from other users).
results = db.session.query(Assignment, UserAssignments).join(UserAssignments, (UserAssignments.assignmentid == Assignment.assignmentid)&(UserAssignments.userid==userid), isouter=True).all()
As said earlier, I want to achieve a result with all assignments listed, with the userassignment included when there is one for the current user.
try next query
results = db.session.query(
Assignment,
UserAssignments,
).join(
UserAssignments,
UserAssignments.assignmentid == Assignment.assignmentid,
isouter=True,
).filter(
or_(
UserAssignments.userid == userid,
UserAssignments.userid.is_(None),
)
).all()

How to access column values in SQLAlchemy result list after a join a query

I need to access colums of result query. I have these models
class Order(Base):
__tablename__ = "orders"
internal_id = Column(Integer, primary_key=True)
total_cost = Column(Float, nullable=False)
created_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=text("now()"))
customer_id = Column(Integer, ForeignKey("customers.id", ondelete="CASCADE"), nullable=False)
customer = relationship("Customer")
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, nullable=False)
internal_id = Column(Integer, nullable=False)
price = Column(Float, nullable=False)
description = Column(String, nullable=False)
order_id = Column(Integer, ForeignKey("orders.internal_id", ondelete="CASCADE"), nullable=False)
order = relationship("Order")
Now I run this left join query that gives me all the columns from both tables
result = db.query(Order, Item).join(Item, Item.order_id == Order.internal_id, isouter=True).filter(Item.order_id == order_id).all()
I get back a list of tuples. How do I access a particular column of the result list? Doing something like this
for i in result:
print(i.???) # NOW WHAT?
Getting AttributeError: Could not locate column in row for column anytime i try to fetch it by the name I declared.
this is the full function where I need to use it
#router.get("/{order_id}")
def get_orders(order_id: int, db: Session = Depends(get_db)):
""" Get one order by id. """
# select * from orders left join items on orders.internal_id = items.order_id where orders.internal_id = {order_id};
result = db.query(Order, Item).join(Item, Item.order_id == Order.internal_id, isouter=True).filter(Item.order_id == order_id).all()
for i in result:
print(i.description) # whatever value i put here it errors out
This is the traceback
...
print(i.description) # whatever value i put here it errors out
AttributeError: Could not locate column in row for column 'description'
At least if I could somehow get the column names.. But i just cant get them. Trying keys(), _metadata.keys .. etc. Nothing works so far.
If additional implicite queries are not an issue for you, you can do something like this:
class Order(Base):
__tablename__ = "orders"
internal_id = Column(Integer, primary_key=True)
total_cost = Column(Float, nullable=False)
created_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=text("now()"))
customer_id = Column(Integer, ForeignKey("customers.id", ondelete="CASCADE"), nullable=False)
customer = relationship("Customer")
items = relationship("Item", lazy="dynamic")
order = session.query(Order).join(Item, Order.internal_id == Item.order_id, isoutrr=True).filter(Order.internal_id == order_id).first()
if order:
for i in order.items:
print(i.description)
print(order.total_cost)
However to avoid additional query when accessing items you can exploit contains_eager option:
from sqlalchemy.orm import contains_eager
order = session.query(Order).join(Item, Order.internal_id == Item.order_id, isoutrr=True).options(contains_eager("items").filter(Order.internal_id == order_id).all()
Here you have some examples: https://jorzel.hashnode.dev/an-orm-can-bite-you
Ok, so acctualy the answer is quite simple. One just simply needs to use dot notation like i.Order.total_cost or whichever other field from the Order model
result = db.query(Order, Item).join(Item, Item.order_id == Order.internal_id, isouter=True).filter(Item.order_id == order_id).all()
for i in result:
print(i.Order.total_cost)
print(i.Item.description)

SQLAlchemy - How to correctly connect two sets of data?

I am hoping for some guidance about what I believe is going to be a common pattern in SQLAlchemy for Python. However, I have so far failed to find a simple explanation for someone new to SQLAlchemy.
I have the follow objects:
Customers
Orders
Products
I am building a Python FastAPI application and I want to be able to create customers, and products individually. And subsequently, I want to then be able to create an order for a customer that can contain 1 or more products. A customer will be able to have multiple orders also.
Here are my SQLAlchemy models:
order_products = Table('order_products', Base.metadata,
Column('order_id', ForeignKey('orders.id'), primary_key=True),
Column('product_id', ForeignKey('products.id'), primary_key=True)
)
class Customer(Base):
__tablename__ = "customers"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
address = Column(String)
phonenumber = Column(String)
email = Column(String, unique=True, index=True)
is_active = Column(Boolean, default=True)
orders = relationship("Order", back_populates="customers")
class Order(Base):
__tablename__ = "orders"
id = Column(Integer, primary_key=True, index=True)
ordernumber = Column(String, index=True)
customer_id = Column(Integer, ForeignKey("customers.id"))
customers = relationship("Customer", back_populates="orders")
products = relationship("Product", secondary="order_products", back_populates="orders")
class Product(Base):
__tablename__ = "products"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
size = Column(Integer)
order_id = Column(Integer, ForeignKey("orders.id"))
orders = relationship("Order", secondary="order_products", back_populates="products")
And here are my CRUD operations:
def create_customer(db: Session, customer: customer.CustomerCreate):
db_customer = models.Customer(name = customer.name, address = customer.address, email=customer.email, phonenumber=customer.phonenumber)
db.add(db_customer)
db.commit()
db.refresh(db_customer)
return db_customer
def create_product(db: Session, product: product.Productreate):
db_product = models.Product(name = product.name, size = product.size)
db.add(db_product)
db.commit()
db.refresh(db_product)
return db_product
def create_order(db: Session, order: order.OrderCreate, cust_id: int):
db_order = models.Order(**order.dict(), customer_id=cust_id)
db.add(db_order)
db.commit()
db.refresh(db_order)
return db_order
def update_order_with_product(db: Session, order: order.Order):
db_order = db.query(models.Order).filter(models.Order.id==1).first()
if db_order is None:
return None
db_product = db.query(models.Order).filter(models.Product.id==1).first()
if db_order is None:
return None
db_order.products.append(db_product)
db.add(db_order)
db.commit()
db.refresh(db_order)
return db_order
All of the CRUD operations work apart from update_order_with_product which gives me this error:
child_impl = child_state.manager[key].impl
KeyError: 'orders'
I'm not sure if I am taking the correct approach to the pattern needed to define the relationships between my models. If not, can someone point me in the right direction of some good examples for a beginner?
If my pattern is valid then there must be an issue with my CRUD operation trying to create the relationships? Can anyone help with that?
This query could be a problem:
db_product = db.query(models.Order).filter(models.Product.id==1).first()
Should probably be:
db_product = db.query(models.Product).filter(models.Product.id==1).first()
because you want to get a Product instance, not Order.
When you update a record you should not add it to the session (because it has been registered to the session when you queried the record).
def update_order_with_product(db: Session, order: order.Order):
db_order = db.query(models.Order).filter(models.Order.id==1).first()
if db_order is None:
return None
db_product = db.query(models.Product).filter(models.Product.id==1).first()
if db_product is None:
return None
db_order.products.append(db_product)
db.commit()
db.refresh(db_order)
return db_order

Flask-Sqlalchemy: filter rows where column contains a certain object

I have the following database setup:
abcs = db.Table('abcs',
db.Column('hero_id', db.Integer, db.ForeignKey('hero.id')),
db.Column('player_id', db.Integer, db.ForeignKey('player.id')),
db.Column('game_id', db.Integer, db.ForeignKey('game.id'))
)
class Player(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(250), nullable=False)
account_id = db.Column(db.String(250), nullable=False, unique=True)
class Hero(db.Model):
id = db.Column(db.Integer, primary_key=True)
hero_id = db.Column(db.Integer, unique=True)
localized_name = db.Column(db.String)
url_small_portrait = db.Column(db.String)
url_large_portrait = db.Column(db.String)
url_full_portrait = db.Column(db.String)
url_vertical_portrait = db.Column(db.String)
class Game(db.Model):
id = db.Column(db.Integer, primary_key=True)
mmr = db.Column(db.Integer)
server_steam_id = db.Column(db.Integer)
match_id = db.Column(db.Integer)
lobby_id = db.Column(db.Integer)
activate_time = db.Column(db.Integer)
deactivate_time = db.Column(db.Integer)
duration = db.Column(db.Integer)
has_pro = db.Column(db.Integer, default=-1)
radiant_win = db.Column(db.Integer)
players = db.relationship("Player", secondary = abcs, lazy='subquery',
backref=db.backref('games', lazy=True))
heroes = db.relationship("Hero", secondary = abcs, lazy='subquery',
backref=db.backref('games', lazy=True))
Which means whenever I add an entry to the table 'Game', Game.players will return a list with objects of the type 'Player'.
Now what I would like to do is to make a query of the form
p = Player.query.filter(P.account_id == 12345).first()
games = Game.query.filter(*get all games where Game.players contain p*).all()
My first naive approach for the 2nd line was
games = Game.query.filter(Game.players.contains(p)).all()
and that DOES return all entries from 'Game' where Game.players contains p, BUT for some reason, using .contains changes the order of elements in every row's .players, so for example the list
games[0].players
has now a different order compared to the situation where I would query that particular game directly.
So basically, I'm asking how I can achieve what I would like to do - how can I query all games in the table Game that contain a specific Player?
Use order_by in your queries to guarantee you get a predictable order. For example
games = Game.query.filter(Game.players.contains(p)).order_by(Game.some_field).all()

join tables in sqlalchemy to get a many relationship in a query for a flask app

How do i query all of the manys-- 2 tables away from my groupby variable?
My data is structured like this (even though my database allows many-to-many everywhere)
my data is like a bowtie
syntax \......................................../ Whenz
syntax --Clump--Clumpdetail -- Whenz
syntax /..........................................\ Whenz
For each syntax.filename I want all of the Whenz.freq
my model
clump_syntaxs = db.Table('clump_syntaxs',
db.Column('syntax_id', db.Integer, db.ForeignKey('syntax.id')),
db.Column('clump_id', db.Integer, db.ForeignKey('clump.id')),
)
clump_deets = db.Table('clump_deets',
db.Column('clumpdetail_id', db.Integer, db.ForeignKey('clumpdetail.id')),
db.Column('clump_id', db.Integer, db.ForeignKey('clump.id')),
)
when_deets = db.Table('when_deets',
db.Column('clumpdetail_id', db.Integer, db.ForeignKey('clumpdetail.id')),
db.Column('whenz_id', db.Integer, db.ForeignKey('whenz.id')),
)
class Whenz(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(150))
freq = db.Column(db.String(150))
frequency = db.relationship('Clumpdetail', secondary=when_deets,
backref=db.backref('frequency', lazy='dynamic'))
def __repr__(self):
return str(self.title)
class Clumpdetail(db.Model):
id = db.Column(db.Integer, primary_key=True)
whenhour = db.Column(db.Time())
wherevar = db.Column(db.String(150))
cat = db.Column(db.String(150))
clumps = db.relationship('Clump', secondary=clump_deets,
backref=db.backref('deets', lazy='dynamic'))
def __repr__(self):
return str(self.cat)
class Clump(db.Model):
id = db.Column(db.Integer, primary_key=True)
clumpname = db.Column(db.String(150), unique=True)
ordervar = db.Column(db.Integer)
syntaxs = db.relationship('Syntax', secondary=clump_syntaxs,
backref=db.backref('clumps', lazy='dynamic'),order_by="Syntax.position",
collection_class=ordering_list('position'))
def __repr__(self):
return str(self.clumpname)
class Syntax(db.Model):
id = db.Column(db.Integer, primary_key=True)
filename = db.Column(db.String(150), unique=True,)
jobs = db.relationship('Jobs',lazy='dynamic', backref='jobhistory')
position = db.Column(db.Integer)
def __repr__(self):
return str(self.filename)
.... cut.
my attempt of many
joblist=db.session.query((models.Syntax.filename).label('filename'),\
(models.Clump.clumpname).label('clumpname'),\
(models.Clumpdetail.whenhour).label('hour'),\
(models.Clumpdetail.wherevar).label('where'),\
(models.Clumpdetail.cat).label('cat'),\
(models.Whenz.freq).label('freq'))\
.outerjoin((models.Syntax.clumps,models.Clump))\
.outerjoin((models.Clump.deets,models.Clumpdetail))\
.outerjoin((models.Clumpdetail.frequency,models.Whenz))\
.group_by(models.Syntax.filename).all()
I would expect this line
(models.Whenz.freq).label('freq'))\
to give me all of the whenz, but it only gives me one. Why is that?
I am able to get what i want using
models.Syntax.query.filter_by(filename=models.Syntax.query.first()\
.filename).first()\
.clumps.first()\
.deets.first()\
.frequency.all()
but this is just one by one, and i want it by syntax.filename
Thank you
This is because of the group_by. If you group by syntax.filename, you'll only have one row for each unique filename, so you won't get all of the freqs back.
If you want a SQLAlchemy core solution, remove the group_by and group the results yourself in python.
If you want a SQLAlchemy ORM solution, you can use subqueryload:
Syntax.query.options(subqueryload(Syntax.clumps).subqueryload(Clump.deets).subqueryload(Clumpdetail.frequency)).all()
Note: I believe you'll have to remove the lazy="dynamic" on Syntax.clumps, Clump.deets, and Clumpdetail.frequency.

Categories

Resources