2012-05-20 14 views
7

मेरे पास टेबल का एक सेट है जिसमें उपयोगकर्ताओं द्वारा बनाई गई और मतदान की गई सामग्री शामिल है।विशिष्ट जटिल एसक्यूएल क्वेरी और Django ORM?

टेबल 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 क्वेरी का उपयोग करने की क्या ज़रूरत है है? और कच्चे एसक्यूएल में इसे हासिल करने का सबसे प्रभावी तरीका क्या होगा?

+0

अपनी 'मतदान' तालिका में वोट देने के बाद, आप यह बता सकते हैं कि यह किस सामग्री तालिका से संबंधित है? यदि एक से अधिक तालिका में 'content_id' मौजूद है तो क्या होगा? – eggyal

+0

मैं क्षमा चाहता हूं, मैं एक कॉलम शामिल करना भूल गया। – mburke13

उत्तर

6

अद्यतन

मान लिया जाये कि मॉडल हैं

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) 

विकल्प ए का प्रयोग 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, (या values_list()) आप में से एक दृश्य देता है User() एस की आईडी अवरोही वोट गिनती के क्रम में। इसके बाद आप शीर्ष उपयोगकर्ताओं को लाने के लिए इसका उपयोग कर सकते हैं।

विकल्प बी User.objects.raw

जब मैं User.objects.raw का उपयोग का उपयोग करना, मैं लगभग एक ही क्वेरी डब्ल्यू/the answer given by 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 
)) 

विकल्प सी अन्य संभव तरीके

  • डी-सामान्यीकृत vote_count से User या प्रोफाइल मॉडल, उदाहरण के लिए, UserProfile, या अन्य रिश्तेदार मॉडल, suggested by Michael Dunn के रूप में। यदि आप vote_count पर अक्सर उड़ान भरते हैं तो यह बहुत बेहतर व्यवहार करता है।
  • एक डीबी व्यू बनाएं जो आपके लिए UNION एस करता है, फिर उसके लिए एक मॉडल मैप करें, इससे क्वेरी का निर्माण आसान हो सकता है।
  • पायथन में क्रमबद्ध करें, आमतौर पर दर्जन टूलकिट्स और एक्सटेंशन तरीकों के कारण बड़े पैमाने पर डेटा के लिए काम करने का यह सबसे अच्छा तरीका है।

आप कुछ Django मॉडल मानचित्रण से पहले उपयोग Django ORM उन तालिकाओं क्वेरी करने के लिए की जरूरत है। मान लिया जाये कि वे सकता है तो

User.objects.annotate(v=models.Sum('voting__vote')).order_by('v') 
+0

यह काम नहीं करेगा, मतदान तालिका कॉलम 'user_id' उपयोगकर्ता द्वारा किए गए वोट से जुड़ा हुआ है। मैं उपयोगकर्ता द्वारा बनाई गई उपयोगकर्ता की सामग्री पर वोटों को जोड़ना चाहता हूं। – mburke13

+0

@ मैट मैं देखता हूं। 'Content_a',' content_b' और 'content_c' के लिए मॉडल क्या हैं? – okm

+0

मॉडल बहुत सामान्य हैं। मुझे लगता है कि ध्यान देने योग्य एकमात्र महत्वपूर्ण बात यह है कि प्रत्येक सामग्री मॉडल किसी उपयोगकर्ता से विदेशीकी (उपयोगकर्ता) संबंध से संबंधित होता है और यह कि सामग्री सामग्री आईडी और सामग्री के साथ जेनेरिक फॉर्निनेकी संबंध द्वारा मतदान तालिका में मतदान से संबंधित प्रत्येक सामग्री मॉडल संबंधित है। सामग्री प्रकार। मुझे लगता है कि मैं जो हासिल करना चाहता हूं वह Django के ओआरएम के लिए बहुत जटिल है, इसलिए मैं पहले एसक्यूएल में ऐसा करने का सबसे अच्छा तरीका जानने की कोशिश कर रहा हूं। इसके कारण, मैंने केवल Django मॉडल के बजाय डेटाबेस तालिका संरचनाएं दीं। यदि Django में ऐसा करने का कोई तरीका है, तो मुझे यह सुनकर खुशी होगी। – mburke13

3

एक कच्चे एसक्यूएल समाधान के लिए User और Voting मॉडल users और voting टेबल मिलान कर रहे हैं, आप, मैं पर ideone here

डेटा सेटअप आपकी समस्या का एक मोटा प्रतिकृति बना लिया है:

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. कच्चे एसक्यूएल का उपयोग करके, टी लगता है स्पष्ट दृष्टिकोण। सबसे पहले सभी सामग्री को एक साथ जोड़ना है, फिर उपयोगकर्ताओं और मतदान तालिकाओं के साथ इसमें शामिल होना। मैंने नीचे इस दृष्टिकोण का परीक्षण किया है।

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; 

वैकल्पिक विकल्प यूनियन चरण के बिना मतदान तालिका में सामग्री तालिकाओं में शामिल होने के लिए प्रतीत होता है। यह अधिक प्रदर्शनकारी हो सकता है, लेकिन मैं इसका परीक्षण करने में सक्षम नहीं हूं क्योंकि विजुअल स्टूडियो मेरे लिए मेरे एसक्यूएल को फिर से लिखता रहता है ... मुझे उम्मीद है कि एसक्यूएल इस तरह कुछ दिखने की उम्मीद करेगा (लेकिन मैंने इसका परीक्षण नहीं किया है):

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; 
+0

'चयन आईडी' में '1', 1 AS content_type_id, user_id content_c' से एक टाइपो हो सकता है? – okm

+0

@okm: धन्यवाद, आप सही हैं कि यह 3 होना चाहिए था, मैंने इसे अपडेट किया है। – forsvarir

0

मैं इसे पूर्व निर्धारित मानों का उपयोग करके करूँगा।

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) 

उपयोग::

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

तो गिनती हर बार एक वोट किया जाता है अद्यतन करने के लिए एक post_save signal का उपयोग करें: सबसे पहले वोट कि प्रत्येक उपयोगकर्ता प्राप्त हुआ है स्टोर करने के लिए एक अलग तालिका बनाने के

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

यदि आपके पास पहले से ही आपके डेटाबेस में डेटा है तो आपको पाठ्यक्रम की पहली बार मैन्युअल रूप से वोट गणना करना होगा।

संबंधित मुद्दे