Correlated subquery in SQLAlchemy - python

I have the following query:
SELECT ID, (SELECT SUM(AMOUNT) FROM PURCHASE_PAYMENTS WHERE PURCHASE_PAYMENTS.PURCHASE_ID
= PURCHASES.ID) AS PAID
-> FROM PURCHASES;
which works as I expect. I have problems translating this to SQLAlchemy. My mapped classes are ( I only show the fields of interest):
class Purchase(Base):
__tablename__ = 'purchases'
id = Column(Integer, primary_key=True)
class PurchasePayment(Base):
__tablename__ = 'purchase_payments'
id = Column(Integer, primary_key=True)
purchase_id = Column(Integer, ForeignKey('purchases.id'))
amount = Column(Numeric(19, 4), nullable=False)
Also, I need the results to be like,
(models.db.Purchase object at 0x0000021DD3683D..., Decimal('1234'))
I tried unsuccessfully to follow this post Can we make correlated queries with SQLAlchemy.
Any help?

Related

Flask SQLAlchemy: Child table with multiple parents?

I'm new to flask_sqlalchemy, and while I understand how the one-to-many, and many-to-many relationships work, I'm struggling to understand how I can apply them to my specific data types. I have the following three tables: TeamStat, PlayerStat, and Stat which are loosely described as follows
class PlayerStat(db.Model):
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime, nullable=False, default=datetime.datetime)
player_id = db.Column(db.Integer, db.ForeignKey('player.player_id'), nullable=False)
class TeamStat(db.Model):
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.DateTime, nullable=False, default=datetime.datetime)
team_id = db.Column(db.Integer, db.ForeignKey('team.team_id'), nullable=False)
class Stat(db.Model):
id = db.Column(db.Integer, primary_key=True)
value = db.Column(db.String(80), nullable=False)
Since Stat is a generic table type, I would like to use it by both the PlayerStat table (for individual player stats), as well as the TeamStat table (as a sum of the stats for all players on a team). Can someone help me understand how I can refer one child table to multiple parent tables in this fashion?
Use association tables to bridge your stat table to your players and team tables. This example is pretty close to what you are already doing, except the date column is moved on to the stat record table and I've replaced your PlayerStat and TeamStat objects with unmapped tables.
I've assumed you have two ORM classes, Player and Team (not Flask-SQLAlchemy sorry but concept remains the same):
plr_stat_assc = Table('plr_stat_assc', Base.metadata,
Column('player_id', Integer, ForeignKey('player.id')),
Column('stat_id', Integer, ForeignKey('stat.id'))
)
team_stat_assc = Table('team_stat_assc', Base.metadata,
Column('team_id', Integer, ForeignKey('team.id')),
Column('stat_id', Integer, ForeignKey('stat.id'))
)
class Player(Base):
__tablename__ = 'player'
id = Column(Integer, primary_key=True)
stats = relationship("Stat", secondary=plr_stat_assc)
class Team(Base):
__tablename__ = 'team'
id = Column(Integer, primary_key=True)
stats = relationship("Stat", secondary=team_stat_assc)
class Stat(Base):
__tablename__ = 'stat'
id = Column(Integer, primary_key=True)
date = Column(DateTime, nullable=False, default=datetime.datetime)
value = Column(String(80), nullable=False)

Query specific columns of type relationships in sqlalchemy

I have a table with some text fields and a lot of many to many relationships. I'm having trouble querying only some of the fields. Here's how I query the table:
with db.session(raise_err=True) as session:
result = session.query(
ClientKnowledge
).options(
*[defaultload(getattr(ClientKnowledge, table.table.name))
.load_only(KqFactory().get_tables([table.table.name])[0].get_pk().name)
for table in ClientKnowledge.__mapper__.relationships]
).filter(
ClientKnowledge.id == 1
)
This works fine, SQLAlchemy is doing all the joins for me and uses the lazy=joinedload param well.
When I specify fields in the query parameters I have to do all the joins myself, but I don't want that; also tried to use with_entities() and to make the query from the other side of the relationship, but got the same result.
Is there a way I can query only specific fields without losing SQLAlchemy's ability to make the joins for me?
PS: I can't really give a complete working example since all my tables are dynamically generated. Please tell me if more context is needed.
EDIT:
here is an example of how my tables looks like
#as_declarative()
class RightTable1:
id = Column(Integer, primary_key=True)
desc = Text()
#as_declarative()
class RightTable2:
id = Column(Integer, primary_key=True)
desc = Text()
#as_declarative()
class Association1:
__tablename__ = 'association1'
left_id = Column(Integer, ForeignKey(ClientKnowledge.rel_field1), primary_key=True)
right_id = Column(Integer, ForeignKey(RightTable1.id), primary_key=True)
#as_declarative()
class Association2:
__tablename__ = 'association2'
left_id = Column(Integer, ForeignKey(ClientKnowledge.rel_field2), primary_key=True)
right_id = Column(Integer, ForeignKey(RightTable2.id), primary_key=True)
#as_declarative()
class ClientKnowledge:
id = Column(Integer, primary_key=True)
text_field = Text()
rel_field1 = relationship(
kq_table, secondary=Association1.__table__, lazy='joined',
backref=backref(RightTable1.__table__.name, lazy='joined')
)
rel_field2 = relationship(
kq_table, secondary=Association2.__table__, lazy='joined',
backref=backref(RightTable2.__table__.name, lazy='joined')
)
what i want is being able to query ClientKnowledge.rel_field1.
I have tried:
session.query(ClientKnowledge.rel_field1)
But i have to do all the join myself.

Generated queries contain redundant products?

I have rather simple models like these:
TableA2TableB = Table('TableA2TableB', Base.metadata,
Column('tablea_id', BigInteger, ForeignKey('TableA.id')),
Column('tableb_id', Integer, ForeignKey('TableB.id')))
class TableA(Base):
__tablename__ = 'TableA'
id = Column(BigInteger, primary_key=True)
infohash = Column(String, unique=True)
url = Column(String)
tablebs = relationship('TableB', secondary=TableA2TableB, backref='tableas')
class TableB(Base):
__tablename__ = 'TableB'
id = Column(Integer, primary_key=True)
url = Column(String, unique=True)
However, sqla generates queries like
SELECT "TableB".id, "TableB".url AS "TableB_url" FROM "TableB", "TableA2TableB"
WHERE "TableA2TableB".tableb_id = "TableB".id AND "TableA2TableB".tablea_id = 408997;
But why is there a cartesian product in the query when the attributes selected are those in TableB? TableA2TableB shouldn't be needed.
Thanks
As it is right now, there is a backref relationship in TableB (tableas) and it's loaded because the default loading mode is set to select.
You may want to change the TableA.tablebs to
tablebs = relationship('TableB', secondary=TableA2TableB, backref='tableas', lazy="dynamic")

Long chained exists query with multiple one-to-many mappings in the chain

Edit: Following piece seems to be the right way:
session.query(User).join("userGadgets", "gadget", "components","gadgetComponentMetals")
Original:
I have the following tables configured:
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
class Gadget(Base):
__tablename__ = "gadget"
id = Column(Integer, primary_key=True)
brand = Column(String)
class UserGadget(Base):
__tablename__ = "user_gadget"
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
gadget_id = Column(Integer, ForeignKey('gadget.id'), primary_key=True)
user = relationship("User", backref=backref('userGadgets', order_by=user_id))
gadget = relationship("Gadget", backref=backref('userGadgets', order_by=gadget_id))
class GadgetComponent(Base):
__tablename__ = "gadget_component"
id = Column(String, primary_key=True)
gadget_id = Column(Integer,ForeignKey('gadget.id'))
component_maker = Column(String)
host = relationship("Gadget", backref=backref('components', order_by=id))
class ComponentUsingMetal(Base):
__tablename__ = "component_metal"
id = Column(Integer, primary_key=True)
component_id = Column(Integer, ForeignKey('GadgetComponent.id'))
metal = Column(String)
component = relationship("GadgetComponent", backref=backref('gadgetComponentMetals', order_by=id))
I want to find all user names for users who own gadgets having at least one component containing some kind of metal. SQL query for this will be something along the lines of:
SELECT distinct u.name FROM user u join user_gadget ug on (u.id = ug.user_id) join gadget_component gc on (ug.gadget_id = gc.id) join component_metal cm on (gc.id = cm.component_id) order by u.name
I have tried various versions along the line of: session.query(User).filter(User.userGadgets.any(UserGadget.gadget.components.any(GadgetComponent.gadgetComponentMetals.exists())))
I get the below error:
AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with UserGadget.gadget has an attribute 'gadgetComponents'
Any ideas on what I am doing wrong or is there a better way to do this kind of query in SQLAlchemy?
the join() is the better way to go here since any() is going to produce lots of expensive nested subqueries. but the mistake you made with the "any" is using syntax like: UserGadget.gadget.components. SQLAlchemy doesn't continue the namespace of attributes in a series like that, e.g. there is no UserGadget.gadget.components; there is just UserGadget.gadget and Gadget.components, separately. Just like SQL won't let you say, "SELECT * from user_gadget.gadget_id.gadget.component_id" or something, SQLAlchemy needs you to tell it how you want to join together multiple tables that you're querying from. With the any() here it would be something like any(and_(UserGadget.gadget_id == GadgetComponent.gadget_id)), but using JOIN is better in any case.

sqlalchemy: referencing label()'d column in a filter or clauselement

I'm trying to perform a query that works across a many->many relation ship between bmarks and tags with a secondary table of bmarks_tags. The query involves several subqueries and I have a need to DISTINCT a column. I later want to join that to another table via the DISTINCT'd ids.
I've tried it a few ways and this seems closest:
tagid = alias(Tag.tid.distinct())
test = select([bmarks_tags.c.bmark_id],
from_obj=[bmarks_tags.join(DBSession.query(tagid.label('tagid'))),
bmarks_tags.c.tag_id == tagid])
return DBSession.execute(qry)
But I get an error:
⇝ AttributeError: '_UnaryExpression' object has no attribute 'named_with_column'
Does anyone know how I can perform the join across the bmarks_tags.tag_id and the result of the Tag.tid.distinct()?
Thanks
Schema:
# this is the secondary table that ties bmarks to tags
bmarks_tags = Table('bmark_tags', Base.metadata,
Column('bmark_id', Integer, ForeignKey('bmarks.bid'), primary_key=True),
Column('tag_id', Integer, ForeignKey('tags.tid'), primary_key=True)
)
class Tag(Base):
"""Bookmarks can have many many tags"""
__tablename__ = "tags"
tid = Column(Integer, autoincrement=True, primary_key=True)
name = Column(Unicode(255), unique=True)
Something like this should work:
t = DBSession.query(Tag.tid.distinct().label('tid')).subquery('t')
test = select([bmarks_tags.c.bmark_id], bmarks_tags.c.tag_id == t.c.tid)
return DBSession.execute(test)
It is hard to tell what you are trying to accomplish, but since you are using orm anyways (and there is not much reason anymore to go with bare selects in sa these days), you should probably start by establishing a many-to-many relation:
bmarks_tags = Table('bmark_tags', Base.metadata,
Column('bmark_id', Integer, ForeignKey('bmarks.bid'), primary_key=True),
Column('tag_id', Integer, ForeignKey('tags.tid'), primary_key=True)
)
class Tag(Base):
"""Bookmarks can have many many tags"""
__tablename__ = "tags"
tid = Column(Integer, primary_key=True)
name = Column(Unicode(255), unique=True)
class BMark(Base):
__tablename__ = 'bmarks'
bid = Column(Integer, primary_key=True)
tags = relation(Tag, secondary=bmarks_tags, backref="bmarks")
Then get your query and go from there:
query = DBSession.query(BMark).join(BMark.tags)
If not, give us the actual sql you are trying to make sqlalchemy emit.

Categories

Resources