How to use Sqlalchemy ORM to just get related ids not the full record of an association in a many-to-many? - sqlalchemy

Is there a way to just get the related category ids of a many-to-many association - without getting the full related category record?
Code being used currently is:
products = session.query(Product).join(association_table).filter(association_table.c.category_id == category)
for product in products:
print(product.product_id, [item.id for item in product.categories])
With the models:
association_table = Table(
"product_category_association_table",
Base.metadata,
Column("product_id", ForeignKey("product.id"), primary_key=True),
Column("category_id", ForeignKey("category.id"), primary_key=True),
)
class Product(Base):
__tablename__ = "product"
id = Column(Integer, primary_key=True)
categories = relationship(
"Category", secondary=association_table, back_populates="products"
)
product_id = Column(String(length=50), unique=True)
label = Column(String, nullable=False)
class Category(Base):
__tablename__ = "category"
id = Column(Integer, primary_key=True)
products = relationship(
"Product", secondary=association_table, back_populates="categories"
)
active = Column(Boolean, default=True)
label = Column(String, nullable=False)
This emits the initial SQL to get all products.
Then for each loop is does a query to the related category table (which is not needed as all that is wanted is the category ids).
Is this possible to achieve without the join over to the category table?

Related

Import data from a joined table as a current (readable) column in SQLAlchemy?

I have this schema:
class Company(db.Model):
__tablename__ = 'companies'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(250), nullable=True, default=None)
domain = db.Column(db.String(250), nullable=True, default=None)
organization_id = db.Column(db.Integer, db.ForeignKey('organizations.id'), nullable=False)
class Contact(db.Model):
__tablename__ = 'contacts'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(250), nullable=True, default=None)
email = db.Column(db.String(250), nullable=False)
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'), nullable=True, default=None)
company = relationship('Company')
organization_id = db.Column({Import Company.organization_id as eager})
The last line is of course garbage, but it's to show the idea:
I'd like to have the value "organization_id" available in Contact, even though it's not present in the table "contacts", but since it's present in "companies", is there a way to ask SQLAlchemy to load the value from "companies" via a JOIN, and affect it to "contacts" as a read-only value?
That way, when I search for a contact, for instance :
contact = Contact.query.filter(Contact.email = 'test#test.com').first()
print(contact.organization_id) # => 1
Thank you.
You can use the hybrid_property decorator to define an attribute on your class:
class Contact(db.Model):
...
#hybrid_property
def organization_id(self):
return self.company.organization_id if self.company else None
Using contact.organization_id will load the company using the foreign key relationship.

SQLAlchemy conditional many to many relationship with an additional column

This is the way that I usually use for m2m relationship implementation.
(Brought from docs.sqlalchemy.org)
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary=association_table,
backref="parents")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
Is there any way for using additional columns at the association_table table?
So it should be like
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id')),
Column('is_valid', Boolean, default=True) # Add the validation column
)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary=association_table,
backref="parents")
# How can I do implement this??
valid_children = relationship("Child",
secondary="and_(association_table.left_id == Parent.id, association_table.right_id == Child.id)"
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
I want to do query depends on is_valid column. How can I modify "secondary" attr in Parent table? Or should I fix the other part?
In this question, time_create column has the same value for all children. But in this case, I need a flag that makes able to retrieve whether this connection is still alive or not.
For example, if you implement a one-on-one chatting, there will be a chatting room consist of two-person, right?
And the table should be like as below:
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id')),
Column('is_left', Boolean, default=False) # Whether the user left or not
)
class Match(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
user = relationship("User",
secondary=association_table,
backref="matches")
# How can I do implement this??
exist_user = relationship("User",
secondary="and_(association_table.left_id == Parent.id, association_table.right_id == Child.id)"
class User(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
nickname = Column(String, unique=True)
How can I do for this?

SQLAlchemy: How to delete a item on one-to-many relation?

I am using SQLAlchemy, and i want to delete certain item in a one-to-many-relationship. Well there are two pictures for you. First you can see an EER-Model. Its a one (gender) to many (person) relation.
Second, you can see a table with fictional data. In gender-table we have two gender currently, and in person-table we have three persons.
Imagine, you will just delete a gender, let us say 'male'. But we see, that 'male' is used as foreign by person-table.
My current source code looks as follows:
class PERSON_GENDER(Base):
__tablename__ = "person_gender"
id = Column(Integer, primary_key=True, unique=True, autoincrement=True)
gender = Column(String(50), nullable=False, unique=True)
class PERSON(Base):
__tablename__ = "person"
id = Column(Integer, primary_key=True, unique=True, autoincrement=True)
nickname = Column(String(255))
alias_name = Column(String (255))
name_normally_used = Column(String(50), nullable=False)
first_middle_name = Column(String(255))
last_name = Column(String(100))
birth_name = Column(String(100))
body_height = Column(String(10))
wedding_anniversary = Column(Date)
birthday = Column(Date)
day_of_death = Column(Date)
notice = Column(Text())
gender_id = Column(Integer, ForeignKey('person_gender.id', ondelete='CASCADE'))
gender = relationship("PERSON_GENDER", single_parent=True, cascade="all, delete, delete-orphan")
When I run this one, then not only the gender is deleted, but also the person. I want that only the gender is deleted, not the person.
ondelete='CASCADE')=> ondelete='SET NULL')

sql alchemy: return all unique types of great great grandchildren

I have 6 tables. I am essentially trying to return all unique types of great great grandchildren
How do I return a list of all the unique types of sku_numbers in a FreightDomesticOrder?
Table Definitions:
class FreightOrderDomestic(db.Model):
"""
A shipment of products from a manufacturer to a fulfillment center
"""
__tablename__ = 'Freight_Order_Domestic'
id = db.Column(db.Integer, primary_key=True, nullable=False)
class Pallet(db.Model):
"""
An individual Pallet (full of individual cases)
"""
__tablename__ = 'Pallet'
id = db.Column(db.Integer, primary_key=True, nullable=False)
freight_order_fkey = db.ForeignKey("Freight_Order_Domestic.id")
freight_order_id = db.Column(db.Integer, freight_order_fkey, nullable=False)
class OuterCase(db.Model):
"""
An outer case (full of inner cases)
"""
__tablename__ = 'Outer_Case'
id = db.Column(db.Integer, primary_key=True, nullable=False)
pallet_fkey = db.ForeignKey("Pallet.id")
pallet_id = db.Column(db.Integer, pallet_fkey, nullable=False)
class InnerCase(db.Model):
"""
An individual case (full of individual items)
"""
__tablename__ = 'Inner_Case'
id = db.Column(db.Integer, primary_key=True, nullable=False)
outer_case_fkey = db.ForeignKey("Outer_Case.id")
outer_case_id = db.Column(db.Integer, outer_case_fkey, nullable=False)
class Each(db.Model):
"""
An individual item
"""
__tablename__ = 'Each'
id = db.Column(db.Integer, primary_key=True, nullable=False)
inner_case_fkey = db.ForeignKey("Inner_Case.id")
inner_case_id = db.Column(db.Integer, inner_case_fkey, nullable=False)
sku_fkey = db.ForeignKey("Sku.id")
sku_id = db.Column(db.Integer, sku_fkey, nullable=False)
class Sku(db.Model):
"""
The SKU of an product, the attributes it should have to determine pricing
"""
__tablename__ = 'Sku'
id = db.Column(db.Integer, primary_key=True, nullable=False)
sku_number = db.Column(db.String(255), nullable=False)
Here is what I am trying so far but I am stuck, I am also wondering how cheap I can make this:
SKUs = Session.query(Pallet, Outer_case, Inner_case, Each, Sku).filter(Pallet.id == Outer_case.pallet_id).filter(Outer_case.id == Inner_case.outer_case_id).filter(Inner_case.id == Each.inner_case_id).filter(Each.sku_id == sku.id).all()
My other idea was to loop through all Pallets and then Outer_cases and so on but that seems too expensive.
Edited post after table definitions:
Given your table definitions, this should work:
SKUs = session.query(Sku.sku_number)
.join(Each).join(InnerCase)
.join(OuterCase).join(Pallet)
.join(FreightOrderDomestic)
.filter(FreightOrderDomestic.id == myOrderNumber)
.group_by(Sku).all()
However, looking at your table definitions I have some other comments that will hopefully help:
You should setup relationships between the tables, so you can easily work with the different objects. Check out the sqlalchemy documentation on relationships here
I would suggest reading up on Database Normalization. This will help you understand some of the below points
You currently have Each setup so there will be duplicate items of the same type if they are assigned to different InnerCases. This is not a good database practice. You should setup this relationship as a many to many relationship, and you can read about that here. This will allow you to have a list of items, and each item can link to many different InnerCases
FreightDomesticOrder should have a column for order number. You don't want to use a key value as an order number
If you are going to handle international orders also, you should probably just create a type field for FreightOrderDomestic and rename it to FreightOrder
Original Post:
Can you provide the table definitions? This is very hard to answer accurately without seeing the relationships you have setup. Something like this could work, if you setup your tables like I would have given the description you gave, or it could not work because you didn't provide enough info:
SKUs = session.query(Sku.sku_number)
.join(Each).join(Inner_case)
.join(Outer_case).join(Pallet)
.join(Freight_order)
.filter(Freight_order.order_number == myOrderNumber)
.group_by(Sku).all()

Querying multiple joined inherited tables filtered by a many-to-many relationship

I have an SQLAlchemy scheme that looks roughly like this:
participation = db.Table('participation',
db.Column('artist_id', db.Integer, db.ForeignKey('artist.id'),
primary_key=True),
db.Column('song_id', db.Integer, db.ForeignKey('song.id'),
primary_key=True),
)
class Streamable(db.Model):
id = db.Column(db.Integer, primary_key=True)
kind = db.Column(db.String(10), nullable=False)
score = db.Column(db.Integer, nullable=False)
__mapper_args__ = {'polymorphic_on': kind}
class Artist(Streamable):
id = db.Column(db.Integer, db.ForeignKey('streamable.id'), primary_key=True)
name = db.Column(db.Unicode(128), nullable=False)
__mapper_args__ = {'polymorphic_identity': 'artist'}
class Song(Streamable):
id = db.Column(db.Integer, db.ForeignKey('streamable.id'), primary_key=True)
name = db.Column(db.Unicode(128), nullable=False)
artists = db.relationship("Artist", secondary=participation,
backref=db.backref('songs'))
__mapper_args__ = {'polymorphic_identity': 'song'}
class Video(Streamable):
id = db.Column(db.Integer, db.ForeignKey('streamable.id'), primary_key=True)
song_id = db.Column(db.Integer, db.ForeignKey('song.id'), nullable=False)
song = db.relationship('Song', backref=db.backref('videos', lazy='dynamic'),
primaryjoin="Song.id==Video.song_id")
__mapper_args__ = {'polymorphic_identity': 'video'}
I'd like to do a single query for Songs or Videos that have a particular artist; i.e., these two queries in one query (all queries should be .order_by(Streamable.score)):
q1=Streamable.query.with_polymorphic(Video)
q1.join(Video.song, participation, Artist).filter(Artist.id==1)
q2=Streamable.query.with_polymorphic(Song)
q2.join(participation, Artist).filter(Artist.id==1)
Here's the best I reached; it emits monstrous SQL and always yields empty results (not sure why):
p1=db.aliased(participation)
p2=db.aliased(participation)
a1=db.aliased(Artist)
a2=db.aliased(Artist)
q=Streamable.query.with_polymorphic((Video, Song))
q=q.join(p1, a1).join(Video.song, p2, a2)
q.filter(db.or_((a1.id==1), (a2.id==1))).order_by('score')
What's the right way to do this query, if at all (maybe a relational datastore is not the right tool for my job...)?
Your queries are basically right. I think the change from join to outerjoin should solve the problem:
q=q.outerjoin(p1, a1).outerjoin(Video.song, p2, a2)
I would also replace the order_by with:
q = q.order_by(Streamable.score)