Можно ли запросить запись с несколькими связанными записями, которые соответствуют определенным критериям?
У меня есть две таблицы, Show
, Character
. Каждое шоу имеет несколько символов.
class Show < ActiveRecord::Base
has_many :characters
class Character < ActiveRecord::Base
belongs_to :show
Что я хочу сделать, так это вернуть результаты Show, связанные с несколькими символами, которые соответствуют определенным критериям.
Например, я хочу иметь возможность возвращать список шоу, которые имеют как символы, так и Бэтмен и Робин. Не Бэтмен ИЛИ Робин, Бэтмен и Робин.
Таким образом, запрос должен выглядеть примерно как
Show.includes(:characters).where(characters: {'name = ? AND name = ?', "Batman", "Robin"})
Но это возвращает ошибку. Есть ли синтаксис для этого, который будет работать?
UPDATE
Запрос
Show.includes(:characters).where('characters.name = ? AND characters.name = ?', "Batman", "Robin")
возвращает значение 0, даже если есть определенные шоу, связанные как с Batman, так и с Robin.
Ответы
Ответ 1
С помощью простого SQL одно решение:
select s. *
from shows as s
join characters as c1 on (s.id=c1.show_id)
join characters as c2 on (s.id=c2.show_id)
where c1.name='Batman'
and c2.name='Robin';
Используя Arel, я бы перевел как:
Show.joins('join characters as c1 on shows.id=c1.show_id').joins('join
characters as c2 on shows.id=c2.show_id').where('c1.name = "Batman"').where(
'c2.name="Robin"')
Ответ 2
Итак, вам нужно немного приукрашивать SQL; особенно если вы хотите, чтобы он был исполнен и обрабатывал различные типы матчи.
select count(distinct(characters.name)) as matching_characters_count, shows.* from shows
inner join characters on characters.show_id = shows.id and (characters.name = 'Batman' or characters.name = 'Robin')
group by shows.id
having matching_characters_count = 2
Перевести на ActiveRecord
Show.select('count(distinct(characters.name)) as matching_characters_count, shows.*').
joins('inner join characters on characters.show_id = shows.id and (characters.name = "Batman" or characters.name = "Robin")').
group('shows.id').
having('matching_characters_count = 2')
Тогда вы, вероятно, сделаете проход с интерполяцией, а затем AREL его; поэтому вы не будете строить строковые запросы.
Ответ 3
Простое решение SQL с использованием агрегатных функций. Операторы SQL возвращают значения идентификатора записей, которые вы ищете.
SELECT c.show_id
FROM characters c
WHERE c.name IN ('Batman','Robin')
GROUP BY c.show_id
HAVING COUNT(DISTINCT c.name) = 2
Вы можете поместить этот оператор в вызов select_values()
, а затем захватить shows
со значениями возвращаемого массива.
Ответ 4
Я думаю, что это должно работать:
super_heroes = ['Batman', 'Robin']
Show.where(
id: Character.select(:show_id).where(name: super_heroes).group(:show_id)
.having("count(distinct characters.name) = #{super_heroes.size}")
)
Я просто пишу запрос @sschmeck в Rails-способе как подзапрос и добавляю super_heroes
var, чтобы показать, как его можно масштабировать.
Ответ 5
Сократить количество записей в запросе как можно скорее, это основная идея, чтобы получить лучший запрос в производительности.
Show.where("id IN (( SELECT show_id FROM characters WHERE name = 'Batman') INTERSECT (SELECT show_id FROM characters WHERE name = 'Robin'))")
В вышеприведенном запросе 1-й подзапрос получает 'show_id' для 'Batman', следующий получает 'show_id' для 'Robin'. На мой взгляд, всего несколько символов соответствуют условию после выполнения "INTERSECT". Btw, вы можете использовать explain в 'rails db' для выбора лучшего решения.
Ответ 6
Я думаю, вы ищете это:
Show.includes(:characters).where(characters: {name: ["Batman", "Robin"]})
или
Show.includes(:characters).where('characters.name IN ?', ["Batman", "Robin"]).references(:characters)
references
требуется, потому что мы используем строку в методе where
.
Ответ 7
Итак, прежде всего, почему ваш подход ошибочен?
Поэтому в таблице "characters_shows" вы можете найти записи, которые выглядят следующим образом:
show_id name character_id character_name
1 first show 1 batman
2 second 1 batman
1 first show 2 robin
1 first show 3 superman
Как вы можете видеть, никогда не будет случая, когда имя символа - битман и robin в той же строке
Другим подходом будет что-то вроде этого
names = ["Batman", "Robin"]
Show.joins(:characters).where(characters: {name: names}).group("shows.id").having("COUNT(*) = #{names.length}")