2014-10-31 6 views
3

В моей системе я имею следующую структуру:Найти записи, которые assoicated записи не принадлежат к определенной записи

class Worker 
    has_many :worker_memberships 
end 

class WorkerMembership 
    belongs_to :worker 
    belongs_to :event 
end 

class Event 
    has_many :worker_memberships 
end 

Представьте себе у меня есть определенный @event. Как я могу найти все workers, которые имеют NO worker_memberships, принадлежащих этому @event?

ответ

2

Это в значительной степени синтез обоих других ответов.

Во-первых: придерживаться has_many through как @ TheChamp предлагает. Возможно, вы уже используете его, просто забыли написать его, иначе он просто не сработает. Ну, вас предупредили.

Как правило, я стараюсь избегать необработанных SQL-запросов в моих запросах. Подсказка о select, представленная выше, создает рабочее решение, но делает некоторые ненужные вещи, такие как join, когда нет никакой практической необходимости. Итак, давайте избежим общения. Не в этот раз.

вот причина, почему я предпочитаю has_many through к has_and_belongs_to_many во многих ко многим ассоциаций: мы можем запросить сама модель присоединиться без необработанного SQL:

WorkerMembership.select(:worker_id).where(event: @event) 

Это не результат, но она заставляет нас список worker_id s мы не хотим.Тогда мы просто обернуть этот вопрос в «дать мне все, но эти ребята»:

Worker.where.not(id: <...>) 

Таким образом, окончательный запрос:

Worker.where.not(id: WorkerMembership.select(:worker_id).where(event: @event)) 

И выводит один запрос (на @event с id равным 1):

SELECT `workers`.* FROM `workers` WHERE (`workers`.`id` NOT IN (SELECT `worker_memberships`.`worker_id` FROM `worker_memberships` WHERE `worker_memberships`.`event_id` = 1)) 

Я также отдать должное @apneadiving за его решения и подсказки о mysql2 «s explain. SQLite's explain ужасен! Мое решение, если я правильно прочитал результат explain, так же эффективен, как и @ apneadiving.

@ TheChamp также обеспечил затраты на выполнение всех запросов ответов. Ознакомьтесь с комментариями для сравнения.

+0

интересно! :) – apneadiving

+0

не могли бы вы разделять расходы обоих? просто любопытство – apneadiving

+0

@apneadiving уверенная вещь: http://pastebin.com/BupHkLQf (обязательно переверните перенос текста, таблицы 'explain' длинны) –

1

Попробуйте это:

Worker.where(WorkerMembership.where("workers.id = worker_memberships.worker_id").where("worker_memberships.event_i = ?", @event.id).exists.not) 

Или короче и многоразовые:

class WorkerMembership 
    belongs_to :worker 
    belongs_to :event 

    scope :event, ->(event){ where(event_id: event.id) } 
end 

Worker.where(WorkerMembership.where("workers.id = worker_memberships.worker_id").event(@event.id).exists.not) 

(я предположил, имена таблиц и столбцов из конвенций)

+0

Я предлагаю вам попробовать все решения, но добавьте их с помощью '.explain'. Таким образом вы увидите стоимость db. Затем я настоятельно рекомендую вам выбрать самый низкий – apneadiving

+0

спасибо за указание метода 'explain'. Мне было бы интересно, что вы думаете о результатах. http://chat.stackoverflow.com/rooms/64000/explain-activerecord-db-queries –

+0

На самом деле, на почти пустой таблице этот запрос жалуется на отсутствие «первого» в «nil» и не работает с длинной stacktrace. –

1

Поскольку вы хотите создать от многих до многих отношений между Worker и Event, я бы предложил вам использовать through association.

Ваши результирующие модели были бы.

class Worker 
    has_many :worker_memberships 
    has_many :events, :through => :worker_memberships 
end 

class WorkerMembership 
    belongs_to :worker 
    belongs_to :event 
end 

class Event 
    has_many :worker_memberships 
    has_many :workers, :through => :worker_memberships 
end 

Теперь вы можете просто позвонить @event.workers, чтобы получить все работники, связанные с событием.

Чтобы найти все рабочие, которые не делают принадлежат @event вы можете использовать:

# get all the id's of workers associated to the event 
@worker_ids = @event.workers.select(:id) 

# get all workers except the ones belonging to the event 
Worker.where.not(:id => @worker_ids) 

Один лайнер

Worker.where.not(:id => @event.workers.select(:id)) 
+0

Да, но не нужно делать запросы 2 дБ, хотя тесты не так понятны. – apneadiving

+0

@apneadiving, заменяющий 'pluck'' 'select, будет делать это, используя подзапросы. Подобно 'Worker.where.not (id: @ event.workers.select (: id))' –

+0

@ D-side, cool :) Спасибо, это намного лучше. –

Смежные вопросы