Interogări în rails, Partea 2

În acest al doilea articol, ne vom arunca cu puțin mai mult în interogările Active Record în Rails. În cazul în care încă mai sunteți la SQL, voi adăuga exemple care sunt destul de simple încât să puteți eticheta împreună și să luăm puțin sintaxa pe măsură ce mergem. 

Acestea fiind spuse, ar fi de ajutor dacă veți trece printr-un tutorial SQL rapid înainte de a vă întoarce să continuați să citiți. În caz contrar, luați-vă timpul să înțelegeți interogările SQL pe care le-am folosit și sper că până la sfârșitul acestei serii nu se va mai simți intimidant. 

Cea mai mare parte este foarte simplă, dar sintaxa este un pic ciudat dacă tocmai ați început să codificați - în special în Ruby. Stai acolo, nu e știință de rachete!

Subiecte

  • Include & Încărcare plăcută
  • Adunarea tabelelor
  • Încărcare plăcută
  • Scopes
  • Cumulările
  • Dinamici Finders
  • Câmpuri specifice
  • Custom SQL

Include & Încărcare plăcută

Aceste interogări includ mai mult de o tabelă de baze de date pentru a lucra cu și ar putea fi cel mai important pentru a lua away de la acest articol. Acesta se reduce la acest lucru: în loc de a face mai multe interogări pentru informații care este răspândit pe mai multe mese, include încearcă să mențină aceste aspecte la un nivel minim. Conceptul cheie care se află în spatele acestui proces este numit "încărcare dornică" și înseamnă că încărcăm obiecte asociate atunci când găsim.

Dacă am făcut acest lucru prin iterarea unei colecții de obiecte și apoi încercând să accesăm înregistrările asociate din alt tabel, am întâmpina o problemă numită "problemă de interogare N + 1". De exemplu, pentru fiecare agent.handler într-o colecție de agenți, am declanșa interogări separate atât pentru agenții, cât și pentru cei care le manipulează. Asta este ceea ce trebuie să evităm, deoarece acest lucru nu se mărește deloc. În schimb, facem următoarele:

Șine

agenți = Agent.include ((: manipulanți)

Dacă vom repeta acum o astfel de colecție de agenți - actualizând faptul că nu am limitat numărul de înregistrări returnate pentru acum - vom termina cu două interogări în loc de eventual un gazilion. 

SQL

SELECT "agenți". * FROM "agenți" SELECT "manipulatori". * FROM "manipulatori" WHERE "

Acest agent din listă are doi agenți de manipulare, iar atunci când cerem acum obiectul de agent pentru agenții săi, nu trebuie să fie lansate interogări suplimentare de bază de date. Putem face acest lucru un pas mai departe, bineînțeles, și încărcarea dornică de mai multe înregistrări de mese asociate. Dacă ar fi nevoie să încărăm nu numai manipulatorii ci și misiunile asociate ale agentului, indiferent de motiv, am putea folosi include asa.

Șine

agenți = Agent.includes (: manipulatori,: misiune)

Simplu! Fiți atenți la utilizarea versiunilor singulare și pluraliste pentru acestea. Ele depind de asociațiile dvs. de model. A are multe asocierea utilizează plural, în timp ce o aparține lui sau a are unul are nevoie de versiunea singulară, desigur. Dacă aveți nevoie, puteți, de asemenea, tuck pe o Unde clauza pentru specificarea condițiilor suplimentare, dar modul preferat de specificare a condițiilor pentru tabelele asociate care sunt încărcate încărcate este prin utilizarea se alătură in schimb. 

Un lucru care trebuie păstrat în minte în ceea ce privește încărcarea dornică este că datele care vor fi adăugate vor fi trimise în întregime la Active Record - care la rândul lor construiesc obiecte Ruby, inclusiv aceste atribute. Acest lucru este în contrast cu "pur și simplu" aderarea la date, în cazul în care veți obține un rezultat virtual pe care îl puteți utiliza pentru calcule, de exemplu, și va fi mai puțin de scurgere de memorie decât include.

Adunarea tabelelor

Tabelele de îmbinare reprezintă un alt instrument care vă permite să evitați trimiterea prea multor interogări inutile pe conductă. Un scenariu comun se unește cu două tabele cu o singură interogare care returnează un fel de înregistrare combinată. se alătură este doar o altă metodă de identificare a înregistrării active care vă permite să introduceți termeni SQL-A ADERA Mese. Aceste interogări pot întoarce înregistrările combinate din mai multe tabele și veți obține o tabelă virtuală care combină înregistrările din aceste tabele. Acest lucru este destul de rad când comparați că la ardere toate tipurile de interogări pentru fiecare tabel în loc. Există câteva tipuri diferite de suprapuneri de date pe care le puteți obține cu această abordare. 

Intrarea internă este modus operandi implicit pentru se alătură. Aceasta se potrivește cu toate rezultatele care se potrivesc cu un anumit id și cu reprezentarea acestuia ca o cheie străină dintr-un alt obiect sau tabel. În exemplul de mai jos, puneți pur și simplu: dați-mi toate misiunile în care se află misiunea id arată ca mission_id în tabelul unui agent. "agenți". "mission_id" = "misiuni". "id". Intrarea internă exclude relațiile care nu există.

Șine

Mission.joins (: agenți)

SQL

SELECT "misiuni". * FROM "misiuni" INNER JOIN "agenți" pe "agenți" "mission_id" = "mission"

Deci, ne potrivim misiunilor și agenților lor care le însoțesc - într-o singură interogare! Sigur, am putea primi mai întâi misiunile, să le repetăm ​​unul câte unul și să le cerem agenților lor. Dar atunci ne-am întoarce la problema noastră de "problemă de interogare N + 1". Nu, mulțumesc! 

Ceea ce este frumos în legătură cu această abordare este că nu vom primi niciun caz nil cu legături interioare; primim numai înregistrări returnate care se potrivesc cu ID-urile lor cu chei străine în tabelele asociate. Dacă trebuie să găsim misiuni, de exemplu, care nu au nici un agent, ar fi nevoie de o conexiune externă în schimb. Întrucât aceasta implică în prezent scrierea propriului dvs. ALEGEREA EXTERIORĂ SQL, vom examina acest lucru în ultimul articol. Înapoi la conexiunile standard, puteți, de asemenea, să vă alăturați mai multor tabele asociate, desigur.

Șine

Mission.joins (: agenți,: cheltuieli,: manipulatori)

Și puteți adăuga pe acestea și pe altele Unde clauze pentru a specifica căutătorii dvs. chiar mai mult. Mai jos, căutăm numai misiuni executate de James Bond și numai agenții care aparțin misiunii "Moonraker" în al doilea exemplu.

Mission.joins (: agents) .de unde (agenți: name: 'James Bond')

SQL

SELECTAȚI "misiuni". * FROM "misiuni" INNER JOIN "agenți" ON "agenți" "mission_id" = "misiuni". [["nume", "James Bond"]]

Șine

Agent.joins (: mission) .unde (misiuni: mission_name: 'Moonraker')

SQL

SELECȚI "agenți". * FROM "agenți" INNER JOIN "misiuni" "ON" misiuni "" id "=" [["numele misiunii", "Moonraker"]]

Cu se alătură, trebuie să fiți atenți la utilizarea singulară și plurală a asociațiilor dvs. de model. Pentru ca al meu Misiune clasă has_many: agenți, putem folosi pluralul. Pe de altă parte, pentru Agent clasă belongs_to: misiune, numai versiunea singulară funcționează fără a sufla. Un mic detaliu important: Unde partea este mai simplă. Deoarece scanați mai multe rânduri din tabel care îndeplinesc o anumită condiție, forma plurală are întotdeauna sens.

Scopes

Scopurile sunt o modalitate la îndemână de a extrage nevoile obișnuite de interogare în metode bine-numite de-ale tale. În acest fel, acestea sunt ușor de trecut și, eventual, mai ușor de înțeles dacă alții trebuie să lucreze cu codul dvs. sau dacă aveți nevoie să revedeți anumite întrebări în viitor. Puteți să le definiți pentru modelele unice, dar să le utilizați și pentru asociațiile lor. 

Cerul este limita într-adevăr-se alătură, include, și Unde sunt toate jocul corect! Din moment ce domeniul se întoarce ActiveRecord :: Relații obiecte, le puteți lansa și puteți apela alte domenii deasupra lor fără ezitare. Extinderea unor astfel de domenii și legarea acestora pentru interogări mai complexe este foarte utilă și le face pe cele mai lungi tot mai ușor de citit. Scopurile sunt definite prin sintaxa "stabby lambda":

Șine

clasa Misiune < ActiveRecord::Base has_many: agents scope :successful, -> where (mission_complete: true) terminați cu succes
Agent de clasă < ActiveRecord::Base belongs_to :mission scope :licenced_to_kill, -> unde (license_to_kill: true) domeniul: femeie, -> unde (femeie: adevărat) domeniu: gambler, Agent.womanizer.gambler Agent.licenced_to_kill.womanizer.gambler

SQL

SELECTați "agenții". * FROM "agenți" WHERE "agenți". "License_to_kill" =? ȘI "agenți". "Femeie" =? ȘI "agenți". "Gambler" =? [["license_to_kill", "t"], ["femeie", "t"], ["gambler", "t"]]

După cum puteți vedea din exemplul de mai sus, descoperirea lui James Bond este mult mai plăcută atunci când puteți să alăturați doar domeniile împreună. În acest fel, puteți amesteca și potrivi diverse interogări și puteți rămâne în același timp DRY. Dacă aveți nevoie de scopuri prin intermediul asociațiilor, acestea sunt la dispoziția dvs., de asemenea:

Mission.last.agents.licenced_to_kill.womanizer.gambler
SELECT "misiunile". * FROM "misiuni" ORDER BY "misiuni" "id" DESC LIMIT 1 SELECT "agenți". ȘI "agenți". "License_to_kill" =? ȘI "agenți". "Femeie" =? ȘI "agenți". "Gambler" =? ["mission_id", 33], ["license_to_kill", "t"], ["femeie", "t"], ["gambler"

De asemenea, puteți redefini default_scope pentru că atunci când te uiți la ceva de genul Mission.all.

clasa Misiune < ActiveRecord::Base default_scope  where status: "In progress"  end Mission.all

SQL

 SELECTAȚI "misiuni". * FROM "misiuni" WHERE "misiuni". "Status" =? [["status", "În curs de desfășurare"]]

Cumulările

Această secțiune nu este atât de avansată în ceea ce privește înțelegerea implicată, dar veți avea nevoie de ele mai des decât de scenarii care pot fi considerate un pic mai avansate decât media dvs. de căutare ca .toate, .primul, .find_by_id sau orice altceva. Filtrarea bazată pe calculele de bază, de exemplu, este, cel mai probabil, ceva care începătorii nu intră în contact imediat. Ce ne uităm exact aici?

  • sumă
  • numara
  • minim
  • maxim
  • in medie

Peasy ușor, nu? Lucrul interesant este că, în loc de a trece printr-o colecție de obiecte returnate pentru a face aceste calcule, putem lăsa Active Record să facă toate aceste lucruri pentru noi și să returneze aceste rezultate cu interogările - într-o singură interogare de preferință. Nisa, huh?

  • numara

Șine

Mission.count # => 24

SQL

SELECT COUNT (*) DE LA "misiuni"
  • in medie

Șine

Agent.average (: number_of_gadgets) .to_f # => 3.5

SQL

SELECT AVG ("agenți". "Number_of_gadgets") FROM "agenți"

Din moment ce știm acum cum putem face uz se alătură, putem face un pas mai departe și doar să solicităm media obiectelor pe care agenții le au pentru o anumită misiune, de exemplu.

Șine

Agent.joins (: misiune) .unde (misiuni: name: 'Moonraker'). Medie (: number_of_gadgets) .to_f # => 3.4

SQL

SELECT AVG ("agenți", "number_of_gadgets") FROM "agenți" INNER JOIN "misiuni" ON "misiuni" "id" = "agents". [["nume", "Moonraker"]]

Grupează acest număr mediu de gadget-uri pe nume de misiuni devine trivial la acel moment. Vedeți mai multe despre gruparea de mai jos:

Șine

Agent.joins (: misiune) .group ( 'missions.name'). (Media: number_of_gadgets)

SQL

SELECT AVG ("agenți", "number_of_gadgets") AS average_number_of_gadgets, missions.name AS missions_name FROM "agenți" INNER JOIN "misiuni" ON "misiuni"
  • sumă

Șine

Agent.sum (: number_of_gadgets) Agent.where (license_to_kill: true) .sum (: number_of_gadgets) Agent.where.not (license_to_kill: true) .sum (: number_of_gadgets)

SQL

SELECT SUM ("agenți". "Number_of_gadgets") FROM "agenți" SELECT SUM ("agenți". ["license_to_kill", "t"]] SELECT SUM ("agenți". "number_of_gadgets") FROM "
  • maxim

Șine

Agent.maximum (: number_of_gadgets) Agent.where (license_to_kill: true) .maximum (: number_of_gadgets) 

SQL

SELECT MAX ("agenți". "Number_of_gadgets") FROM "agenți" SELECT MAX ("agenți".) "License_to_kill" =? [["license_to_kill", "t"]]
  • minim

Șine

Agent.minimum (: iq) Agent.where (license_to_kill: true) .minimum (: iq) 

SQL

SELECT MIN ("agenți". "Iq") FROM "agenți" SELECT MIN ("agenți". [["license_to_kill", "t"]]

Atenţie!

Toate aceste metode de agregare nu te lasă să lansezi alte lucruri - sunt terminale. Ordinea este importantă pentru a face calcule. Nu înțelegem ActiveRecord :: Relația obiect înapoi de la aceste operațiuni, ceea ce face ca muzica să se oprească la acel moment - obținem în schimb un hash sau numere. Exemplele de mai jos nu vor funcționa:

Șine

Agent.maximum (: number_of_gadgets) .de unde (license_to_kill: true) Agent.sum (: number_of_gadgets) .de unde (agent_jucător: adevărat) Agent.joins (: „)

grupate

Dacă doriți ca calculele să fie defalcate și sortate în grupuri logice, ar trebui să utilizați a GRUP clauză și nu face acest lucru în Ruby. Ceea ce vreau să spun este că ar trebui să eviți iterarea unui grup care produce potențiale tone de interogări.

Șine

Agent.joins (: mission) .group ('missions.name') media (: number_of_gadgets) # => "Moonraker" => 4.4, "Octopussy" => 4.9

SQL

SELECT AVG ("agenți", "number_of_gadgets") AS average_number_of_gadgets, missions.name AS missions_name FROM "agenți" INNER JOIN "misiuni" ON "misiuni"

Acest exemplu găsește toți agenții care sunt grupați la o anumită misiune și returnează un hash cu numărul mediu calculat de gadget-uri ca valori - într-o singură interogare! Da! Același lucru este valabil și pentru celelalte calcule, desigur. În acest caz, este cu adevărat mai logic să lăsați SQL să facă lucrul. Numărul de interogări pentru aceste agregări este prea important.

Dinamici Finders

Pentru fiecare atribut pe modelele tale, să zicem Nume, adresa de emailfavorite_gadget și așa mai departe, Active Record vă permite să utilizați metode de căutare foarte ușor de citit, create dinamic pentru dvs. Sună criptic, știu, dar nu înseamnă altceva decât find_by_id sau find_by_favorite_gadget. find_by parte este standard, iar funcția Active Record dă pe numele atributului pentru tine. Puteți chiar să adăugați un ! dacă doriți ca acel căutător să ridice o eroare dacă nu se poate găsi nimic. Partea bolnavă este, puteți chiar să îmbinați împreună aceste metode dinamice. La fel ca aceasta:

Șine

Agent.find_by_name ("James Bond") Agent.find_by_name_and_licence_to_kill ("James Bond", adevărat)

SQL

Selectați "agenți". * FROM "agenți" WHERE "agenți". "Nume" =? LIMIT 1 [["nume", "James Bond"]] SELECT "agenți". * FROM "agenți" WHERE "agenți". ȘI "agenți". "License_to_kill" =? LIMIT 1 [["nume", "James Bond"], ["license_to_kill", "t"]]

Bineînțeles că puteți merge cu acest lucru, dar cred că își pierde farmecul și utilitatea dacă depășiți două atribute:

Șine

Agent.find_by_name_and_licence_to_kill_and_womanizer_and_gambler_and_number_of_gadgets ("James Bond", adevărat, adevărat, adevărat, 3) 

SQL

Selectați "agenți". * FROM "agenți" WHERE "agenți". "Nume" =? ȘI "agenți". "License_to_kill" =? ȘI "agenți". "Femeie" =? ȘI "agenți". "Gambler" =? ȘI "agenți". "Number_of_gadgets" =? LIMIT 1 [["nume", "James Bond"], ["license_to_kill", "t"], ["feminin", "t"], ["gambler" ]]

În acest exemplu, este totuși plăcut să vedem cum funcționează sub capotă. Fiecare nou _și_ adaugă un SQL ȘI operator pentru a lega logic atributele împreună. Per ansamblu, principalul beneficiu al detectoarelor dinamice este că lizibilitatea-prinderea pe prea multe atribute dinamice pierde acest avantaj rapid, totuși. Folosesc rar acest lucru, poate mai ales când mă joc în consola, dar este cu siguranță bine să știți că Rails oferă această minciună puțină.

Câmpuri specifice

Înregistrare activă vă oferă opțiunea de a returna obiecte care sunt puțin mai concentrate asupra atributelor pe care le poartă. De obicei, dacă nu este specificat altfel, interogarea va cere toate câmpurile într-un rând prin * (SELECTĂ "agenți". *), iar apoi Active Record construieste obiecte Ruby cu setul complet de atribute. Cu toate acestea, puteți Selectați numai câmpurile specifice care ar trebui să fie returnate de interogare și să limiteze numărul de atribute pe care obiectele Ruby trebuie să le "transporte".

Șine

Agent.select ("nume") => #, #,...]>

SQL

SELECTați "agenții". "Nume" FROM "agenți"

Șine

Agent.select ("număr, favorit_gadget") => #, #,...]>

SQL

SELECT "agenți" "" număr "," agenți "." Favorite_gadget "FROM" agenți "

După cum puteți vedea, obiectele returnate vor avea doar atributele selectate, plus numerele lor, desigur, care sunt date cu orice obiect. Nu are importanță dacă folosiți șiruri de caractere, ca mai sus, sau simboluri - interogarea va fi aceeași.

Șine

Agent.select (: number_of_kills) Agent.select (: name,: license_to_kill)

Un cuvânt de precauție: Dacă încercați să accesați atributele pe obiectul pe care nu l-ați selectat în interogările dvs., veți primi un MissingAttributeError. Din moment ce id va fi în mod automat furnizat pentru tine oricum, puteți cere pentru id-ul fără a selecta, totuși.

Custom SQL

Nu în ultimul rând, puteți scrie propriul dvs. SQL personalizat prin find_by_sql. Dacă sunteți suficient de încrezători în propria dvs. SQL-Fu și aveți nevoie de anumite apeluri personalizate către baza de date, această metodă ar putea fi foarte utilă uneori. Dar aceasta este o altă poveste. Nu uitați să verificați mai întâi metodele Active Record wrap și să evitați să reinventați roata în cazul în care Rails încearcă să vă întâlnească mai mult de jumătate.

Șine

Agent.find_by_sql ("SELECT * FROM agents") Agent.find_by_sql ("SELECT name, license_to_kill FROM agents") 

În mod surprinzător, aceasta are ca rezultat:

SQL

SELECT * FROM agenți SELECT nume, license_to_kill FROM agenți

Din moment ce domeniile și metodele proprii de clasă pot fi folosite interschimbabil pentru nevoile personalizate ale căutătorilor, putem face acest lucru cu un pas mai departe pentru interogări SQL mai complexe. 

Șine

Agent de clasă < ActiveRecord::Base… def self.find_agent_names query = <<-SQL SELECT name FROM agents SQL self.find_by_sql(query) end end

Putem scrie metode de clasă care încapsulează SQL într-un document Here. Acest lucru ne permite să scriem șiruri cu mai multe linii într-o manieră foarte lizibilă și apoi să stocăm șirul SQL într-o variabilă pe care o putem reutiliza și treci în find_by_sql. În felul acesta, nu am tencuială tone de cod de interogare în cadrul apelului metodei. Dacă aveți mai mult de un loc pentru a utiliza această interogare, este și DRY.

Din moment ce acest lucru ar trebui să fie un prieten nou și nu un tutorial SQL în sine, am păstrat exemplul foarte minimalist pentru un motiv. Tehnica pentru interogări mai complexe este însă aceeași. Este ușor să vă imaginați că aveți o interogare SQL personalizată acolo care se întinde dincolo de zece linii de cod. 

Du-te ca niște nuci cum trebuie - în mod rezonabil! Poate fi un economizor de viață. Un cuvânt despre sintaxa de aici. SQL parte este doar un identificator aici pentru a marca începutul și sfârșitul șirului. Pun pariu că nu veți avea nevoie de această metodă atât de mult - să sperăm! Are cu siguranță locul său, iar terenul Rails nu ar fi același fără el - în cazurile rare pe care le veți dori absolut să vă reglați propriul SQL cu el.

Gândurile finale

Sper că ai un pic mai confortabil în scrierea de interogări și lectură a temutului ol 'raw SQL. Cele mai multe dintre subiectele pe care le-am abordat în acest articol sunt esențiale pentru scrierea de interogări care se ocupă de logica de afaceri mai complexă. Luați-vă timp să înțelegeți aceste lucruri și să jucați în jur cu întrebări în consolă. 

Sunt destul de sigur că atunci când părăsiți terenul tutorial în urmă, mai devreme sau mai târziu, Rails cred că va crește semnificativ dacă lucrați la primele proiecte din viața reală și trebuie să vă creați propriile interogări personalizate. Dacă sunteți încă un pic timid de subiect, aș spune pur și simplu să se distreze cu ea - într-adevăr nu este știință rachetă!

Cod