I am using sqlachemy with MySQL backend. Actually having problems optimizing a query.
I have the following set of models:
class Book(Base):
__tablename__ = 'books'
name = Column(String(32), primary_key=True)
sku = Column(Integer, nullable=False)
class Author(Base):
__tablename__ = 'authors'
name = Column(String(64), primary_key=True)
# book
books = relationship(Book, secondary=Table(
'author_books', Base.metadata,
Column('author', String(32),
ForeignKey(Author.name), primary_key=True),
Column('book', String(64), ForeignKey("book.title"),
primary_key=True)
), backref=backref('book_authors', cascade="save-update"))
And was doing a query:
authors_with_books = [(autor.name, [book.name for book in books]) for author in session.query(Authors)]
Which is actually very very slow :(
Your query is slow because you are joining data on python.
Do it on database.
try something like that:
authors_with_books = session.query(Author.name, Book.name)
It'll create a join on SQL
authors_with_books = session.query(Author).join(Author.books)
or
Use 'lazyload' param.
Try rewrite many_to_many table in models. It's work for me:
class Author(Base):
__tablename__ = 'authors'
name = Column(String(64), primary_key=True)
books = relationship('Book', secondary='author_books')
author_book = Table(
'author_books', Base.metadata,
Column('author', String(32), ForeignKey("author.name", match=u'FULL'), primary_key=True),
Column('book', String(64), ForeignKey("book.title", match=u'FULL'), primary_key=True)
)
And I think ForeignKey(Author.name) - Incorrect code, fix it.
Related
A store can have many interests. User request a product that is tagged. Query required is to get the product requests that have tags shared with current store.
# in Store -> relationship('Tag', secondary=store_interest_tags, lazy='dynamic', backref=backref('store', lazy=True))
store_tags = store.interests
matched_requests_to_store = []
for tag in store_tags:
r = session.query(ProductRequest).filter(ProductRequest.product_tags.contains(tag)).all()
matched_requests_to_store.extend(r)
I am sure there might be a more efficient way to query that. I have tried the following:
session.query(ProductRequest).filter(ProductRequest.product_tags.any(store_tags)).all()
But got
psycopg2.errors.SyntaxError: subquery must return only one column
LINE 5: ..._id AND tag.id = product_requests_tags.tag_id AND (SELECT ta...
Any idea how to achieve such query?
A query like this might work, I think it could be done with less joins but this is less rigid than dropping into using the secondary tables directly and specifying the individual joins:
q = session.query(
ProductRequest
).join(
ProductRequest.tags
).join(
Tag.stores
).filter(
Store.id == store.id)
product_requests_for_store = q.all()
With a schema like this:
stores_tags_t = Table(
"stores_tags",
Base.metadata,
Column("id", Integer, primary_key=True),
Column("store_id", Integer, ForeignKey("stores.id")),
Column("tag_id", Integer, ForeignKey("tags.id")),
)
product_requests_tags_t = Table(
"product_request_tags",
Base.metadata,
Column("id", Integer, primary_key=True),
Column("product_request_id", Integer, ForeignKey("product_requests.id")),
Column("tag_id", Integer, ForeignKey("tags.id")),
)
class Store(Base):
__tablename__ = "stores"
id = Column(Integer, primary_key=True)
name = Column(String(), unique=True, index=True)
tags = relationship('Tag', secondary=stores_tags_t, backref=backref('stores'))
class ProductRequest(Base):
__tablename__ = "product_requests"
id = Column(Integer, primary_key=True)
name = Column(String(), unique=True, index=True)
tags = relationship('Tag', secondary=product_requests_tags_t, backref=backref('product_requests'))
class Tag(Base):
__tablename__ = "tags"
id = Column(Integer, primary_key=True)
name = Column(String())
This worked:
session.query(ProductRequest).filter( ProductRequest.product_tags.any(Tag.id.in_(store_tag.id for store_tag in store_tags) ) ).all()
I am defining a table in Flask like
groups = db.Table(
"types",
db.Column("one_id", db.Integer, db.ForeignKey("one.id")),
db.Column("two_id", db.Integer, db.ForeignKey("two.id")),
UniqueConstraint('one_id', 'two_id', name='uix_1') #Unique constraint given for unique-together.
)
But this is not working.
I think you can refer to an old topic https://stackoverflow.com/a/10061143/18269348
Here is the code :
# version1: table definition
mytable = Table('mytable', meta,
# ...
Column('customer_id', Integer, ForeignKey('customers.customer_id')),
Column('location_code', Unicode(10)),
UniqueConstraint('customer_id', 'location_code', name='uix_1')
)
# or the index, which will ensure uniqueness as well
Index('myindex', mytable.c.customer_id, mytable.c.location_code, unique=True)
# version2: declarative
class Location(Base):
__tablename__ = 'locations'
id = Column(Integer, primary_key = True)
customer_id = Column(Integer, ForeignKey('customers.customer_id'),
nullable=False)
location_code = Column(Unicode(10), nullable=False)
__table_args__ = (UniqueConstraint('customer_id', 'location_code',
name='_customer_location_uc'),
)
You have a little explanation on the post and a link to the official documentation of sqlalchemy.
Thanks to Van who posted that.
My models look like this:
class Company(DB_BASE):
__tablename__ = 'Company'
id = Column(Integer, primary_key=True, index=True)
...
products = relationship('Product', secondary=Company_Products, backref='Company')
class Product(DB_BASE):
__tablename__ = 'Product'
id = Column(Integer, primary_key=True, index=True)
...
companies = relationship('Company', secondary=Company_Products, backref='Product')
This is my association table
Company_Products = Table(
'Company_Products',
DB_BASE.metadata,
Column('id', Integer, primary_key=True),
Column('company_id', Integer, ForeignKey('Company.id')),
Column('product_id', Integer, ForeignKey('Product.id')),
Column('quantity', Integer, default=0),
Column('price_per_unit', Integer, default=0),
)
And this is how I'm querying the association table.
company_product = db.query(Company_Products).filter_by(product_id=id, company_id=user.company_id).first()
company_product.quantity = data.data['quantity']
company_product.price = data.data['price']
After creating the many-to-many relationship between a Company and a Product, I would like to modify the relationship extra data, quantity and price_per_unit in this instance. After querying the association object, modifying any attribute yields:
AttributeError: can't set attribute 'quantity'
Follow up on my question, the solution which ended up working for me is making a new model and using it to somewhat simulate an association table.
class Company_Products(DB_BASE):
__tablename__ = 'Company_Products'
id = Column(Integer, primary_key=True, index=True)
...
quantity = Column(String) # 1 - client, 2 - furnizor
price_per_unit = Column(String)
company_id = Column(Integer, ForeignKey('Company.id'))
company = relationship('Company', back_populates='products', lazy='select')
product_id = Column(Integer, ForeignKey('Product.id'))
product = relationship('Product', back_populates='companies', lazy='select')
This is definitely not the best solution, if I come up with something else or come across something which might work out, I will edit this.
There are tables for my question.
class TemplateExtra(ExtraBase, InsertMixin, TimestampMixin):
__tablename__ = 'template_extra'
id = Column(Integer, primary_key=True, autoincrement=False)
name = Column(Text, nullable=False)
roles = relationship(
'RecipientRoleExtra',
secondary='template_to_role',
)
class RecipientRoleExtra(
ExtraBase, InsertMixin, TimestampMixin,
SelectMixin, UpdateMixin,
):
__tablename__ = 'recipient_role'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(Text, nullable=False)
description = Column(Text, nullable=False)
class TemplateToRecipientRoleExtra(ExtraBase, InsertMixin, TimestampMixin):
__tablename__ = 'template_to_role'
id = Column(Integer, primary_key=True, autoincrement=True)
template_id = Column(Integer, ForeignKey('template_extra.id'))
role_id = Column(Integer, ForeignKey('recipient_role.id'))
I want to select all templates with prefetched roles in two sql-queries like Django ORM does with prefetch_related. Can I do it?
This is my current attempt.
def test_custom():
# creating engine with echo=True
s = DBSession()
for t in s.query(TemplateExtra).join(RecipientRoleExtra, TemplateExtra.roles).all():
print(f'id = {t.id}')
for r in t.roles:
print(f'-- {r.name}')
But..
it generates select query for every template to select its roles. Can I make sqlalchemy to do only one query?
generated queries for roles are without join, just FROM recipient_role, template_to_role with WHERE %(param_1)s = template_to_role.template_id AND recipient_role.id = template_to_role.role_id. Is it correct?
Can u help me?
Based on this answer:
flask many to many join as done by prefetch_related from django
Maybe somthing like this:
roles = TemplateExtra.query.options(db.joinedload(TemplateExtra.roles)).all
Let me know if it worked.
I created a many to many relationship with sqlalchemy like this:
subject_books = Table('subject_books', Base.metadata,
Column('subject_id', Integer, ForeignKey('subjects.id')),
Column('book_id', Integer, ForeignKey('books.id')),
Column('group', Integer)
)
class Subject(Base):
__tablename__ = 'subjects'
id = Column(Integer, primary_key=True)
value = Column(Unicode(255), unique=True)
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
title = Column(Unicode(255))
isbn = Column(Unicode(24))
subjects = relationship('Subject', secondary=subject_books, collection_class=attribute_mapped_collection('group'), backref='books')
after that I created a test like following:
book = Book(title='first book',isbn='test')
book.subjects[0] = Subject(value='first subject')
book.subjects[1] = Subject(value='second subject')
session.add(book)
transaction.commit()
and it works fine. But what I really want is to store more than one subject with the same group value, so I tried the following test:
book = Book(title='first book',isbn='test')
book.subjects[0] = [Subject(value='first subject'),Subject(value='second subject')]
book.subjects[1] = [Subject(value='third subject'),Subject(value='forth subject')]
session.add(book)
transaction.commit()
but it does not work.
Can this be done using sqlalchemy?
Thanks in Advance
Razi
I think you are constructing wrong relation ship.
Your relation ship must be
book M2M subject
subject M2M group
So you have to create one more model for group and that must be assign as m2m in Subject
Your models will be like.
subject_books = Table('subject_books', Base.metadata,
Column('subject_id', Integer, ForeignKey('subjects.id')),
Column('book_id', Integer, ForeignKey('books.id')),
)
subject_group = Table('subject_groups', Base.metadata,
Column('group_id', Integer, ForeignKey('groups.id')),
Column('subject_id', Integer, ForeignKey('subjects.id')),
)
class Subject(Base):
__tablename__ = 'subjects'
id = Column(Integer, primary_key=True)
value = Column(Unicode(255), unique=True)
groups = relationship('Groups', secondary=subject_groups, backref='subjects')
class Groups(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
name = Column(Unicode(255), unique=True)
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
title = Column(Unicode(255))
isbn = Column(Unicode(24))
subjects = relationship('Subject', secondary=subject_books, backref='books')
I also check the docs for attribute_mapped_collection. But each time I found that each key is associated with only one object not more then one. If you read anywhere then please provide the link so I can check that how it will be fit in your code.
I think this will be help you.