Left join z warunkiem

Mam mniej wiecej taki kawalek kodu, ktory wybiera wydarzenia wraz z typami uzytkownika:

Event.joins(“left join bets on bets.event_id = events.id and bets.user_id = #{current_user.id}”)

Moj problem polega na tym, ze pozniej w widoku odwolujac sie do event.bets idzie ponownie zapytanie do bazy danych, oczywiscie nie uwzgledniajac tez uzytkownika.
Jak tego uniknac i rozwiazac to w ladny sposob?
Dzieki za wszelka pomoc!

Do załadowania relacji do obiektu służy includes (które technicznie zrobi dwa zapytania), albo dostępne w rails 4 preload / eager_load. joins służy tylko i wyłącznie do tworzenia warunków (w select nie zostanie uwzględnione).

Do poczytania o różnicach:


Includes decyduje czy zostanie zrobiony left join czy wywolane drugie zapytanie, jesli chcemy jasno okreslic zachowanie wtedy uzywamy preload lub eager_load.

Zalozmy jednak, ze uzyje polecanego includes. W tym przypadku zbudowane zostanie zapytanie z left joinem, ktore pobierze WSZYSTKIE bets dla eventu.
Ja chce ta asocjacje ograniczyc do konkretnego uzytkownika, a zatem dokladajac warunek where(“bets.user_id = ?”, current_user) spowoduje, ze tylko events, ktore maja juz swoj typ zostana pobrane.
to zapytanie bedzie wygladac tak:

select * from events left join bets on bets.event_id = events.id where bets.user_id = ?

mi bardziej chodzi o cos takiego:

select * from events left join bets on bets.event_id = events.id and bets.user_id = ?

mam nadzieje, ze teraz jest to bardziej zrozumiale :slight_smile:

Nie. Używając includes możesz zrobić

Event.includes(:bets).where("bets.user_id = #{current_user.id}").references(:bets)

No moge, ale czy zrobie tak jak napisales:

Event.includes(:bets).where(“bets.user_id = #{current_user.id}”).references(:bets)

czy w ten sposob:

Event.includes(:bets).where(bets: {user_id: current_user})

to i tak bede mial takie samo zapytanie, tzn. bets.user_id znajdzie sie w WHERE, co ogranicza mi Events, do tych ktore uzytkownik juz wytypowal. porownaj prosze moje dwa zapytania z posta wyzej i zobacz czym sie roznia.

Event.includes(:bets).where("bets.user_id = #{current_user.id} OR bets.event_id == event.id").references(:bets)

Może tak?

Normalny or będzie dopiero w Rails 5, albo z wykorzystaniem różnych gemów które nie wchodzą w skład railsów już teraz.

Tak na pierwszy rzut oka wydaje mi sie, ze problem bedzie jesli current_user nie typowal eventu, a typowali inni (w bets beda typy innych uzytkownikow) lub ani current user ani nikt inny nie typowal (wtedy event w ogole sie nie pokaze) :wink:

Ale dlaczego? Przecież jest or, a to co po or w ogóle się nie odnosi do żadnego user. Nie mam struktury twojej bazy, więc najprościej będzie jak sam sobie to przetestujesz.

Twoj kod zbuduje takie zapytanie:

select * from events left join bets on bets.event_id = events.id WHERE bets.user_id = ? OR bets.event_id = events.id;

i taki przyklad, co sie stanie jesli w bets nie ma zadnych wpisow, a masz kilka eventow?
zwrocne zostana tylko te ktore juz maja jakis wpis w tabeli bets

Czyli ty chcesz wszystkie events oraz wszystkie bets które mają bets.event_id = events.id and bets.user_id = ?

To tak się chyba w railsach nie da tego zrobić, nie wiem czy jest jakiś ORM który na to pozwala :slight_smile: Bo ORM’y są sprytne ale chyba nie na tyle by każde dowolne pytanie SQL przerobić na obiekty. Sam przerabiałem inny problem ale też o optymalizację ilości pytań chodziło i albo robisz jedno zapytanie którego wynik zajmie dużo pamięci i podczas iteracji po rekordach sprawdzasz warunki, albo dostajesz dokładnie te obiekty które chcesz bez zbędnego narzutu pamięci, tylko wtedy więcej razy trzeba odpytać bazę danych.

dokladnie, chce wszystkie eventy wraz z typami uzytkownika.
cos takiego uzyskalbym takim zapytaniem:

select * from events left join bets on bets.event_id = events.id AND bets.user_id = current_user.id;

probowalem to zrobic w taki sposob:

Event.joins(“left join bets on bets.event_id = events.id and bets.user_id = #{current_user.id}”)

jak rowniez wykorzystujac arel, no ale niestety pozniej odwolanie sie do event.bets powoduje wywolanie zapytania:

select * from bets where event_id = ?

czyli nie dosc, ze mam N+1 zapytan, to jeszcze nie uwazglednia tego usera.

Może czas przemyśleć samą logikę. Zastanowić się po co Ci Events.all oraz Bets.where(bets.user_id = current_user.id) w jednym zapytaniu. Najprościej chyba wyjść od tego jakie obiekty potrzebujesz do pracy. Różnica pomiędzy jednym pytaniem a dwoma, nie jest jeszcze taka straszna.