2010-09-29 47 views
2
SELECT *, SUM(cardtype.price - cardtype.cost) AS profit 
FROM user 
LEFT OUTER JOIN card ON ( user.id = card.buyer_id) 
LEFT OUTER JOIN cardtype ON ( card.cardtype_id = cardtype.id) 
GROUP BY user.id 
ORDER BY profit DESC 

我尝试这样做:如何在django模型中执行这个sql?

User.objects.extra(select=dict(profit='SUM(cardtype.price-cardtype.cost)')).annotate(sum=Sum('card__cardtype__price')).order_by('-profit') 

但Django的自动添加SUM(cardtype.price)GROUP BY条款,以及SQL 不运行

这可以在没有原始SQL的情况下完成吗?


提供模型,别提这些中国字:)

class User(models.Model): 

    class Meta: 
     verbose_name = "用户" 
     verbose_name_plural = "用户" 
     ordering = ['-regtime'] 

    user_status= (
     ("normal", "正常"), 
     ("deregistered", "注销"), 
     ("locked", "锁定"), 
    ) 

    name = models.CharField("姓名", max_length=20, db_index=True) 
    spec_class = models.ForeignKey(SpecClass, verbose_name="专业班级") 
    idcard = models.CharField("身份证号", max_length=18) 
    mobileno = models.CharField("手机号", max_length=11) 
    password = models.CharField("密码", max_length=50) # plain 
    address = models.CharField("住址", max_length=100) 
    comment = models.TextField("备注") 
    certserial = models.CharField("客户证书序列号", max_length=100) 
    regtime = models.DateTimeField("注册时间", default=datetime.datetime.now) 
    lastpaytime = models.DateTimeField("上次付款时间", default=datetime.datetime.now) 
    credit = models.FloatField("信用额度", default=100) 
    money = models.FloatField("余额", default=0) 
    use_password = models.BooleanField("使用密码") 
    use_fetion = models.BooleanField("接收飞信提示") 
    status = models.CharField("账户状态", choices = user_status, default="normal", max_length=20, db_index=True) 

    def __unicode__(self): 
     return self.name 

class CardType(models.Model): 

    class Meta: 
     verbose_name = "点卡类型" 
     verbose_name_plural = "点卡类型" 
     ordering = ['name'] 

    name = models.CharField("类型名称", max_length=20, db_index=True) 
    note = models.CharField("说明", max_length=100) 
    offcial = models.BooleanField("官方卡", default=True) 
    available = models.BooleanField("可用", default=True, db_index=True) 
    payurl = models.CharField("充值地址", max_length=200) 
    price = models.FloatField("价格") 
    cost = models.FloatField("进货价格") 

    def __unicode__(self): 
     return u"%s(%.2f元%s)" % (self.name, self.price, u", 平台卡" if not self.offcial else "") 

    def profit(self): 
     return self.price - self.cost 
    profit.short_description = "利润" 


class Card(models.Model): 

    class Meta: 
     verbose_name = "点卡" 
     verbose_name_plural = "点卡" 
     ordering = ['-createtime'] 

    card_status = (
     ("instock", "未上架"), 
     ("available", "可用"), 
     ("sold", "已购买"), 
     ("invalid", "作废"), 
     ("returned", "退卡"), # sell to the same person ! 
     ("reselled", "退卡重新售出"), 
    ) 

    cardtype = models.ForeignKey(CardType, verbose_name="点卡类型") 
    serial = models.CharField("卡号", max_length=40) 
    password = models.CharField("卡密", max_length=20) 
    status = models.CharField("状态", choices = card_status, default="instock", max_length=20, db_index=True) 
    createtime = models.DateTimeField("入库时间") 
    buytime = models.DateTimeField("购买时间", blank=True, null=True) 
    buyer = models.ForeignKey(User, blank=True, null=True, verbose_name="买家") 

    def __unicode__(self): 
     return u'%s[%s]' % (self.cardtype.name, self.serial) 
+0

是的,你可以用django使用原始的sql查询。 – 2010-09-29 09:07:11

+0

@anand:OP明确表示_可以这样做**没有**原始SQL?_ – 2010-09-29 09:11:25

回答

1

首先,其中一个外连接看起来对于这种事情来说是个坏主意。由于您提供了没有您的模型信息,我只能猜测。

你是说你可能没有每个用户的CARD?这是有道理的。

你是否还说有些卡没有卡类型?这通常不合情理。你没有提供任何细节。但是,如果一张卡片没有卡片类型,我敢打赌你在你的应用程序的其他地方有问题,或者你选择了真正可怜的名字,这些名字并没有提供这些东西的含义。您应该修复应用程序的其他部分,以确保每张卡片实际上都具有卡片类型。或者你应该修复你的名字是有意义的。

显然,ORM语句使用内部连接,并且您的SQL使用外部连接。什么是真正的问题?如何正确执行外连接? 如果您花时间搜索[Django]和Left Outer Join,您会发现原始SQL是一个糟糕的主意。

或者是真正的问题如何正确地完成总和?从你自己的答案看来,SQL是错误的,你真的遇到了麻烦。如果是这样,请清理SQL以保持正确。

如果外部连接是问题的一部分 - 不仅仅是视觉噪声 - 那么您必须对这样的外部连接进行类似的操作。

def user_profit(): 
    for u in User.objects.all(): 
     profit = sum[ t.price - t.cost 
      for c in u.card_set.all() 
       for t in c.cardtype_set.all() ] 
     yield user, profit 

在您的视图函数中,您可以提供函数的值给模板来呈现报表。由于它是一个生成器,因此在内存中不会创建巨大的列表。如果您需要分页,您可以将生成器提供给分页器,并且所有内容都可以合理完成。

这通常与具有大量外连接的复杂原始SQL查询速度相当。

如果的确,卡片类型关系的卡片实际上并不是可选的,那么您可以稍微缩短它。你还有一个外部连接需要思考。

def user_profit(): 
    for u in User.objects.all(): 
     profit = sum[ c.cardtype.price - c.cardtype.cost 
      for c in u.card_set.all() ] 
     yield user, profit 
+0

非常感谢您的详细解答和耐心。 1)模型附在原始问题上。 2)用户有多张卡片,并且卡片具有卡片类型(非可选)。 3)SQL由Django ORM生成,删除了错误的GROUP BY条款。 4)您的答案已足够,不需要进一步的帮助,谢谢! – Proton 2010-09-29 13:24:31

+0

原始的SQL工作。这也是另一个作品: SELECT t。 *,SUM(t.cnt *(cardtype.price - cardtype.cost))AS profit FROM( SELECT user.id AS uid,user.name AS name,card.cardtype_id AS cardtype,COUNT(*)AS cnt FROM卡 INNER JOIN用户ON card.buyer_id = user.id GROUP BY user.id,card.cardtype_id )AS吨 INNER JOIN cardtype ON cardtype.id = t.cardtype GROUP BY t.uid ORDER BY利润DESC 哪一个在性能上更好?(格式似乎不起作用) – Proton 2010-09-29 14:40:49

+0

@Proton:额外 - 无用 - 外部联接将显示为“工作”。但是,如果Card to CardType关系不是可选的,那么Card和Type之间的外部连接是无意义的,完全错误的,以及无用的和混乱的。 – 2010-09-29 16:45:34

-1

好吧,我发现这个
Sum computed column in Django QuerySet

不得不使用原始SQL现在...
谢谢二!

+0

您没有:)现在只需点击此答案旁边的刻度线图标即可接受您自己的答案。 – 2010-09-29 09:49:56

+2

你看起来不是很难:http://stackoverflow.com/search?q=%5Bdjango%5D+left+outer+join。这是一个常见的问题。 – 2010-09-29 09:58:04

+0

这是你破坏的规则。规则1:首先搜索;问第二。既然你找到了自己的答案,你可以使用这个答案,并且从不在第一个地方发布重复问题。我们不喜欢重复。我们都有权通过首先搜索来避免轻微的重复,只有在我们阅读完所有相关问题后才会问。 – 2010-09-29 11:02:33