问题是:我有一袋(表A)蔬菜(表B)和水果(表C)。袋子里可以装多种蔬菜和多种水果。每种蔬菜和水果都有价格。
我需要查询返回包中物品的总数量(整数)和价格总和(小数)。袋子可以有 0 个蔬菜,但有 3 个水果,反之亦然,它可以有物品,但是免费的,也可以是空的,但有价格(袋子本身的价格)。所以我们只过滤掉最后有 0 件商品和 0 总价格的袋子。
我能做的最好的就是四个像这样的子查询:
query = select(Bag.id, Bag.purchase_datetime)
vegetable_price = select(func.sum(Vegetable.amount)).where(Vegetable.bag_id == Bag.id).scalar_subquery().label("vegetable_price_total")
vegetable_count = select(func.count(Vegetable.id)).where(Vegetable.bag_id == Bag.id).scalar_subquery().label("vegetable_count_total")
fruit_price = select(func.count(Fruit.id)).where(Fruit.bag_id == Bag.id).scalar_subquery().label("fruit_price_total")
fruit_count = select(func.count(Fruit.id)).where(Fruit.bag_id == Bag.id).scalar_subquery().label("fruit_count_total")
query = add_columns(vegetable_price, vegetable_count, fruit_price, fruit_count)
然后根据路线的参数(仅询问水果、仅询问蔬菜或所有类型),我有一个查询。where(fruit_price + Vegetable_price > 0)。
最后我将这些列添加到路由返回的对象中,如下所示:
result = session.execute(query.distinct())
for b in result:
yield BagObject(
id=b.id
item_count=(getattr(b, "vegetable_count_total", 0) or 0) + (getattr(b, "fruit_count_total", 0) or 0),
total_price=(getattr(b, "vegetable_price_total", 0) or 0) + (getattr(b, "fruit_price_total", 0) or 0),
)
即使作为后端开发的初学者,我也知道这很糟糕。我不可能用更少的代码使用更优化的解决方案来做到这一点。我可以从具有更多 SQL 经验的人那里获得帮助吗?
我尝试了其他东西,比如 join、outerjoin。但所有这些都会导致获得重复的行,并且价格/item_count 会在最终结果中相乘。
您说您尝试过加入,但得到了重复项。这可能是因为您将蔬菜和水果放入了他们的袋子中。在 SQL 中,这会为您提供一个表,其中各行包含一种蔬菜和一种水果,因此对于一袋 3 种蔬菜和 4 种水果,您将生成 3 x 4 = 12 行。
您想要做的是将蔬菜 count 和水果 count 添加到他们的袋子中。
我不清楚你的表中有哪些列。从您的代码中,我假设如下:
列_名称 | 内容 |
---|---|
bag_id | 购买包 |
水果名称 | 水果名称 |
金额 | 本次购买该水果的总金额 |
价格 | 本次购买该水果的总价 |
列_名称 | 内容 |
---|---|
bag_id | 购买包 |
蔬菜名称 | 蔬菜名称 |
金额 | 本次购买该蔬菜的总金额 |
价格 | 本次购买的该蔬菜的总价 |
这是用 SQL 编写的一种方法。 (我不了解 SQLAlchemy,所以我无法帮助您。)
with
v as
(
select
bag_id,
sum(amount) as total_amount,
sum(price) as total_price
from vegetable
where amount <> 0 or price <> 0
group by bag_id
),
f as
(
select
bag_id,
sum(amount) as total_amount,
sum(price) as total_price
from fruit
where amount <> 0 or price <> 0
group by bag_id
)
select
bag_id,
coalesce(v.total_amount, 0) as vegetable_amount,
coalesce(f.total_amount, 0) as fruit_amount,
coalesce(v.total_amount, 0) + coalesce(f.total_amount, 0) as total_amount,
coalesce(v.total_price, 0) as vegetable_price,
coalesce(f.total_price, 0) as fruit_price,
coalesce(v.total_price, 0) + coalesce(f.total_price, 0) as total_price
from v full outer join f using (bag_id);