Flask-SQLAlchemy запрос для подсчета - PullRequest
0 голосов
/ 21 ноября 2018

Я использую Flask-SQLAlchemy, и я использую отношения один-ко-многим.Две модели

class Request(db.Model):

      id = db.Column(db.Integer, primary_key = True)
      r_time = db.Column(db.DateTime, index = True, default=datetime.utcnow)
      org = db.Column(db.String(120))
      dest = db.Column(db.String(120))
      buyer_id = db.Column(db.Integer, db.ForeignKey('buyer.id'))
      sale_id = db.Column(db.Integer, db.ForeignKey('sale.id'))
      cost = db.Column(db.Integer)
      sr = db.Column(db.Integer)
      profit = db.Column(db.Integer)

      def __repr__(self):
          return '<Request {} by {}>'.format(self.org, self.buyer_id)

class Buyer(db.Model):
      id  = db.Column(db.Integer, primary_key = True)
      name = db.Column(db.String(120), unique = True)
      email = db.Column(db.String(120), unique = True)
      requests = db.relationship('Request', backref = 'buyer', lazy='dynamic')

      def __repr__(self):
          return '<Buyer {}>'.format(self.name)

Мне нужно определить, у какого Покупателя есть минимальные запросы от всех покупателей.

Я мог бы сделать это вручную, создав дополнительные списки и поместив все запросы в списки ипоиск по списку.Но я считаю, что есть еще один простой способ сделать это с помощью запроса SQLAlchemy

1 Ответ

0 голосов
/ 21 ноября 2018

Вы можете сделать это с помощью CTE (общее табличное выражение) для выбора, который производит идентификаторы покупателя вместе с их количеством запросов, поэтому

buyer_id | request_count
:------- | :------------
1        | 5
2        | 3
3        | 1
4        | 1

Здесь вы можете отфильтровать значения, которые должны быть больше, чемВ списке должно быть 0.

Затем вы можете присоединиться к таблице покупателей, чтобы получить:

buyer_id | buyer_name | buyer_email      | request_count
:------- | :--------- | :--------------- | :------------
1        | foo        | foo@example.com  | 5
2        | bar        | bar@example.com  | 3
3        | baz        | baz@example.com  | 1
4        | spam       | spam@example.com | 1

, но поскольку мы используем CTE, вы также можете запросить CTE для наименьшего количествазначение.В приведенном выше примере это 1, и вы можете добавить предложение WHERE к объединенному запросу customer-with-cte-countts, чтобы отфильтровать результаты до тех строк, где значение request_count равно этому минимальному числу.

Для этого SQL-запрос

WITH request_counts AS (
    SELECT request.buyer_id AS buyer_id, count(request.id) AS request_count
    FROM request GROUP BY request.buyer_id
    HAVING count(request.id) > ?
)
SELECT buyer.*
FROM buyer
JOIN request_counts ON buyer.id = request_counts.buyer_id
WHERE request_counts.request_count = (
    SELECT min(request_counts.request_count)
    FROM request_counts
)

WITH request_counts AS (...) определяет CTE, и именно эта часть создаст первую таблицу с buyer_id и request_count.Затем таблица request_count объединяется с request, а предложение WHERE выполняет фильтрацию по значению min(request_counts.request_count).

Перевод приведенного выше кода в код Flask-SQLAlchemy:

request_count = db.func.count(Request.id).label("request_count")
cte = (
    db.select([Request.buyer_id.label("buyer_id"), request_count])
    .group_by(Request.buyer_id)
    .having(request_count > 0)
    .cte('request_counts')
)
min_request_count = db.select([db.func.min(cte.c.request_count)]).as_scalar()
buyers_with_least_requests = Buyer.query.join(
    cte, Buyer.id == cte.c.buyer_id
).filter(cte.c.request_count == min_request_count).all()

Демонстрация:

>>> __ = db.session.bulk_insert_mappings(
...     Buyer, [{"name": n} for n in ("foo", "bar", "baz", "spam", "no requests")]
... )
>>> buyers = Buyer.query.order_by(Buyer.id).all()
>>> requests = [
...     Request(buyer_id=b.id)
...     for b in [*([buyers[0]] * 3), *([buyers[1]] * 5), *[buyers[2], buyers[3]]]
... ]
>>> __ = db.session.add_all(requests)
>>> request_count = db.func.count(Request.id).label("request_count")
>>> cte = (
...     db.select([Request.buyer_id.label("buyer_id"), request_count])
...     .group_by(Request.buyer_id)
...     .having(request_count > 0)
...     .cte("request_counts")
... )
>>> buyers_w_counts = Buyer.query.join(cte, cte.c.buyer_id == Buyer.id)
>>> for buyer, count in buyers_w_counts.add_column(cte.c.request_count):
...     # print out buyer and request count for this demo
...     print(buyer, count, sep=": ")
<Buyer foo>: 3
<Buyer bar>: 5
<Buyer baz>: 1
<Buyer spam>: 1
>>> min_request_count = db.select([db.func.min(cte.c.request_count)]).as_scalar()
>>> buyers_w_counts.filter(cte.c.request_count == min_request_count).all()
[<Buyer baz>, <Buyer spam>]

Я также создал здесь db <> fiddle , содержащую те же запросы, с которыми можно поиграть.

...