具体的复杂SQL查询和Django ORM?

mbu*_*e13 7 sql django orm

我有一组表,其中包含用户创建和投票的内容.

content_a

id         /* the id of the content */
user_id    /* the user that contributed the content */
content    /* the content */
Run Code Online (Sandbox Code Playgroud)

content_b

id
user_id
content
Run Code Online (Sandbox Code Playgroud)

content_c

id
user_id
content
Run Code Online (Sandbox Code Playgroud)

投票

user_id         /* the user that made the vote */
content_id      /* the content the vote was made on */
content_type_id /* the content type the vote was made on */
vote            /* the value of the vote, either +1 or -1 */
Run Code Online (Sandbox Code Playgroud)

我希望能够选择一组用户,并根据他们制作的内容的总票数对它们进行排序.例如,

SELECT * FROM users ORDER BY <sum of votes on all content associated with user>
Run Code Online (Sandbox Code Playgroud)

有没有一种特定的方法可以使用Django的ORM实现,或者我是否必须使用原始SQL查询?在原始SQL中实现这一目标的最有效方法是什么?

okm*_*okm 7

更新

假设模型是

from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType


class ContentA(models.Model):
    user = models.ForeignKey(User)
    content = models.TextField()

class ContentB(models.Model):
    user = models.ForeignKey(User)
    content = models.TextField()

class ContentC(models.Model):
    user = models.ForeignKey(User)
    content = models.TextField()

class GenericVote(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey()
    user = models.ForeignKey(User)
    vote = models.IntegerField(default=1)
Run Code Online (Sandbox Code Playgroud)

选项A.使用 GenericVote

GenericVote.objects.extra(select={'uid':"""
CASE
WHEN content_type_id = {ct_a} THEN (SELECT user_id FROM {ContentA._meta.db_table} WHERE id = object_id)
WHEN content_type_id = {ct_b} THEN (SELECT user_id FROM {ContentB._meta.db_table} WHERE id = object_id)
WHEN content_type_id = {ct_c} THEN (SELECT user_id FROM {ContentC._meta.db_table} WHERE id = object_id)
END""".format(
ct_a=ContentType.objects.get_for_model(ContentA).pk,
ct_b=ContentType.objects.get_for_model(ContentB).pk,
ct_c=ContentType.objects.get_for_model(ContentC).pk,
ContentA=ContentA,
ContentB=ContentB,
ContentC=ContentC
)}).values('uid').annotate(vc=models.Sum('vote')).order_by('-vc')
Run Code Online (Sandbox Code Playgroud)

上面的ValuesQuerySet(或使用values_list())User()按递减计票次数的顺序给出了一系列s 的ID .然后,您可以使用它来获取最高用户.

选项B.使用 User.objects.raw

当我使用时User.objects.raw,我得到了与forsvarir给出的答案几乎相同的查询:

User.objects.raw("""
SELECT "{user_tbl}".*, SUM("gv"."vc") as vote_count from {user_tbl},
    (SELECT id, user_id, {ct_a} AS ct FROM {ContentA._meta.db_table} UNION
     SELECT id, user_id, {ct_b} AS ct FROM {ContentB._meta.db_table} UNION
     SELECT id, user_id, {ct_c} as ct FROM {ContentC._meta.db_table}
    ) as c,
   (SELECT content_type_id, object_id, SUM("vote") as vc FROM {GenericVote._meta.db_table} GROUP BY content_type_id, object_id) as gv
WHERE {user_tbl}.id = c.user_id
    AND gv.content_type_id = c.ct
    AND gv.object_id = c.id
GROUP BY {user_tbl}.id
ORDER BY "vc" DESC""".format(
    user_tbl=User._meta.db_table, ContentA=ContentA, ContentB=ContentB,
    ContentC=ContentC, GenericVote=GenericVote, 
    ct_a=ContentType.objects.get_for_model(ContentA).pk,
    ct_b=ContentType.objects.get_for_model(ContentB).pk,
    ct_c=ContentType.objects.get_for_model(ContentC).pk
))
Run Code Online (Sandbox Code Playgroud)

选项C.其他可能的方式

  • 例如,如Michael Dunn建议的那样,对模型进行vote_count去标准化User或分析模型,UserProfile或其他相对模型.如果您经常访问,这会表现得更好.vote_count
  • 构建一个DB视图,UNION为您执行s,然后将模型映射到它,这可以使查询的构造更容易.
  • 在Python中排序,通常它是处理大规模数据的最佳方式,因为有十几种工具包和扩展方式.

在使用Django ORM进行查询之前,您需要一些Django模型映射这些表.假设他们是UserVoting模型匹配usersvoting表,然后你可以

User.objects.annotate(v=models.Sum('voting__vote')).order_by('v')
Run Code Online (Sandbox Code Playgroud)