复杂的Django查询

时间:2014-08-05 10:22:04

标签: python django

我的大杂烩应用程序的查询超出了我对Django的ORM如何工作的了解。

这是我当前(不正确)的尝试:

queryset = Mentor.objects.filter(
    shift__session = session,
    jobs_desired = job
).exclude(
    shift__session = session,
    shift__jobs__time = job.time
)

如果您想阅读它们,我的模型如下。

最初的filter()工作正常。我的问题是exclude()链接到最后。

exclude()似乎将Mentor排除在:

  • 符合指定条件的关联Shiftshift__session = session),
  • 和符合第二组条件Shift的关联shift__jobs__time = job.time的一个(可能不同的)。

我只想过滤掉与Mentor关联的Shift符合条件的class DojoSession(models.Model): term = models.ForeignKey(DojoTerm, help_text = "Dojo Term") date = models.DateField( blank = False, help_text = "Date during which the session will take place." ) start = models.TimeField( blank = False, help_text = "Start Time" ) end = models.TimeField( blank = False, help_text = "End Time" ) rooms = models.ManyToManyField( Room, blank = True, help_text = "The rooms in which this session will be running." ) class Shift(models.Model): mentor = models.ForeignKey( 'mentors.Mentor', blank = False, help_text = 'The mentor unergoing this shift.' ) session = models.ForeignKey( DojoSession, blank = False, help_text = 'The session during which this shift takes place.', ) role = models.ForeignKey( 'mentors.Role', blank = False, help_text = "The role that the mentor will be undertaking during this shift.", ) room = models.ForeignKey( Room, blank = True, null = True, help_text = "The room, if any, that the mentor will be undertaking the shift in." ) jobs = models.ManyToManyField( 'jobs.Job', blank = True, null = True, ) start = models.TimeField( blank = False, help_text = "Start Time" ) end = models.TimeField( blank = False, help_text = "End Time" ) class Job(models.Model): BEFORE = 'B' DURING = 'D' AFTER = 'A' TIME_CHOICES = ( (BEFORE, 'Before session'), (DURING, 'During session'), (AFTER, 'After session'), ) name = models.CharField( max_length = 50, help_text = "The job's name." ) description = models.TextField( max_length = 1024, help_text = "A description of the job." ) location = models.CharField( max_length = 50, help_text = "The job's location." ) time = models.CharField( max_length = 1, choices = TIME_CHOICES, help_text = "The time during a session at which this job can be carried out." ) class Mentor(models.Model): MALE_SMALL = "MS" MALE_MEDIUM = "MM" MALE_LARGE = "ML" MALE_EXTRA_LARGE = "MXL" FEMALE_EXTRA_SMALL = "FXS" FEMALE_SMALL = "FS" FEMALE_MEDIUM = "FM" FEMALE_LARGE = "FL" FEMALE_EXTRA_LARGE = "FXL" SHIRT_SIZE_CHOICES = ( ('Male', ( (MALE_SMALL, "Male S"), (MALE_MEDIUM, "Male M"), (MALE_LARGE, "Male L"), (MALE_EXTRA_LARGE, "Male XL") )), ('Female', ( (FEMALE_EXTRA_SMALL, "Female XS"), (FEMALE_SMALL, "Female S"), (FEMALE_MEDIUM, "Female M"), (FEMALE_LARGE, "Female L"), (FEMALE_EXTRA_LARGE, "Female XL") )) ) ASSOCIATE = 'A' STAFF = 'S' NEITHER = 'N' CURTIN_STATUS_CHOICES = ( (ASSOCIATE, 'Associate'), (STAFF, 'Staff'), (NEITHER, 'Neither/not sure') ) NOTHING = 'NO' SOMETHING = 'SO' EVERYTHING = 'EV' KNOWLEDGE_CHOICES = ( (NOTHING, 'I know nothing but am keen to learn!'), (SOMETHING, 'I know some basics'), (EVERYTHING, 'I know a great deal') ) uni = models.CharField( max_length = 50, null = True, blank = True, help_text = "University of study" ) uni_study = models.CharField( max_length = 256, null = True, blank = True, help_text = "If you're attending university, what are you studying?" ) work = models.CharField( max_length = 256, null = True, blank = True, help_text = "If you workwhat do you do?" ) shirt_size = models.CharField( max_length = 3, blank = True, choices = SHIRT_SIZE_CHOICES, help_text = "T-shirt size (for uniform)" ) needs_shirt = models.BooleanField( default = True, help_text = "Does the mentor need to have a shirt provisioned for them?" ) wwcc = models.CharField( max_length = 10, verbose_name = "WWCC card number", blank = True, null = True, help_text = "WWCC card number (if WWCC card holder)" ) wwcc_receipt = models.CharField( max_length = 15, verbose_name = "WWCC receipt number", blank = True, null = True, help_text = "WWCC receipt number (if WWCC is processing)" ) curtin_status = models.CharField( max_length = 1, verbose_name = "Current Curtin HR status", choices = CURTIN_STATUS_CHOICES, default = NEITHER, blank = False, help_text = "When possible, we recommend that all CoderDojo mentors are either Curtin University Associates or Staff members." ) curtin_id = models.CharField( max_length = 10, verbose_name = "Curtin Staff/Associate ID", blank = True, null = True, help_text = "Your Curtin Staff/Associate ID (if applicable)" ) coding_experience = models.CharField( max_length = 2, blank = False, default = NOTHING, choices = KNOWLEDGE_CHOICES, help_text = "How much programming experience do you have?" ) children_experience = models.CharField( max_length = 2, blank = False, default = NOTHING, choices = KNOWLEDGE_CHOICES, help_text = "How much experience do you have with children?" ) roles_desired = models.ManyToManyField(Role) jobs_desired = models.ManyToManyField('jobs.Job') shift_availabilities = models.ManyToManyField( 'planner.DojoSession', help_text = "When are you available?" ) user = models.OneToOneField(settings.AUTH_USER_MODEL, unique = True )

有什么想法吗?

{{1}}

3 个答案:

答案 0 :(得分:6)

首先,让我们解释一下这里发生了什么。当你写:

set.exclude( A=arg1, B=arg2 )

这转换为以下查询:

SELECT [...] WHERE NOT (A=arg1 AND B=arg2)

boolean algebra中,¬(A∧B)(不是[A和B])实际上是(¬A∨¬B)(不是[A] OR not not [B])。 因此,您在查询中的意思是:

SELECT [...] WHERE NOT(A=arg1) OR NOT(B=arg2)

在编写具有多个参数的exclude过滤器时请记住这一点。

因此,如果在您的查询中,您想要排除检查BOTH标准的元素(如果您愿意,那么交叉标准),最简单和最好的方法是链排除过滤器

set.exclude(A=arg1).exclude(B=arg2)

Queryset操作 lazy ,大致意味着您的exclude过滤器将同时进行评估。因此,两个过滤器不会“工作两倍”。

过滤器将转换为:

SELECT [...] WHERE NOT(A=arg1) AND NOT(B=arg2)

这正是你想要的!

编写查询有时很难,但请记住:

  • 排除多个args转换为:not(A)OR not(B)OR not(C)...
  • 如果您需要排除多个因素(AND)的项目,只需多次调用exclude过滤器。

现在,这是您的新查询:

queryset = Mentor.objects.filter(
    shift__session = session,
    jobs_desired = job
).exclude(
    shift__session = session
).exclude(
    shift__jobs__time = job.time
)

如果我们“压扁”您要求的内容,您需要:

  • 属于会话的记录:filter(shift__session = session)
  • 但是...... 属于该会话.exclude(shift__session = session)

生成的SQL将是:

SELECT [...] WHERE shift__session = session AND [...] AND NOT(shift__session = session)

但是A∧¬A( A AND NOT [A] )是空集。所以问题在于查询的语义

从你的帖子我读到:

  

排除符合指定条件的相关Shift(shift__session = session)和符合第二组标准的(可能不同的)相关Shift

您使用的filter已经保证shift__session = session,因此您不应将其放在exclude过滤器内。

根据我的猜测(但告诉我,如果我错了),你想要的是:

queryset = Mentor.objects.filter(
    shift__session = session,
    jobs_desired = job
).exclude(
    shift__jobs__time = job.time
)

答案 1 :(得分:1)

我相信你应该能够使用这样的东西来获得你正在寻找的东西。

shifts = Shift.objects.filter(session=session)
excluded_shifts = shifts.filter(jobs__time=job.time)
queryset = Mentor.objects.filter(
    jobs_desired=job
    shift__in=shifts
).exclude(
    shift__in=excluded_shifts
)

在运行查询之前,不要担心shiftsexcluded_shifts被执行;它们很懒惰,只会作为子查询包含在您正在创建的最终查询集中。

在伪sql中,我认为上面的内容将对应于以下内容(这里只是过去的经验):

SELECT *
FROM mentor
LEFT JOIN jobs_desired ON (mentor.id=jobs_desired.mentor_id)
WHERE jobs_desired.id=1
AND shift_id IN (
    SELECT id
    FROM shifts
    WHERE session_id=2
)
AND shift_id NOT IN (
    SELECT id
    FROM shifts
    LEFT JOIN jobs ON (shifts.id=jobs.session_id)
    WHERE session_id=2
    AND jobs.time='B'
)

正如您可能已经注意到的那样,在这两个子查询中,数据库执行了一些双重工作,但我认为没有办法避免这种情况。

答案 2 :(得分:0)

如何使用Q functions

from django.db.models import Q
queryset = Mentor.objects.exclude(
        Q(shift__jobs__time=job.time) & Q(shift__session=session)
    ).filter(jobs_desired=job, shift__session=session)
print str(queryset.query)

产生SQL的结果如下:

SELECT "your_project_mentor"."id", "your_project_mentor"."uni", "your_project_mentor"."uni_study", "your_project_mentor"."work", "your_project_mentor"."shirt_size", "your_project_mentor"."needs_shirt", "your_project_mentor"."wwcc", "your_project_mentor"."wwcc_receipt", "your_project_mentor"."curtin_status", "your_project_mentor"."curtin_id", "your_project_mentor"."coding_experience", "your_project_mentor"."children_experience", "your_project_mentor"."user_id" 
  FROM "your_project_mentor" 
  INNER JOIN "your_project_mentor_jobs_desired" 
    ON ( "your_project_mentor"."id" = "your_project_mentor_jobs_desired"."mentor_id" ) 
  INNER JOIN "your_project_shift" 
    ON ( "your_project_mentor"."id" = "your_project_shift"."mentor_id" ) 
  WHERE 
    (NOT 
      ("your_project_mentor"."id" IN 
        (SELECT U1."mentor_id" 
          FROM "your_project_shift" U1 
          INNER JOIN "your_project_shift_jobs" U2 
            ON ( U1."id" = U2."shift_id" ) 
          INNER JOIN "your_project_job" U3 
            ON ( U2."job_id" = U3."id" ) 
          WHERE U3."time" = <job_time> ) 
      AND "your_project_mentor"."id" IN 
        (SELECT U1."mentor_id" 
          FROM "your_project_shift" U1 
          WHERE U1."session_id" = <session_id> )
      ) 
    AND "your_project_mentor_jobs_desired"."job_id" = <job_id>  
    AND "your_project_shift"."session_id" = <session_id> )