2014-08-05 2 views
3

Мои запросы приложения hodgepodge идут дальше моего знания того, как работает ORM от Django.Сложный запрос Django

Вот моя текущая (неправильная) попытка:

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

Мои модели ниже, если вы хотите, чтобы прочитать их.

Начальный filter() отлично работает. Моя проблема - exclude(), прикованный к концу.

exclude(), как представляется, за исключением Mentor с с:

  • ассоциированным Shift который удовлетворяет условиям, указанным (shift__session = session),
  • И (возможно другое), связанного Shift, что соответствует второй набор критерии shift__jobs__time = job.time.

Я только хочу, чтобы отфильтровать Mentor S, которые имеют 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 
    ) 

ответ

6

Во-первых, давайте объясним, что здесь происходит. Когда вы пишете:

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

Это означает следующий запрос:

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

В boolean algebra, ¬ (A ∧ B) (не [A и B]) на самом деле (¬A ∨ ¬B) (не [A] ИЛИ не [B]). Таким образом, что вы имели в виду в запросе был:

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

Имейте это в виду, когда вы пишете exclude фильтр, который имеет несколько параметров.

Таким образом, если в запросе, вы хотите, чтобы исключить элементы, которые проверяют ОБА критерий (в пересечение критерия, если вы будете), самый простой и лучший способ сделать это состоит в цепи исключает фильтры:

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

операция QuerySet является ленивого, то есть примерно что ваши exclude фильтры будут оцениваться одновременно. Таким образом, два фильтра не будут «дважды работать».

Фильтр переведет:

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

Какой именно то, что вы хотите!

Написание запросов может быть иногда трудно, но помните:

  • исключить с несколькими аргументами перевести: нет (A) или нет (B) или нет (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) 

But A ∧ ¬A (А И НЕ [A]) есть пустое множество. Поэтому проблема связана с семантикой вашего запроса.

С вашего постом я прочитал:

за исключение [...] ассоциированный сдвиг, который соответствует условиям, указанным (shift__session = сессия) и (возможно, другой), ассоциированного сдвиг, который соответствует второму набору критериев

filter вы использовали уже гарантирует, что shift__session = session, так что вы не должны поместить его внутрь exclude фильтра.

Из того, что я думаю (но скажите мне, если я ошибаюсь), что вы хотите:

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

Я прочитал Ответ на этот вопрос несколько раз, насколько я могу сказать, что я делаю смысл этого, но я с трудом связав его с фактическим запросом, который мне потребуется. Опять же, может быть, я только начал использовать Django, и мне сложно сопоставить, что 'exclude()' (или 'filter()', если на то пошло) делает для реального SQL. Буду признателен за измененный запрос, если возможно :) Спасибо за вашу помощь. –

+1

Обновлен с полным ответом! –

+0

Ха-ха, теперь я чувствую себя глупо. Огромное спасибо. Я сделаю это. –

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 
) 

Не беспокойтесь о shifts или excluded_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' 
) 

Как вы могли заметить, что есть немного двойной работы, выполняемой БД здесь в этих двух подзапросах, но я не думаю, что есть способ избежать этого.

+1

Куриный обед победителя! Работает отлично. Огромное спасибо. Я разочарован тем, насколько это просто, на самом деле: P –

+0

Нет проблем! Рад, что это сработало. :) –

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>) 
Смежные вопросы