You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将用户资产统计SQL查询转换为Django ORM语句?

Django ORM 转换:统计用户资产

问题背景

我有一个包含4个模型的Django项目,定义如下:

stock/models.py

class StockIndex(models.Model):
  name = models.CharField(max_length=128)
  
  def __str__(self):
    return self.name

class Stock(models.Model):
  code = models.CharField(max_length=24)
  name = models.CharField(max_length=128)
  price = models.DecimalField(max_digits=10, decimal_places=2)
  dividend_rate = models.DecimalField(decimal_places=2, max_digits=4)
  index = models.ForeignKey(StockIndex, related_name='stocks', on_delete=models.SET_NULL, null=True)
  
  def __str__(self):
    return self.name

class StockSet(models.Model):
  code = models.ForeignKey(Stock, related_name='stock_set', on_delete=models.CASCADE)
  quantity = models.IntegerField()
  owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='stock_set', to_field='wallet')
  
  def __str__(self):
    return f'{self.owner} - {self.code}'

users/models.py

class User(AbstractBaseUser, PermissionsMixin):
  
  objects = UserManager()
  
  username = models.CharField(
      max_length=150,
      unique=True,
  ) 
  email = models.EmailField(blank=True)
  is_staff = models.BooleanField(
    default=False,
  )
  is_active = models.BooleanField(
    default=True,
  )
  wallet = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
  
  EMAIL_FIELD = "email"
  USERNAME_FIELD = "username"
  REQUIRED_FIELDS = ["email"]
  
  def __str__(self):
    return self.username

需要统计每个用户的资产,计算规则为:将每个StockSet对象的quantity字段与对应Stock的price字段相乘后求和。对应的SQL查询语句已写出:

SELECT u.username, u.email, SUM(st.quantity * s.price) AS user_asset 
FROM stock_stockset st JOIN users_user u ON (st.owner_id = u.wallet) 
JOIN stock_stock s ON (st.code_id = s.id) 
GROUP BY u.username;

现在需要把该SQL转换为Django ORM语句,填充到UserView的queryset中:

class UserView(ListAPIView):
  queryset = ?
  serializer_class = UserSerializer

解决方案

用Django ORM实现统计

推荐通过get_queryset方法动态生成查询集,结合annotate、F表达式和聚合函数Sum实现需求:

from django.db.models import Sum, F
from users.models import User
from rest_framework.generics import ListAPIView

class UserView(ListAPIView):
    serializer_class = UserSerializer
    
    def get_queryset(self):
        return User.objects.annotate(
            user_asset=Sum(F('stock_set__quantity') * F('stock_set__code__price'))
        ).filter(stock_set__isnull=False).distinct()

代码说明:

  • F表达式:直接引用数据库层面的字段值,在数据库内完成quantity和price的乘积计算,比拉取数据到内存计算更高效。
  • Sum聚合:对每个用户的所有持仓乘积结果求和,得到总资产品值user_asset。
  • 过滤与去重:filter(stock_set__isnull=False)过滤无任何股票持仓的用户(可按需移除);distinct()避免关联查询导致的用户记录重复。

配套更新序列化器

要让API响应返回user_asset字段,需修改UserSerializer:

from rest_framework import serializers
from users.models import User

class UserSerializer(serializers.ModelSerializer):
    user_asset = serializers.DecimalField(max_digits=12, decimal_places=2, read_only=True)
    
    class Meta:
        model = User
        fields = ['username', 'email', 'user_asset']

备选:直接执行原生SQL

如果ORM写法不符合预期,也可以用raw()方法执行你已编写的SQL语句:

class UserView(ListAPIView):
    serializer_class = UserSerializer
    
    def get_queryset(self):
        return User.objects.raw("""
            SELECT u.username, u.email, SUM(st.quantity * s.price) AS user_asset 
            FROM stock_stockset st JOIN users_user u ON (st.owner_id = u.wallet) 
            JOIN stock_stock s ON (st.code_id = s.id) 
            GROUP BY u.username;
        """)

使用原生SQL时,序列化器同样需要添加user_asset字段,确保字段名与SQL返回结果一致。

内容的提问来源于stack exchange,提问作者Antoliny Lee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 10:57:21