Using SQLAlchemy, Accessing Latest Value for Foreign Key as Attribute - python

I'm sure I'm wording this improperly, mainly because I'm not quite sure how I'm looking to achieve my goal here...
I've got an SQL Alchemy db class that, among other things, stores a value for the price of a commodity. I need to do two things with this price:
Access the latest price of a Commodity at any given moment
Access historical Commodity.price changes
Currently, my models look like this:
class Commodity(Base):
# Create Tablename
__tablename__ = "commodities"
# Defaults
id = Column(Integer, primary_key=True, autoincrement=True)
created = Column(DateTime, default=datetime.now)
updated = Column(DateTime, onupdate=datetime.now)
# Uniques
name = Column(String(), unique=True)
price = ???
class Price(Base):
# Create Tablename
__tablename__ = "prices"
# Defaults
id = Column(Integer, primary_key=True, autoincrement=True)
# Uniques
date = Column(DateTime, default=datetime.now)
commodity = Column(ForeignKey(Commodity))
price = Column(Integer())
In the Commodity class you'll note I have a ??? value as a placeholder for the price. I'd like to be able to access the price as Commodity.price in a way that would give me the latest value for that Commodity, identical to the following query:
Price.query.order_by('updated desc').limit(1)
I'm very new to SQL Alchemy, and appreciate that my approach may be wrong in general. Any help would be much-appreciated.
With my current understanding, I am planning to create an entry in the prices table for each update and simply update the Commodity.price value during the process. It seems a bit more elegant to use a function to get the Commodity.price via the prices table though. Is such an approach possible/recommended?
EDIT 1:
Essentially, I'm wondering how to do something like this:
class Commodity(Base):
...
price = Column(ForeignKey(Price.query.order_by('updated desc').limit(1)))
Just to clarify, this isn't (to my understanding) a one-to-many relationship but, rather, a many-to-many.

Related

Many-to-many join table with additional field in Flask

I have two tables, Products and Orders, inside my Flask-SqlAlchemy setup, and they are linked so an order can have several products:
class Products(db.Model):
id = db.Column(db.Integer, primary_key=True)
....
class Orders(db.Model):
guid = db.Column(db.String(36), default=generate_uuid, primary_key=True)
products = db.relationship(
"Products", secondary=order_products_table, backref="orders")
....
linked via:
order_products_table = db.Table("order_products_table",
db.Column('orders_guid', db.String(36), db.ForeignKey('orders.guid')),
db.Column('products_id', db.Integer, db.ForeignKey('products.id'))
# db.Column('license', dbString(36))
)
For my purposes, each product in an order will receive a unique license string, which logically should be added to the order_products_table rows of each product in an order.
How do I declare this third license column on the join table order_products_table so it gets populated it as I insert an Order?
I've since found the documentation for the Association Object from the SQLAlchemy docs, which allows for exactly this expansion to the join table.
Updated setup:
# Instead of a table, provide a model for the JOIN table with additional fields
# and explicit keys and back_populates:
class OrderProducts(db.Model):
__tablename__ = 'order_products_table'
orders_guid = db.Column(db.String(36), db.ForeignKey(
'orders.guid'), primary_key=True)
products_id = db.Column(db.Integer, db.ForeignKey(
'products.id'), primary_key=True)
order = db.relationship("Orders", back_populates="products")
products = db.relationship("Products", back_populates="order")
licenses = db.Column(db.String(36), nullable=False)
class Products(db.Model):
id = db.Column(db.Integer, primary_key=True)
order = db.relationship(OrderProducts, back_populates="order")
....
class Orders(db.Model):
guid = db.Column(db.String(36), default=generate_uuid, primary_key=True)
products = db.relationship(OrderProducts, back_populates="products")
....
What is really tricky (but also shown on the documentation page), is how you insert the data. In my case it goes something like this:
o = Orders(...) # insert other data
for id in products:
# Create OrderProducts join rows with the extra data, e.g. licenses
join = OrderProducts(licenses="Foo")
# To the JOIN add the products
join.products = Products.query.get(id)
# Add the populated JOIN as the Order products
o.products.append(join)
# Finally commit to database
db.session.add(o)
db.session.commit()
I was at first trying to populate the Order.products (or o.products in the example code) directly, which will give you an error about using a Products class when it expects a OrderProducts class.
I also struggled with the whole field naming and referencing of the back_populates. Again, the example above and on the docs show this. Note the pluralization is entirely to do with how you want your fields named.

SQLAlchemy query model when related model with given properties don’t exist

I have three models (note that this is done in Flask-SQLAlchemy, but if you can only write an answer for vanilla SQLAlchemy, that is fine with me.) Irrelevant fields are removed for clarity.
class KPI(db.Model):
__tablename__ = 'kpis'
id = db.Column(db.Integer, primary_key=True)
identifier = db.Column(db.String(length=50))
class Report(db.Model):
__tablename__ = 'reports'
id = db.Column(db.Integer, primary_key=True)
class ReportKPI(db.Model):
report_id = db.Column(db.Integer, db.ForeignKey('reports.id'), primary_key=True)
kpi_id = db.Column(db.Integer, db.ForeignKey('kpis.id'), primary_key=True)
report = db.relationship('Report', backref=db.backref('values'))
kpi = db.relationship('KPI')
My goal is to find all Report objects that don’t measure a specific KPI (ie. there is no ReportKPI object whose KPI relationship has identifier set to a specific value).
One of my tries look like
Report.query \
.join(ReportKPI) \
.join(KPI) \
.filter(KPI.identifier != 'reflection')
but this gives back more Report objects that actually exist (I guess I get one for every ReportKPI that has a KPI with anything but “reflection”.)
Is the thing I want to achieve actually possible with SQLAlchemy? If so, what is the magic word (pleas doesn’t seem to work…)
An EXISTS subquery expression is a good fit for your goal. A shorthand way to write such a query would be:
Report.query.\
filter(db.not_(Report.values.any(
ReportKPI.kpi.has(identifier='reflection'))))
but this produces 2 nested EXISTS expressions, though a join in an EXISTS would do as well:
Report.query.\
filter(db.not_(
ReportKPI.query.
filter_by(report_id=Report.id).
join(KPI).
filter_by(identifier='reflection').
exists()))
Finally, a LEFT JOIN with an IS NULL is an option as well:
Report.query.\
outerjoin(db.join(ReportKPI, KPI),
db.and_(ReportKPI.report_id == Report.id,
KPI.identifier == 'reflection')).\
filter(KPI.id.is_(None))

Column with default checking same table

Using SQLAlchemy, I'd like to autogenerate an identifier for a model:
class Foo(Model):
__tablename__ = 'foo'
id = Column(Integer, primary_key=True)
date = Column(Date, default=datetime.now)
reference = Column(Unicode, default=generate_reference)
Basically, I want generate_reference returning a field like:
FOO201410-001
where 2014 is current's year, 10 current's month and 001 the next id for current month, calculated using a query such as:
SELECT COUNT(*)
FROM foo
WHERE strftime('%m', datetime(date, 'unixepoch')) == strftime('%m', date('now'))
AND strftime('%y', datetime(date, 'unixepoch')) == strftime('%y', date('now'))
I've taken the syntax form from what I've read from SQLite3, though it's just an example. Basically, I want to know how many other records have the same month/year, and assign the last part with that count.
I've tried doing defaults with select expressions, but as my table is not yet created, it looks like I cannot create a select expression from it yet.
Thanks!
Here's a way to do it with default instead of event:
def foo_reference_default(context):
now = datetime.now()
month, year = now.month, now.year
ref = context.connection.execute(db.select([Foo.__table__]).where(
db.and_(db.extract('month', Foo.date)==month,
db.extract('year', Foo.date)==year)
).count()).scalar() + 1
return u'FOO{year}{month}-{ref:03}'.format(year=year, month=month, ref=ref)
class Foo(Model):
__tablename__ = 'foo'
id = Column(Integer, primary_key=True)
date = Column(Date, default=datetime.now)
reference = Column(Unicode, default=foo_reference_default)
This is effectively the same as your answer: A prior SELECT is done in order to populate the INSERT. (Note that I added +1 so it would start at 001 instead of 000.)
You could, of course, use a lambda to embed the function into default, but I don't recommend it -- you only want to call now() once. Calling it multiple times introduces a slim but real chance of getting inconsistent data on month and year edges.
I finally found a solution that works fine, thanks to other answers:
class Foo(Model):
__tablename__ = 'foo'
id = Column(Integer, primary_key=True)
date = Column(Date, default=datetime.now)
reference = Column(Unicode)
#db.event.listens_for(Foo, 'before_insert')
def receive_before_insert(mapper, connection, foo):
ref = foo.query.filter(db.and_(db.extract('month', Foo.date)==datetime.now().month,
db.extract('year', Foo.date)==datetime.now().year)
).count()
foo.reference = 'FOO{year}{month}-{ref:03}'.format(year=datetime.now().year,
month=datetime.now().month,
ref=ref)
though, I'm leaving the question open, in case someone suggests something that could
be directly embedded within the default key.

SQLAlchemy Return All Distinct Column Values

I am creating a website using Flask and SQLAlchemy. This website keeps track of classes that a student has taken. I would like to find a way to search my database using SQLAlchemy to find all unique classes that have been entered. Here is code from my models.py for Class:
class Class(db.Model):
__tablename__ = 'classes'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100))
body = db.Column(db.Text)
created = db.Column(db.DateTime, default=datetime.datetime.now)
user_email = db.Column(db.String(100), db.ForeignKey(User.email))
user = db.relationship(User)
In other words, I would like to get all unique values from the title column and pass that to my views.py.
Using the model query structure you could do this
Class.query.with_entities(Class.title).distinct()
query = session.query(Class.title.distinct().label("title"))
titles = [row.title for row in query.all()]
titles = [r.title for r in session.query(Class.title).distinct()]
As #van has pointed out, what you are looking for is:
session.query(your_table.column1.distinct()).all(); #SELECT DISTINCT(column1) FROM your_table
but I will add that in most cases, you are also looking to add another filter on the results. In which case you can do
session.query(your_table.column1.distinct()).filter_by(column2 = 'some_column2_value').all();
which translates to sql
SELECT DISTINCT(column1) FROM your_table WHERE column2 = 'some_column2_value';

sqlalchemy / table setup

I have items, warehouses, and items are in warehouses.
So I have table that has information about items (sku, description, cost ...) and a table that describes warehouses(location, code, name, ...). Now I need a way to store inventory so that I know I have X items in warehouse Y. An item can be in any warehouse.
How would I go about setting up the relationship between them and storing the qty?
class Item(DeclarativeBase):
__tablename__ = 'items'
item_id = Column(Integer, primary_key=True,autoincrement=True)
item_code = Column(Unicode(35),unique=True)
item_description = Column(Unicode(100))
item_long_description = Column(Unicode())
item_cost = Column(Numeric(precision=13,scale=4))
item_list = Column(Numeric(precision=13,scale=2))
def __init__(self,code,description,cost,list):
self.item_code = code
self.item_description = description
self.item_cost = cost
self.item_list = list
class Warehouse(DeclarativeBase):
__tablename__ = 'warehouses'
warehouse_id = Column(Integer, primary_key=True, autoincrement=True)
warehouse_code = Column(Unicode(15),unique=True)
warehouse_description = Column(Unicode(55))
If I am correct I would setup the many to many using an intermediate table something like ...
item_warehouse = Table(
'item_warehouse', Base.metadata,
Column('item_id', Integer, ForeignKey('items.item_id')),
Column('warehouse_id', Integar, ForeignKey('warehouses.warehouse_id'))
)
But i would need to start the qty available on this table but since its not its own class I am not sure how that would work.
What would be the "best" practice for modeling this and having it usable in my app?
Model:
As mentioned by #Lafada, you need an Association Object. As such I would create a SA-persistent object and not only a table:
class ItemWarehouse(Base):
# version-1:
__tablename__ = 'item_warehouse'
__table_args__ = (PrimaryKeyConstraint('item_id', 'warehouse_id', name='ItemWarehouse_PK'),)
# version-2:
#__table_args__ = (UniqueConstraint('item_id', 'warehouse_id', name='ItemWarehouse_PK'),)
#id = Column(Integer, primary_key=True, autoincrement=True)
# other columns
item_id = Column(Integer, ForeignKey('items.id'), nullable=False)
warehouse_id = Column(Integer, ForeignKey('warehouses.id'), nullable=False)
quantity = Column(Integer, default=0)
This covers the model requirement with the following:
added a PrimaryKey
added a UniqueConstraint covering the (item_id, warehouse_id) pairs.
In the code above this is solved in two ways:
version-1: uses composite primary key (which must be unique)
version-2: uses simple primary key, but also adds an explicit unique constraint [I personally prefer this option]
Relationship: Association Object
Now. You can use the Association Object as is, which will look similar to this:
w = Warehouse(...)
i = Item(name="kindle", price=...)
iw = ItemWarehouse(quantity=50)
iw.item = i
w.items.append(i)
Relationship: Association Proxy extension
or, you could go one step further and use the Composite Association Proxies example, and you may configure dictionary-like access to the association object similar to this:
w = Warehouse(...)
i = Item(name="kindle", price=...)
w[i] = 50 # sets the quantity to 50 of item _i_ in warehouse _w_
i[w] = 50 # same as above, if you configure it symmetrically
Beware: the code for the relationships definition might look really not easily readable, but the usage pattern is really nice. So if this option is too much to digest, I would start with Association Object with maybe some helper functions to add/get/update the item stocks, and eventually move to the Association Proxy extesion.
You have to use "Association Object".
I try to give you hint for your problem you have to create table like you mention in your question
item_warehouse = Table( 'item_warehouse',
Base.metadata,
Column('item_id',
Integer,
ForeignKey('items.item_id')
),
Column('warehouse_id',
Integar,
ForeignKey('warehouses.warehouse_id')
),
Column('qty',
Integer,
default=0,
),
)
Now you can add warehouse, item and qty in single object and you have to write method which will take warehouse_id and item_id and get the sum of qty for those itmes.
Hope this will help you to solve your problem.

Categories

Resources