2013-02-12 20 views
32

मैं दो मॉडल इस तरह है:एक Django क्वेरीसमूह, के लिए फिल्टर करने के लिए कैसे में "नहीं मौजूद है" एक बहुत-से-एक रिश्ते में

class User(models.Model): 
    email = models.EmailField() 

class Report(models.Model): 
    user = models.ForeignKey(User) 

हकीकत में एक मॉडल के अधिक क्षेत्रों कोई महत्व के हैं जो इस सवाल के लिए।

मैं उन सभी उपयोगकर्ताओं को फ़िल्टर करना चाहता हूं जिनके पास एक ईमेल है जो 'ए' से शुरू होता है और कोई रिपोर्ट नहीं है। अन्य क्षेत्रों के आधार पर .filter() और .exclude() मानदंड होंगे।

मैं इस तरह यह दृष्टिकोण हैं:

users = User.objects.filter(email__like = 'a%') 

users = users.filter(<other filters>) 

users = ??? 

मैं चाहते हैं ??? उन उपयोगकर्ताओं को फ़िल्टर करने के लिए जिनके पास उनके साथ जुड़े रिपोर्ट नहीं हैं। यह मैं कैसे करूंगा? यदि यह संभव नहीं है जैसा मैंने प्रस्तुत किया है, तो वैकल्पिक दृष्टिकोण क्या है?

उत्तर

60

उपयोग isnull

users_without_reports = User.objects.filter(report__isnull=True) 
users_with_reports = User.objects.filter(report__isnull=False).distinct() 

आप isnull=False उपयोग करते हैं, distinct() डुप्लिकेट परिणाम को रोकने के लिए आवश्यक है।

+6

यह ठीक है, लेकिन यह '__isnull = True' और' __isnull = गलत 'दोनों के मामले में' रिपोर्ट 'के साथ' बाहरी जॉइन 'उत्पन्न करता है। रिपोर्ट वाले उपयोगकर्ताओं के बारे में प्रश्न के लिए यह 'इनर जॉइन' से कम कुशल हो सकता है। मुझे इस मामले के लिए एक बदसूरत हैक मिला है: 'User.objects.filter (report__id__gt = 0) .distinct()'। यह मानता है कि आईडी> 0 हैं, जिन्हें एक मामला नहीं होना चाहिए। किसी भी शामिल होने के लिए मजबूर करने का कोई बेहतर तरीका, कोई भी? –

3

उपयोगकर्ताओं को, जो उन लोगों के साथ संबद्ध रिपोर्ट की जरूरत नहीं है इस कोशिश को फिल्टर करने के लिए:

users = User.objects.exclude(id__in=[elem.user.id for elem in Report.objects.all()])

+0

यह वादा किया जा रहा है। मुझे यह देखने की ज़रूरत है कि यह किस प्रकार का एसक्यूएल उत्पन्न करेगा। मुझे लगता है कि 'id__in = Report.objects.all()' 'exclude() 'कॉल के लिए पर्याप्त होगा। –

+2

यदि आप केवल 'उपयोगकर्ता = User.objects.exclude (id__in = Report.objects.all()) का उपयोग करते हैं, तो आप सभी उपयोगकर्ताओं को प्राप्त करेंगे जो आईडी किसी भी रिपोर्ट आईडी –

+3

जैसा ही है, यह एक बुरा जवाब है, सिवाय इसके कि जब आप' केवल वेबसाइट का उपयोग करने जा रहे हैं, या आपके दोस्तों को बंद कर देगा;) यदि आपके पास 1 बिलियन रिपोर्ट हैं, तो यह शायद डेटाबेस को क्रैश कर देगी। अंगूठे का नियम: कभी भी प्रश्नों में '__in =' का उपयोग न करें! इसके अलावा, आपके पास 'Report.objects.values ​​(' user_id ') भी नहीं है।अलग() ', जो कई रिपोर्टों के साथ थोड़ा सा मदद करेगा लेकिन कुछ उपयोगकर्ता परिदृश्य। –

5

देशी एसक्यूएल प्राप्त करने के लिए एक ही रास्ता मौजूद है/नहीं अतिरिक्त प्रश्नों के बिना मौजूद है या मिलती कच्चे एसक्यूएल के रूप में यह जोड़ने के लिए .extra() खंड में है:

users = users.extra(where=[ 
    """NOT EXISTS(SELECT 1 FROM {reports} 
        WHERE user_id={users}.id) 
    """.format(reports=Report._meta.db_table, users=User._meta.db_table) 
]) 

वास्तव में, यह एक बहुत स्पष्ट है और कुशल समाधान और मुझे कभी-कभी आश्चर्य होता है कि यह एक लुकअप के रूप में Django में क्यों नहीं बनाया गया था। इसके अलावा यह सबकुरी को खोजने के लिए उपनिवेश को परिशोधित करने की अनुमति देता है उदा। केवल के दौरान पिछले सप्ताह, या [अनुत्तरित] अनुत्तरित/अनदेखी रिपोर्ट के साथ रिपोर्ट के साथ [केवल] उपयोगकर्ता।

3

Alasdair's answer सहायक है, लेकिन मुझे distinct() का उपयोग करना पसंद नहीं है। यह कभी-कभी उपयोगी हो सकता है, लेकिन आमतौर पर यह एक कोड गंध है जो आपको बताती है कि आपने अपने जुड़ने को गड़बड़ कर दिया है।

सौभाग्य से, Django के queryset आपको सबक्वायरीज़ पर फ़िल्टर करने देता है।

2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) PRAGMA foreign_keys; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) PRAGMA foreign_keys = 0; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) BEGIN; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.schema.execute(): CREATE TABLE "udjango_user" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "email" varchar(254) NOT NULL); (params None) 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) CREATE TABLE "udjango_user" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "email" varchar(254) NOT NULL); args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) PRAGMA foreign_keys = 0; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) PRAGMA foreign_keys; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) PRAGMA foreign_keys = 0; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) BEGIN; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.schema.execute(): CREATE TABLE "udjango_report" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "user_id" integer NOT NULL REFERENCES "udjango_user" ("id")); (params None) 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) CREATE TABLE "udjango_report" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "user_id" integer NOT NULL REFERENCES "udjango_user" ("id")); args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.schema.execute(): CREATE INDEX "udjango_report_e8701ad4" ON "udjango_report" ("user_id"); (params []) 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) CREATE INDEX "udjango_report_e8701ad4" ON "udjango_report" ("user_id"); args=[] 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) PRAGMA foreign_keys = 0; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) BEGIN; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) INSERT INTO "udjango_user" ("email") VALUES ('[email protected]'); args=['[email protected]'] 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) BEGIN; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) INSERT INTO "udjango_user" ("email") VALUES ('[email protected]'); args=['[email protected]'] 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) BEGIN; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) INSERT INTO "udjango_user" ("email") VALUES ('[email protected]'); args=['[email protected]'] 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) BEGIN; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) INSERT INTO "udjango_user" ("email") VALUES ('[email protected]'); args=['[email protected]'] 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) BEGIN; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) INSERT INTO "udjango_report" ("user_id") VALUES (1); args=[1] 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) BEGIN; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) INSERT INTO "udjango_report" ("user_id") VALUES (3); args=[3] 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) BEGIN; args=None 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) INSERT INTO "udjango_report" ("user_id") VALUES (3); args=[3] 
2017-10-06 09:56:22[INFO]root.main(): users without reports 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) SELECT "udjango_user"."id", "udjango_user"."email" FROM "udjango_user" LEFT OUTER JOIN "udjango_report" ON ("udjango_user"."id" = "udjango_report"."user_id") WHERE ("udjango_report"."id" IS NULL AND "udjango_user"."email" LIKE 'a%' ESCAPE '\') LIMIT 21; args=(u'a%',) 
2017-10-06 09:56:22[INFO]root.main(): [User(u'[email protected]')] 
2017-10-06 09:56:22[INFO]root.main(): users with reports (allows duplicates) 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) SELECT "udjango_user"."id", "udjango_user"."email" FROM "udjango_user" INNER JOIN "udjango_report" ON ("udjango_user"."id" = "udjango_report"."user_id") WHERE ("udjango_report"."id" IS NOT NULL AND "udjango_user"."email" LIKE 'a%' ESCAPE '\') LIMIT 21; args=(u'a%',) 
2017-10-06 09:56:22[INFO]root.main(): [User(u'[email protected]'), User(u'[email protected]'), User(u'[email protected]')] 
2017-10-06 09:56:22[INFO]root.main(): users with reports (no duplicates) 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) SELECT "udjango_user"."id", "udjango_user"."email" FROM "udjango_user" WHERE (NOT ("udjango_user"."id" IN (SELECT U0."id" AS Col1 FROM "udjango_user" U0 LEFT OUTER JOIN "udjango_report" U1 ON (U0."id" = U1."user_id") WHERE U1."id" IS NULL)) AND "udjango_user"."email" LIKE 'a%' ESCAPE '\') LIMIT 21; args=(u'a%',) 
2017-10-06 09:56:22[INFO]root.main(): [User(u'[email protected]'), User(u'[email protected]')] 
2017-10-06 09:56:22[INFO]root.main(): users with reports (no duplicates, simpler SQL) 
2017-10-06 09:56:22[DEBUG]django.db.backends.execute(): (0.000) SELECT "udjango_user"."id", "udjango_user"."email" FROM "udjango_user" WHERE ("udjango_user"."email" LIKE 'a%' ESCAPE '\' AND "udjango_user"."id" IN (SELECT U0."user_id" FROM "udjango_report" U0)) LIMIT 21; args=(u'a%',) 
2017-10-06 09:56:22[INFO]root.main(): [User(u'[email protected]'), User(u'[email protected]')] 
2017-10-06 09:56:22[INFO]root.main(): Done. 

:

# Tested with Django 1.9.2 
import logging 
import sys 

import django 
from django.apps import apps 
from django.apps.config import AppConfig 
from django.conf import settings 
from django.db import connections, models, DEFAULT_DB_ALIAS 
from django.db.models.base import ModelBase 

NAME = 'udjango' 


def main(): 

    setup() 

    class User(models.Model): 
     email = models.EmailField() 

     def __repr__(self): 
      return 'User({!r})'.format(self.email) 

    class Report(models.Model): 
     user = models.ForeignKey(User) 

    syncdb(User) 
    syncdb(Report) 

    anne = User.objects.create(email='[email protected]') 
    User.objects.create(email='[email protected]') 
    alice = User.objects.create(email='[email protected]') 
    User.objects.create(email='[email protected]') 

    Report.objects.create(user=anne) 
    Report.objects.create(user=alice) 
    Report.objects.create(user=alice) 

    logging.info('users without reports') 
    logging.info(User.objects.filter(report__isnull=True, email__startswith='a')) 

    logging.info('users with reports (allows duplicates)') 
    logging.info(User.objects.filter(report__isnull=False, email__startswith='a')) 

    logging.info('users with reports (no duplicates)') 
    logging.info(User.objects.exclude(report__isnull=True).filter(email__startswith='a')) 

    logging.info('users with reports (no duplicates, simpler SQL)') 
    report_user_ids = Report.objects.values('user_id') 
    logging.info(User.objects.filter(id__in=report_user_ids, email__startswith='a')) 

    logging.info('Done.') 


def setup(): 
    db_file = NAME + '.db' 
    with open(db_file, 'w'): 
     pass # wipe the database 
    settings.configure(
     DEBUG=True, 
     DATABASES={ 
      DEFAULT_DB_ALIAS: { 
       'ENGINE': 'django.db.backends.sqlite3', 
       'NAME': db_file}}, 
     LOGGING={'version': 1, 
       'disable_existing_loggers': False, 
       'formatters': { 
        'debug': { 
         'format': '%(asctime)s[%(levelname)s]' 
            '%(name)s.%(funcName)s(): %(message)s', 
         'datefmt': '%Y-%m-%d %H:%M:%S'}}, 
       'handlers': { 
        'console': { 
         'level': 'DEBUG', 
         'class': 'logging.StreamHandler', 
         'formatter': 'debug'}}, 
       'root': { 
        'handlers': ['console'], 
        'level': 'INFO'}, 
       'loggers': { 
        "django.db": {"level": "DEBUG"}}}) 
    app_config = AppConfig(NAME, sys.modules['__main__']) 
    apps.populate([app_config]) 
    django.setup() 
    original_new_func = ModelBase.__new__ 

    # noinspection PyDecorator 
    @staticmethod 
    def patched_new(cls, name, bases, attrs): 
     if 'Meta' not in attrs: 
      class Meta: 
       app_label = NAME 
      attrs['Meta'] = Meta 
     return original_new_func(cls, name, bases, attrs) 
    ModelBase.__new__ = patched_new 


def syncdb(model): 
    """ Standard syncdb expects models to be in reliable locations. 

    Based on https://github.com/django/django/blob/1.9.3 
    /django/core/management/commands/migrate.py#L285 
    """ 
    connection = connections[DEFAULT_DB_ALIAS] 
    with connection.schema_editor() as editor: 
     editor.create_model(model) 

main() 

आप एक अजगर फ़ाइल में है कि डाल दिया और इसे चलाने के हैं, तो आप कुछ इस तरह देखना चाहिए:

यहाँ अपने प्रश्न के सवालों को चलाने के लिए कुछ तरीके हैं आप देख सकते हैं कि अंतिम क्वेरी सभी आंतरिक जोड़ों का उपयोग करती है।

+0

ऐसा लगता है कि आपका कोड स्निपेट पूरा नहीं हो सकता है। अंतिम विवरण 'Report.objects.create (उपयोगकर्ता = anne) 'है जबकि आपके आउटपुट और विचार इंगित करते हैं कि आप अधिक कोड दिखाना चाहते हैं। मैं इसे देखने के लिए उत्सुक हूँ! –

+0

सुनिश्चित नहीं है कि आप किस बारे में बात कर रहे हैं, @ क्रिस्टियन क्युलस्की। मेरा ब्राउज़र उस कोड के बारे में दिखाता है, लेकिन फिर बाकी को देखने के लिए स्क्रॉल बार है। यदि आपको ब्राउज़र समस्या हो रही है, तो मार्कडाउन स्रोत देखने के लिए उत्तर को संपादित करने का प्रयास करें। –

+0

आप सही हैं। मैं क्षमाप्रार्थी हूं। मैक पर क्रोम चालाक है और एम्बेडेड कोड बॉक्स में स्क्रॉल बार छुपाता है। मुझे नहीं पता था कि यह स्क्रोल करने योग्य है। –

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