2010-09-29 48 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