Mam klasę Post która ma has_many comments z polem title typu string
Zalozmy ze mamy w bazie jedna instancję klasy Post oraz dwie instancje klasy Comment które należą do Post i mają title odpowiednio “Foo” i “Bar”
Chcę znaleźć w bazie wszystkie instancje Post które mają komentarze zawierające tytuły “Foo” i “Bar”
Próbowałem:
Post.joins(:comments).where(“comments.title = ?”, “Foo”).where(“comments.title = ?”, “Bar”) # => []
mogę zrobić tak
w post.rb
has_many :comments_foo, conditions: {title: “foo”}, class_name: “Comment”
a potem:
Problem w tym, że w przyrodzie mam relację i parametry i chce ograniczyć scope w pętli
filters.each do |title|
scope = scope.where(“comments.title = ?”, title)
end
Czy ktoś ma pomysł na rozwiązanie problemu?
“Foo” i “Bar”, czy “Foo” lub “Bar”? Łączenie where w łańcuch w SQL-u tworzy klauzule połączone operatorem AND. Druga sprawa, to czy chcesz wyszukiwać dokładnego dopasowania tytułu, czy wystarczy jego część? Jeśli część, to trzeba użyć operatora LIKE (lub ILIKE). Do tworzenia zaawansowanych zapytań polecam gema Squeel: https://github.com/activerecord-hackery/squeel
Dzięki za odpowiedź.
Eleganckie rozwiązanie dla stałej ilości parametrów(title). Gorzej będzie się generować kiedy nie znamy z góry ile aliasów będziemy potrzebować. Przeglądam dokumentację Squeel’a, może tam będzie taka funkcjonalność
To prawda, zapytanie sql generowane przez mój kod wygląda następująco:
SELECT posts.* FROM posts WHERE posts.id IN (SELECT post_id FROM posts INNER JOIN comments ON comments.post_id = posts.id WHERE comments.title = ‘foo’) AND posts.id IN (SELECT post_id FROM posts INNER JOIN comments ON comments.post_id = posts.id WHERE posts.id IN (SELECT post_id FROM posts INNER JOIN comments ON comments.post_id = posts.id WHERE comments.title = ‘foo’) AND comments.title = ‘bar’)
Podczas gdy zapytanie wygenerowane przez zaproponowany kod:
SELECT posts.* FROM posts WHERE posts.id IN (SELECT post_id FROM posts INNER JOIN comments ON comments.post_id = posts.id WHERE comments.title = ‘foo’) AND posts.id IN (SELECT post_id FROM posts INNER JOIN comments ON comments.post_id = posts.id WHERE comments.title = ‘bar’)
Niestety nie mogę przekazać tablicy tytułów, ponieważ use-case dla metody jest z chain’owaniem z góry nie wiadomej ilości filtrów.
PS: Różnica czasowa dla bazy mysql przy 10k rekordów i 10k zapytań wynosi 4.1156 vs 4.2900
mała różnica w czasie wynika z faktu że zapytania są podobne i relatywnie łatwe, Query Analyzer potrafi dobrać optymalny plan w tym przypadku, ale miałem na myśli coś innego.
Zobacz wydajność w ten sposób:
def self.with_comment_titles(*titles)
titles.inject(where(nil)) do |s,t|
s.where("EXISTS (SELECT 1 FROM comments WHERE title = ? LIMIT 1)", t)
end
end
to powinno znacząco poprawić wydajność (i lepiej już raczej nie będzie).
Ten scope możesz dowolnie chainować tak jak własny przykład, tj. nic nie stoi na przeszkodzie żeby zrobić