2

I am creating a flask app with SQLAlchemy and Postgres. I am pretty green at this so I any feedback would be appreciated. However, my direct question is with constructing a query on the following model.

from app import db
from sqlalchemy import or_, and_


# Items Table
class Item(db.Model):

    __tablename__ = "items"

    id = db.Column(db.Integer, primary_key=True)
    itemName = db.Column(db.String, unique=True, nullable=False)
    measurement = db.Column(db.String, nullable=False)
    defaultPrice = db.Column(db.Float, nullable=False)
    minimumOrder = db.Column(db.Float, nullable=False)
    maximumOrder = db.Column(db.Float, nullable=False)
    orders = db.relationship('Order', back_populates='item')
    prices = db.relationship('Price', back_populates='item')

    def __init__(self, itemName, measurement, defaultPrice,
                 minimumOrder, maximumOrder):
        self.itemName = itemName
        self.measurement = measurement
        self.defaultPrice = defaultPrice
        self.minimumOrder = minimumOrder
        self.maximumOrder = maximumOrder

    def __repr__(self):
        return '<Item {0}>'.format(self.id)


# Users Table
class User(db.Model):

    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    fullName = db.Column(db.String, unique=True, nullable=False)
    userName = db.Column(db.String, unique=True, nullable=False)
    password = db.Column(db.String, nullable=False)
    role = db.Column(db.String, nullable=False)
    orders = db.relationship('Order', back_populates='user')
    prices = db.relationship('Price', back_populates='user')

    def __init__(self, fullName, userName, password, role):
        self.fullName = fullName
        self.userName = userName
        self.password = password
        self.role = role

    def __repr__(self):
        return '<User {0}>'.format(self.userName)


# Availability / Price Table
class Price(db.Model):

    __tablename__ = 'prices'

    id = db.Column(db.Integer, primary_key=True)
    userId = db.Column(db.Integer, db.ForeignKey('users.id'))
    user = db.relationship('User', back_populates='prices')
    itemId = db.Column(db.Integer, db.ForeignKey('items.id'))
    item = db.relationship('Item', back_populates='prices')
    available = db.Column(db.Boolean)
    priceMeasurement = db.Column(db.String)
    price = db.Column(db.Float)

    def __init__(self, userId, itemId, priceMeasurement, price):
        self.userId = userId
        self.itemId = itemId
        self.priceMeasurement = priceMeasurement
        self.price = price

    def __repr__(self):
        return '<Price {0}>'.format(self.price)


# Orders Table
class Order(db.Model):

    __tablename__ = 'orders'

    id = db.Column(db.Integer, primary_key=True)
    userId = db.Column(db.Integer, db.ForeignKey('users.id'))
    user = db.relationship('User', back_populates='orders')
    itemId = db.Column(db.Integer, db.ForeignKey('items.id'))
    item = db.relationship('Item', back_populates='orders')
    orderQuantity = db.Column(db.Float)
    orderMeasurement = db.Column(db.String)
    orderPrice = db.Column(db.Float)
    orderDelivery = db.Column(db.Date)
    orderPlaced = db.Column(db.Date)

    def __init__(self, userId, itemId, orderQuantity,
                 orderMeasurement, orderPrice, orderDelivery, orderPlaced):
        self.userId = userId
        self.itemId = itemId
        self.orderQuantity = orderQuantity
        self.orderMeasurement = orderMeasurement
        self.orderPrice = orderPrice
        self.orderDelivery = orderDelivery
        self.orderPlaced = orderPlaced

    def __repr__(self):
        return '<Order {0}>'.format(self.orderDelivery)

What I would like from the query is to return a table similar to that which the following query returns:

SELECT * FROM items
JOIN prices ON prices.itemId=items.id
WHERE prices.userId = 1 AND prices.available = True
LEFT JOIN (
SELECT * FROM orders WHERE orderDelivery = '2017-07-05') as orders
ON orders.itemId=items.id

In an SQLAlchemy query. I will pass the userId and orderDelivery variables to the query from the route and session - @app.route('/user/order/<order_date>') | session['userID'] : established at login.

Thanks

1

1 Answer 1

5

If I understood you correctly, you'd like to query tuples of (Item, Price, Order) entitites, where Order comes from the subquery. This is explained in the Object Relational Tutorial under Selecting Entities from Subqueries.

In [5]: from datetime import date

In [6]: orders_sq = db.session.query(Order).\
   ...:     filter(Order.orderDelivery == date(2017, 7, 5)).\
   ...:     subquery()

In [7]: orders_alias = db.aliased(Order, orders_sq)

In [8]: query = db.session.query(Item, Price, orders_alias).\
   ...:     join(Price).\
   ...:     outerjoin(orders_alias, Item.orders).\
   ...:     filter(Price.userId == 1,
   ...:            Price.available)

and the produced SQL when compiled against SQLite:

In [9]: print(query)
SELECT items.id AS items_id, items."itemName" AS "items_itemName", items.measurement AS items_measurement, items."defaultPrice" AS "items_defaultPrice", items."minimumOrder" AS "items_minimumOrder", items."maximumOrder" AS "items_maximumOrder", prices.id AS prices_id, prices."userId" AS "prices_userId", prices."itemId" AS "prices_itemId", prices.available AS prices_available, prices."priceMeasurement" AS "prices_priceMeasurement", prices.price AS prices_price, anon_1.id AS anon_1_id, anon_1."userId" AS "anon_1_userId", anon_1."itemId" AS "anon_1_itemId", anon_1."orderQuantity" AS "anon_1_orderQuantity", anon_1."orderMeasurement" AS "anon_1_orderMeasurement", anon_1."orderPrice" AS "anon_1_orderPrice", anon_1."orderDelivery" AS "anon_1_orderDelivery", anon_1."orderPlaced" AS "anon_1_orderPlaced" 
FROM items JOIN prices ON items.id = prices."itemId" LEFT OUTER JOIN (SELECT orders.id AS id, orders."userId" AS "userId", orders."itemId" AS "itemId", orders."orderQuantity" AS "orderQuantity", orders."orderMeasurement" AS "orderMeasurement", orders."orderPrice" AS "orderPrice", orders."orderDelivery" AS "orderDelivery", orders."orderPlaced" AS "orderPlaced" 
FROM orders 
WHERE orders."orderDelivery" = ?) AS anon_1 ON items.id = anon_1."itemId" 
WHERE prices."userId" = ? AND prices.available = 1

Also alternatively you could simply pass your statement to Query.from_statement with a few fixes and changes:

In [45]: query2 = db.session.query(Item, Price, Order).\
    ...:     from_statement(db.text("""
    ...: SELECT * FROM items
    ...: JOIN prices ON prices.itemId=items.id
    ...: LEFT JOIN (
    ...: SELECT * FROM orders WHERE orderDelivery = :orderDelivery) as orders
    ...: ON orders.itemId=items.id
    ...: WHERE prices.userId = :userId AND prices.available
    ...: """)).\
    ...:     params(userId=1, orderDelivery='2017-07-05')

but I'd recommend using the former approach as it is more database agnostic.

Sign up to request clarification or add additional context in comments.

2 Comments

sorry for the delay - this is perfect. Makes way more sense than my initial SQL query which was returning a really wide table.
Heh, it is still wide underneath, but the entity objects hide the multitude of columns.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.