7

ユーザーが作成して投票したコンテンツを含む一連のテーブルがあります。

テーブルcontent_a

id         /* the id of the content */
user_id    /* the user that contributed the content */
content    /* the content */

テーブルcontent_b

id
user_id
content

テーブルcontent_c

id
user_id
content

テーブル投票

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 */

ユーザーのセットを選択し、ユーザーが作成したコンテンツへの投票の合計で注文できるようにしたいと思います。例えば、

SELECT * FROM users ORDER BY <sum of votes on all content associated with user>

DjangoのORMを使用してこれを実現する特定の方法はありますか、それとも生のSQLクエリを使用する必要がありますか?そして、生のSQLでこれを達成するための最も効率的な方法は何でしょうか?

4

3 に答える 3

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)

オプション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')

上記ValuesQuerySetの(またはuse )は、投票数の降順でsvalues_list()のIDのシーケンスを提供します。User()次に、それを使用してトップユーザーを取得できます。

オプションB.使用User.objects.raw

を使用すると、 forsvarirによって与えられた答えUser.objects.rawとほぼ同じクエリが得られました:

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
))

オプションC.その他の可能な方法

  • Michael Dunnによって提案されているように、モデル、たとえば、または他の相対モデルに非正規化vote_countまたはプロファイルします。オンフライで頻繁にアクセスする場合、これははるかに適切に動作します。UserUserProfilevote_count
  • を実行するDBビューを作成しUNION、それにモデルをマップします。これにより、クエリの作成が容易になります。
  • Pythonで並べ替えます。ツールキットと拡張機能が多数あるため、通常は大規模なデータを処理するのに最適な方法です。

Django ORMを使用してクエリを実行する前に、これらのテーブルをマッピングするDjangoモデルが必要です。それらが一致し、テーブルと一致するモデルであるUserと仮定すると、Votingusersvoting

User.objects.annotate(v=models.Sum('voting__vote')).order_by('v')
于 2012-05-20T15:14:04.610 に答える
3

生のSQLソリューションについては、ここでideoneに関する問題の大まかな複製を作成しました

データ設定:

create table content_a(id int, user_id int, content varchar(20));
create table content_b(id int, user_id int, content varchar(20));
create table content_c(id int, user_id int, content varchar(20));
create table voting(user_id int, content_id int, content_type_id int, vote int);
create table users(id int, name varchar(20));
insert into content_a values(1,1,'aaaa');
insert into content_a values(2,1,'bbbb');
insert into content_a values(3,1,'cccc');
insert into content_b values(1,2,'dddd');
insert into content_b values(2,2,'eeee');
insert into content_b values(3,2,'ffff');
insert into content_c values(1,1,'gggg');
insert into content_c values(2,2,'hhhh');
insert into content_c values(3,3,'iiii');
insert into users values(1, 'first');
insert into users values(2, 'second');
insert into users values(3, 'third');
insert into users values(4, 'voteonly');

-- user 1 net votes (2)
insert into voting values (1, 1, 1, 1);
insert into voting values (2, 3, 1, -1);
insert into voting values (3, 1, 1, 1); 
insert into voting values (4, 2, 1, 1); 

-- user 2 net votes (3)
insert into voting values (1, 2, 2, 1);
insert into voting values (1, 1, 2, 1);
insert into voting values (2, 3, 2, -1);
insert into voting values (4, 2, 2, 1);
insert into voting values (4, 2, 3, 1);

-- user 3 net votes (-1)
insert into voting values (2, 3, 3, -1);

基本的に、content_aのタイプは1、content_bのタイプは2、content_cのタイプは3であると想定しています。生のSQLを使用すると、2つの明らかなアプローチがあるようです。1つは、すべてのコンテンツを結合してから、ユーザーおよび投票テーブルと結合することです。このアプローチを以下でテストしました。

select users.*, sum(voting.vote)
from users, 
    voting, (
        SELECT     id, 1 AS content_type_id, user_id
        FROM         content_a
        UNION
        SELECT     id, 2 AS content_type_id, user_id
        FROM         content_b
        UNION
        SELECT     id, 3 AS content_type_id, user_id
        FROM         content_c) contents
where contents.user_id = users.id
and voting.content_id = contents.id
and voting.content_type_id = contents.content_type_id
group by users.id
order by sum(voting.vote) desc;

別の方法は、結合ステップなしで、コンテンツテーブルを投票テーブルに外部結合することであるように思われます。これはパフォーマンスが高いかもしれませんが、Visual StudioがSQLを書き直し続けているため、テストできませんでした... SQLは次のようになると思います(ただし、テストはしていません)。

select users.*, sum(voting.vote)
from users, voting, content_a, content_b, content_c
where users.id = content_a.user_id (+)
and users.id = content_b.user_id (+)
and users.id = content_c.user_id (+)
and ((content_a.id = voting.content_id and voting.content_type_id = 1) OR
     (content_b.id = voting.content_id and voting.content_type_id = 2) OR
     (content_c.id = voting.content_id and voting.content_type_id = 3))
group by users.id
order by sum(voting.vote) desc;
于 2012-05-29T07:41:13.843 に答える
0

事前に計算された値を使用してこれを行います。まず、各ユーザーが受け取った投票を保存するための個別のテーブルを作成します。

class VotesReceived(models.Model):
    user = models.OneToOneField(User, primary_key=True)
    count = models.IntegerField(default=0, editable=False)

次に、post_saveシグナルを使用して、投票が行われるたびにカウントを更新します。

def update_votes_received(sender, instance, **kwargs):
    # `instance` is a Voting object
    # assuming here that `instance.content.user` is the creator of the content
    vr, _ = VotesReceived.objects.get_or_create(user=instance.content.user)
    # you should recount the votes here rather than just incrementing the count
    vr.count += 1 
    vr.save()

models.signals.post_save.connect(update_votes_received, sender=Voting)

使用法:

user = User.objects.get(id=1)
print user.votesreceived.count

データベースにすでにデータがある場合は、もちろん、最初に手動で投票数を更新する必要があります。

于 2012-05-29T08:28:21.727 に答える