2016-03-04 33 views
1

我想寫一個Django查詢來查找一組父記錄與某些種類的子記錄。問題是帶有兩個匹配搜索子項的父記錄將包含兩次結果。Django查詢子記錄沒有得到重複的行

即使有多個匹配的孩子,我如何才能讓每個父母都有一次?

我已經在下面包含了一個簡單的例子來演示這個問題。 Blog是父母,Entry是孩子。當我在標題中搜索包含「Hello」的條目的博客時,我會收到Jimmy博客的兩份副本。

下面是我創建的記錄和查詢我想:

b = Blog(name="Jimmy's Jottings") 
    b.save() 
    Entry(blog=b, headline='Hello, World!').save() 
    Entry(blog=b, headline='Hello Kitty').save() 

    blog_count = Blog.objects.filter(entries__headline__contains='Hello').count() 
    assert blog_count == 1, blog_count 

你可以看到,只有一個博客,但斷言失敗的兩個計數。

下面是完整的例子:

# Tested with Django 1.9.2 
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 Blog(models.Model): 
     name = models.CharField(max_length=100) 
     tagline = models.TextField() 

     def __str__(self):    # __unicode__ on Python 2 
      return self.name 

    class Entry(models.Model): 
     blog = models.ForeignKey(Blog, related_name='entries') 
     headline = models.CharField(max_length=255) 
     body_text = models.TextField() 

     def __str__(self):    # __unicode__ on Python 2 
      return self.headline 

    syncdb(Blog) 
    syncdb(Entry) 

    b = Blog(name="Jimmy's Jottings") 
    b.save() 
    Entry(blog=b, headline='Hello, World!').save() 
    Entry(blog=b, headline='Hello Kitty').save() 

    blog_count = Blog.objects.filter(entries__headline__contains='Hello').count() 
    assert blog_count == 1, blog_count 

    print('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': 'WARN'}, 
       'loggers': { 
        "django.db": {"level": "WARN"}}}) 
    app_config = AppConfig(NAME, sys.modules['__main__']) 
    apps.populate([app_config]) 
    django.setup() 
    original_new_func = ModelBase.__new__ 

    @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

訣竅是使用查找匹配的孩子的博客ID的子查詢,然後尋找有在該子查詢的ID的所有博客。然後子查詢可以有重複而不會導致主要查詢中的重複。

這裏的固定查詢:

blog_ids = Entry.objects.filter(headline__contains='Hello').values('blog_id') 
blog_count = Blog.objects.filter(id__in=blog_ids).count() 
assert blog_count == 1, blog_count 

這裏的SQL查詢生成:

SELECT COUNT(*) AS "__count" 
FROM "udjango_blog" 
WHERE "udjango_blog"."id" IN 
     (
     SELECT U0."blog_id" 
     FROM "udjango_entry" U0 
     WHERE U0."headline" LIKE '%Hello%' ESCAPE '\' 
     ) 
0

雖然我相信唐Kirby的響應的作品,我覺得一個更好的解決辦法是添加.distinct( )在查詢集的末尾。這簡單地消除了查詢結果中的任何重複行。 SQL的等價物是在給定的查詢中使用SELECT DISTINCT。