Flask-SQLAlchemy query for count
I'am using Flask-SQLAlchemy and i use one-to-many relationships. Two models
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)
I need to identify which Buyer has a minimum requests from all
of the buyers.
I could do it manually by creating additional lists and put all
requests in a lists and search for the list. But I believe there is another simple way to do it via SQLAlchemy query
python sql sqlalchemy flask-sqlalchemy
|
show 4 more comments
I'am using Flask-SQLAlchemy and i use one-to-many relationships. Two models
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)
I need to identify which Buyer has a minimum requests from all
of the buyers.
I could do it manually by creating additional lists and put all
requests in a lists and search for the list. But I believe there is another simple way to do it via SQLAlchemy query
python sql sqlalchemy flask-sqlalchemy
1
You mean you need to find the one buyer with the fewest requests? That's different from a query for count.
– Martijn Pieters♦
Nov 21 at 8:10
1
What should happen if there is a tie? Say two buyers have each just one request, and all other buyers have more. Should both buyers be returned? Or just one? If so, which one?
– Martijn Pieters♦
Nov 21 at 8:11
Possible duplicate of SQLAlchemy ordering by count on a many to many relationship
– vishes_shell
Nov 21 at 8:11
@vishes_shell: many-to-many and many-to-one differ quite a bit, and this question is about finding a single or a small subset of results.
– Martijn Pieters♦
Nov 21 at 8:14
next question for the OP: should buyers without any requests (Buyer.requests.count() == 0
) be ignored?
– Martijn Pieters♦
Nov 21 at 8:14
|
show 4 more comments
I'am using Flask-SQLAlchemy and i use one-to-many relationships. Two models
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)
I need to identify which Buyer has a minimum requests from all
of the buyers.
I could do it manually by creating additional lists and put all
requests in a lists and search for the list. But I believe there is another simple way to do it via SQLAlchemy query
python sql sqlalchemy flask-sqlalchemy
I'am using Flask-SQLAlchemy and i use one-to-many relationships. Two models
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)
I need to identify which Buyer has a minimum requests from all
of the buyers.
I could do it manually by creating additional lists and put all
requests in a lists and search for the list. But I believe there is another simple way to do it via SQLAlchemy query
python sql sqlalchemy flask-sqlalchemy
python sql sqlalchemy flask-sqlalchemy
edited Nov 21 at 20:20
Martijn Pieters♦
699k13124202259
699k13124202259
asked Nov 21 at 8:07
J_log
285
285
1
You mean you need to find the one buyer with the fewest requests? That's different from a query for count.
– Martijn Pieters♦
Nov 21 at 8:10
1
What should happen if there is a tie? Say two buyers have each just one request, and all other buyers have more. Should both buyers be returned? Or just one? If so, which one?
– Martijn Pieters♦
Nov 21 at 8:11
Possible duplicate of SQLAlchemy ordering by count on a many to many relationship
– vishes_shell
Nov 21 at 8:11
@vishes_shell: many-to-many and many-to-one differ quite a bit, and this question is about finding a single or a small subset of results.
– Martijn Pieters♦
Nov 21 at 8:14
next question for the OP: should buyers without any requests (Buyer.requests.count() == 0
) be ignored?
– Martijn Pieters♦
Nov 21 at 8:14
|
show 4 more comments
1
You mean you need to find the one buyer with the fewest requests? That's different from a query for count.
– Martijn Pieters♦
Nov 21 at 8:10
1
What should happen if there is a tie? Say two buyers have each just one request, and all other buyers have more. Should both buyers be returned? Or just one? If so, which one?
– Martijn Pieters♦
Nov 21 at 8:11
Possible duplicate of SQLAlchemy ordering by count on a many to many relationship
– vishes_shell
Nov 21 at 8:11
@vishes_shell: many-to-many and many-to-one differ quite a bit, and this question is about finding a single or a small subset of results.
– Martijn Pieters♦
Nov 21 at 8:14
next question for the OP: should buyers without any requests (Buyer.requests.count() == 0
) be ignored?
– Martijn Pieters♦
Nov 21 at 8:14
1
1
You mean you need to find the one buyer with the fewest requests? That's different from a query for count.
– Martijn Pieters♦
Nov 21 at 8:10
You mean you need to find the one buyer with the fewest requests? That's different from a query for count.
– Martijn Pieters♦
Nov 21 at 8:10
1
1
What should happen if there is a tie? Say two buyers have each just one request, and all other buyers have more. Should both buyers be returned? Or just one? If so, which one?
– Martijn Pieters♦
Nov 21 at 8:11
What should happen if there is a tie? Say two buyers have each just one request, and all other buyers have more. Should both buyers be returned? Or just one? If so, which one?
– Martijn Pieters♦
Nov 21 at 8:11
Possible duplicate of SQLAlchemy ordering by count on a many to many relationship
– vishes_shell
Nov 21 at 8:11
Possible duplicate of SQLAlchemy ordering by count on a many to many relationship
– vishes_shell
Nov 21 at 8:11
@vishes_shell: many-to-many and many-to-one differ quite a bit, and this question is about finding a single or a small subset of results.
– Martijn Pieters♦
Nov 21 at 8:14
@vishes_shell: many-to-many and many-to-one differ quite a bit, and this question is about finding a single or a small subset of results.
– Martijn Pieters♦
Nov 21 at 8:14
next question for the OP: should buyers without any requests (
Buyer.requests.count() == 0
) be ignored?– Martijn Pieters♦
Nov 21 at 8:14
next question for the OP: should buyers without any requests (
Buyer.requests.count() == 0
) be ignored?– Martijn Pieters♦
Nov 21 at 8:14
|
show 4 more comments
1 Answer
1
active
oldest
votes
You can do this with a CTE (common table expression) for a select that produces buyer ids together with their request counts, so
buyer_id | request_count
:------- | :------------
1 | 5
2 | 3
3 | 1
4 | 1
You can filter here on the counts having to be greater than 0 to be listed.
You can then join the buyers table against that to produce:
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
but because we are using a CTE, you can also query the CTE for the lowest count value. In the above example, that's 1
, and you can add a WHERE
clause to the joined buyer-with-cte-counts query to filter the results down to only rows where the request_count
value is equal to that minimum number.
The SQL query for this is
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
)
The WITH request_counts AS (...)
defines a CTE, and it is that part that would produce the first table with buyer_id
and request_count
. The request_count
table is then joined with request
and the WHERE
clause does the filtering on the min(request_counts.request_count)
value.
Translating the above to Flask-SQLAlchemy code:
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()
Demo:
>>> __ = 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>]
I've also created a db<>fiddle here, containing the same queries, to play with.
Thanks a lot for your help i will try to understand your code. It will takes a time for me but i will. May be you could advise some good tutorial about complicated queries and how it need to be done. For me it's complicated i'm new in SQLAlchemy.
– J_log
Nov 21 at 17:50
1
@J_log: What I did here was use SQL, translated to SQLAlchemy. Master SQL first if you want to learn how to construct queries like these, thesql
tag wiki has a section on tutorials and books. But don't despair, it took me a while to learn tricks like CTEs and proper aggregation and such.
– Martijn Pieters♦
Nov 21 at 17:56
@J_log: I've reworked the answer to add more explanation as to what is going on.
– Martijn Pieters♦
Nov 21 at 20:04
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53407641%2fflask-sqlalchemy-query-for-count%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
You can do this with a CTE (common table expression) for a select that produces buyer ids together with their request counts, so
buyer_id | request_count
:------- | :------------
1 | 5
2 | 3
3 | 1
4 | 1
You can filter here on the counts having to be greater than 0 to be listed.
You can then join the buyers table against that to produce:
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
but because we are using a CTE, you can also query the CTE for the lowest count value. In the above example, that's 1
, and you can add a WHERE
clause to the joined buyer-with-cte-counts query to filter the results down to only rows where the request_count
value is equal to that minimum number.
The SQL query for this is
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
)
The WITH request_counts AS (...)
defines a CTE, and it is that part that would produce the first table with buyer_id
and request_count
. The request_count
table is then joined with request
and the WHERE
clause does the filtering on the min(request_counts.request_count)
value.
Translating the above to Flask-SQLAlchemy code:
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()
Demo:
>>> __ = 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>]
I've also created a db<>fiddle here, containing the same queries, to play with.
Thanks a lot for your help i will try to understand your code. It will takes a time for me but i will. May be you could advise some good tutorial about complicated queries and how it need to be done. For me it's complicated i'm new in SQLAlchemy.
– J_log
Nov 21 at 17:50
1
@J_log: What I did here was use SQL, translated to SQLAlchemy. Master SQL first if you want to learn how to construct queries like these, thesql
tag wiki has a section on tutorials and books. But don't despair, it took me a while to learn tricks like CTEs and proper aggregation and such.
– Martijn Pieters♦
Nov 21 at 17:56
@J_log: I've reworked the answer to add more explanation as to what is going on.
– Martijn Pieters♦
Nov 21 at 20:04
add a comment |
You can do this with a CTE (common table expression) for a select that produces buyer ids together with their request counts, so
buyer_id | request_count
:------- | :------------
1 | 5
2 | 3
3 | 1
4 | 1
You can filter here on the counts having to be greater than 0 to be listed.
You can then join the buyers table against that to produce:
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
but because we are using a CTE, you can also query the CTE for the lowest count value. In the above example, that's 1
, and you can add a WHERE
clause to the joined buyer-with-cte-counts query to filter the results down to only rows where the request_count
value is equal to that minimum number.
The SQL query for this is
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
)
The WITH request_counts AS (...)
defines a CTE, and it is that part that would produce the first table with buyer_id
and request_count
. The request_count
table is then joined with request
and the WHERE
clause does the filtering on the min(request_counts.request_count)
value.
Translating the above to Flask-SQLAlchemy code:
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()
Demo:
>>> __ = 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>]
I've also created a db<>fiddle here, containing the same queries, to play with.
Thanks a lot for your help i will try to understand your code. It will takes a time for me but i will. May be you could advise some good tutorial about complicated queries and how it need to be done. For me it's complicated i'm new in SQLAlchemy.
– J_log
Nov 21 at 17:50
1
@J_log: What I did here was use SQL, translated to SQLAlchemy. Master SQL first if you want to learn how to construct queries like these, thesql
tag wiki has a section on tutorials and books. But don't despair, it took me a while to learn tricks like CTEs and proper aggregation and such.
– Martijn Pieters♦
Nov 21 at 17:56
@J_log: I've reworked the answer to add more explanation as to what is going on.
– Martijn Pieters♦
Nov 21 at 20:04
add a comment |
You can do this with a CTE (common table expression) for a select that produces buyer ids together with their request counts, so
buyer_id | request_count
:------- | :------------
1 | 5
2 | 3
3 | 1
4 | 1
You can filter here on the counts having to be greater than 0 to be listed.
You can then join the buyers table against that to produce:
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
but because we are using a CTE, you can also query the CTE for the lowest count value. In the above example, that's 1
, and you can add a WHERE
clause to the joined buyer-with-cte-counts query to filter the results down to only rows where the request_count
value is equal to that minimum number.
The SQL query for this is
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
)
The WITH request_counts AS (...)
defines a CTE, and it is that part that would produce the first table with buyer_id
and request_count
. The request_count
table is then joined with request
and the WHERE
clause does the filtering on the min(request_counts.request_count)
value.
Translating the above to Flask-SQLAlchemy code:
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()
Demo:
>>> __ = 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>]
I've also created a db<>fiddle here, containing the same queries, to play with.
You can do this with a CTE (common table expression) for a select that produces buyer ids together with their request counts, so
buyer_id | request_count
:------- | :------------
1 | 5
2 | 3
3 | 1
4 | 1
You can filter here on the counts having to be greater than 0 to be listed.
You can then join the buyers table against that to produce:
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
but because we are using a CTE, you can also query the CTE for the lowest count value. In the above example, that's 1
, and you can add a WHERE
clause to the joined buyer-with-cte-counts query to filter the results down to only rows where the request_count
value is equal to that minimum number.
The SQL query for this is
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
)
The WITH request_counts AS (...)
defines a CTE, and it is that part that would produce the first table with buyer_id
and request_count
. The request_count
table is then joined with request
and the WHERE
clause does the filtering on the min(request_counts.request_count)
value.
Translating the above to Flask-SQLAlchemy code:
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()
Demo:
>>> __ = 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>]
I've also created a db<>fiddle here, containing the same queries, to play with.
edited Nov 21 at 20:20
answered Nov 21 at 14:59
Martijn Pieters♦
699k13124202259
699k13124202259
Thanks a lot for your help i will try to understand your code. It will takes a time for me but i will. May be you could advise some good tutorial about complicated queries and how it need to be done. For me it's complicated i'm new in SQLAlchemy.
– J_log
Nov 21 at 17:50
1
@J_log: What I did here was use SQL, translated to SQLAlchemy. Master SQL first if you want to learn how to construct queries like these, thesql
tag wiki has a section on tutorials and books. But don't despair, it took me a while to learn tricks like CTEs and proper aggregation and such.
– Martijn Pieters♦
Nov 21 at 17:56
@J_log: I've reworked the answer to add more explanation as to what is going on.
– Martijn Pieters♦
Nov 21 at 20:04
add a comment |
Thanks a lot for your help i will try to understand your code. It will takes a time for me but i will. May be you could advise some good tutorial about complicated queries and how it need to be done. For me it's complicated i'm new in SQLAlchemy.
– J_log
Nov 21 at 17:50
1
@J_log: What I did here was use SQL, translated to SQLAlchemy. Master SQL first if you want to learn how to construct queries like these, thesql
tag wiki has a section on tutorials and books. But don't despair, it took me a while to learn tricks like CTEs and proper aggregation and such.
– Martijn Pieters♦
Nov 21 at 17:56
@J_log: I've reworked the answer to add more explanation as to what is going on.
– Martijn Pieters♦
Nov 21 at 20:04
Thanks a lot for your help i will try to understand your code. It will takes a time for me but i will. May be you could advise some good tutorial about complicated queries and how it need to be done. For me it's complicated i'm new in SQLAlchemy.
– J_log
Nov 21 at 17:50
Thanks a lot for your help i will try to understand your code. It will takes a time for me but i will. May be you could advise some good tutorial about complicated queries and how it need to be done. For me it's complicated i'm new in SQLAlchemy.
– J_log
Nov 21 at 17:50
1
1
@J_log: What I did here was use SQL, translated to SQLAlchemy. Master SQL first if you want to learn how to construct queries like these, the
sql
tag wiki has a section on tutorials and books. But don't despair, it took me a while to learn tricks like CTEs and proper aggregation and such.– Martijn Pieters♦
Nov 21 at 17:56
@J_log: What I did here was use SQL, translated to SQLAlchemy. Master SQL first if you want to learn how to construct queries like these, the
sql
tag wiki has a section on tutorials and books. But don't despair, it took me a while to learn tricks like CTEs and proper aggregation and such.– Martijn Pieters♦
Nov 21 at 17:56
@J_log: I've reworked the answer to add more explanation as to what is going on.
– Martijn Pieters♦
Nov 21 at 20:04
@J_log: I've reworked the answer to add more explanation as to what is going on.
– Martijn Pieters♦
Nov 21 at 20:04
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53407641%2fflask-sqlalchemy-query-for-count%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
You mean you need to find the one buyer with the fewest requests? That's different from a query for count.
– Martijn Pieters♦
Nov 21 at 8:10
1
What should happen if there is a tie? Say two buyers have each just one request, and all other buyers have more. Should both buyers be returned? Or just one? If so, which one?
– Martijn Pieters♦
Nov 21 at 8:11
Possible duplicate of SQLAlchemy ordering by count on a many to many relationship
– vishes_shell
Nov 21 at 8:11
@vishes_shell: many-to-many and many-to-one differ quite a bit, and this question is about finding a single or a small subset of results.
– Martijn Pieters♦
Nov 21 at 8:14
next question for the OP: should buyers without any requests (
Buyer.requests.count() == 0
) be ignored?– Martijn Pieters♦
Nov 21 at 8:14