2012-01-12 56 views
7

我在Django中建立了一個複雜的數據庫模型,我必須根據過濾器數據做一些計算。我有一個Test對象,一個TestAttempt對象和一個UserProfile對象(外鍵返回測試,外鍵返回給用戶配置文件)。我在TestAttempt上運行了一個方法,該方法計算測試分數(根據用戶提供的選項數量與與每個測試相關的正確答案進行比較)。然後我在Test上運行另一種方法,該方法根據每個關聯的TestAttempt的平均測試分數計算平均測試分數。但有時我只想要基於與特定集關聯的關聯TestAttempt的供應子集的平均值的UserProfiles。因此,不要像這樣計算特定測試的平均測試分數:查詢查詢的Django __in查詢效率

[x.score() for x in self.test_attempts.all()] 

然後對這些值進行平均。 我做了這樣的查詢:

[x.score() for x in self.test_attempts.filter(profile__id__in=user_id_list).all()] 

其中user_id_list爲用戶配置ID對,我想找到一個列表的形式平均測試成績的特定子集。我的問題是:如果user_id_list確實是整套UserProfile的(因此過濾器將返回與self.test_attempts.all()相同的值),並且大多數情況下都是這樣,是否支付檢查此案件的費用,如果是這樣根本不執行過濾器?或者__in查找效率足夠高,即使user_id_list包含所有用戶,運行過濾器效率也會更高。另外,我是否需要擔心產生test_attempts distinct()?或者他們不可能用我的queryset的結構變成重複的?

編輯:任何人誰的興趣看原始的SQL查詢,它看起來像這樣不使用濾鏡:

SELECT "mc_grades_testattempt"."id", "mc_grades_testattempt"."date", 
"mc_grades_testattempt"."test_id", "mc_grades_testattempt"."student_id" FROM 
"mc_grades_testattempt" WHERE "mc_grades_testattempt"."test_id" = 1 

,這與過濾器:

SELECT "mc_grades_testattempt"."id", "mc_grades_testattempt"."date", 
"mc_grades_testattempt"."test_id", "mc_grades_testattempt"."student_id" FROM 
"mc_grades_testattempt" INNER JOIN "mc_grades_userprofile" ON 
("mc_grades_testattempt"."student_id" = "mc_grades_userprofile"."id") WHERE 
("mc_grades_testattempt"."test_id" = 1 AND "mc_grades_userprofile"."user_id" IN (1, 2, 3)) 

請注意,數組(1,2,3)只是一個例子

+0

這兩種情況下生成的SQL是什麼? – 2012-01-12 03:28:17

+0

不知道,我將如何輸出特定查詢集的SQL?編輯,找出它。給我一點時間找到它 – ecbtln 2012-01-12 03:33:15

+0

SQL查詢已被添加 – ecbtln 2012-01-12 03:41:16

回答

2
  1. 簡短答案是 - 基準。在不同的情況下測試它並測量負載。這將是最好的答案。

  2. 這裏不能有重複。

  3. 檢查兩個座標是否真的存在問題?這裏的hypotetic代碼:

    def average_score(self, user_id_list=None): 
        qset = self.test_attempts.all() 
        if user_id_list is not None: 
         qset = qset.filter(profile__id__in=user_id_list) 
        scores = [x.score() for x in qset] 
        # and compute the average 
    
  4. 我不知道該怎麼做score方法做,但你不能在計算分貝值的平均值?它會給你更顯着的性能提升。

  5. 不要忘記緩存。

2

從我所瞭解的文檔中,所有查詢都是在實際使用之前構建的。因此,例如,test_attempts.all()會生成一次SQL代碼,當您執行查詢時,實際上通過執行類似.count()for t in test_attempts.all():等的操作獲取數據,它會在數據庫上運行查詢並返回Queryset對象或僅返回一個Object得到()。考慮到這一點,對數據庫的調用次數將完全相同,而實際調用會有所不同。正如你在你編輯的文章中顯示的那樣,原始查詢是不同的,但是在Django訪問數據之前,它們都以相同的方式生成。從Django的角度來看,它們都將以相同的方式創建,然後在數據庫上執行。在我看來,最好不要測試all()情況,因爲你必須運行兩個查詢來確定這一點。我相信你應該使用你有的代碼運行,並跳過檢查all()場景,這是你描述的最常見的情況。大多數現代數據庫引擎都以這樣的方式運行查詢,即添加的連接不會妨礙性能指標,因爲它們以最佳順序處理查詢,無論如何。

2

使用Annotation代替遍歷查詢集,這是建立在user_id_list每一項新的數據庫命中和蟒蛇進行平均。

ms = MyModel.objects.annotate(Avg('some_field')) 
ms[0].avg__some_field # prints the average for that instance 

將查詢集返回查詢集中對象的屬性作爲屬性。使用ORM可能需要對外鍵關係進行結構更改,以及哪個模型保存哪些數據以便使註釋方便。如有必要,這種重新排序會產生有益的副作用(數據喜歡以某種方式生活),所以這是一個很好的練習。